diff --git a/.github/scripts/run_langgraph_cli_test.py b/.github/scripts/run_langgraph_cli_test.py new file mode 100644 index 000000000..a6024778b --- /dev/null +++ b/.github/scripts/run_langgraph_cli_test.py @@ -0,0 +1,116 @@ +import asyncio +import json +import os +import pathlib +import sys +import langgraph_cli +import langgraph_cli.docker +import langgraph_cli.config + +from langgraph_cli.exec import Runner, subp_exec +from langgraph_cli.progress import Progress +from langgraph_cli.constants import DEFAULT_PORT + + +def test( + config: pathlib.Path, + port: int, + tag: str, + verbose: bool, +): + with Runner() as runner, Progress(message="Pulling...") as set: + # check docker available + capabilities = langgraph_cli.docker.check_capabilities(runner) + # open config + with open(config) as f: + config_json = langgraph_cli.config.validate_config(json.load(f)) + + set("Running...") + args = [ + "run", + "--rm", + "-p", + f"{port}:8000", + ] + if isinstance(config_json["env"], str): + args.extend( + [ + "--env-file", + str(config.parent / config_json["env"]), + ] + ) + else: + for k, v in config_json["env"].items(): + args.extend( + [ + "-e", + f"{k}={v}", + ] + ) + if capabilities.healthcheck_start_interval: + args.extend( + [ + "--health-interval", + "5s", + "--health-retries", + "1", + "--health-start-period", + "10s", + "--health-start-interval", + "1s", + ] + ) + else: + args.extend( + [ + "--health-interval", + "5s", + "--health-retries", + "2", + ] + ) + + _task = None + + def on_stdout(line: str): + nonlocal _task + if "GET /ok" in line or "Uvicorn running on" in line: + set("") + sys.stdout.write( + f"""Ready! +- API: http://localhost:{port} +""" + ) + sys.stdout.flush() + _task.cancel() + return True + return False + + async def subp_exec_task(*args, **kwargs): + nonlocal _task + _task = asyncio.create_task(subp_exec(*args, **kwargs)) + await _task + + try: + runner.run( + subp_exec_task( + "docker", + *args, + tag, + verbose=verbose, + on_stdout=on_stdout, + ) + ) + except asyncio.CancelledError: + pass + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument("-t", "--tag", type=str) + parser.add_argument("-c", "--config", type=str, default="./langgraph.json") + parser.add_argument("-p", "--port", default=DEFAULT_PORT) + args = parser.parse_args() + test(pathlib.Path(args.config), args.port, args.tag, verbose=True) diff --git a/.github/workflows/_integration_test.yml b/.github/workflows/_integration_test.yml index 4f4e9bac7..c2d7cb895 100644 --- a/.github/workflows/_integration_test.yml +++ b/.github/workflows/_integration_test.yml @@ -39,22 +39,36 @@ jobs: - name: Install cli globally if: steps.changed-files.outputs.all run: pip install -e . - - name: Start service A + - name: Build and test service A if: steps.changed-files.outputs.all + working-directory: libs/cli/examples run: | - timeout 60 langgraph test -c examples/langgraph.json --verbose || (exit "$(($? == 124 ? 0 : $?))") - - name: Start service B + # The build-arg isn't used; just testing that we accept other args + langgraph build -t langgraph-test-a --base-image "langchain/langgraph-trial" + cp .env.example .envg + timeout 60 python ../../../.github/scripts/run_langgraph_cli_test.py -c langgraph.json -t langgraph-test-a + - name: Build and test service B if: steps.changed-files.outputs.all working-directory: libs/cli/examples/graphs run: | - timeout 60 langgraph test --verbose || (exit "$(($? == 124 ? 0 : $?))") - - name: Start service C + langgraph build -t langgraph-test-b --base-image "langchain/langgraph-trial" + timeout 60 python ../../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-b + - name: Build and test service C if: steps.changed-files.outputs.all working-directory: libs/cli/examples/graphs_reqs_a run: | - timeout 60 langgraph test --verbose || (exit "$(($? == 124 ? 0 : $?))") - - name: Start service D + langgraph build -t langgraph-test-c --base-image "langchain/langgraph-trial" + timeout 60 python ../../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-c + - name: Build and test service D if: steps.changed-files.outputs.all working-directory: libs/cli/examples/graphs_reqs_b run: | - timeout 60 langgraph test --verbose || (exit "$(($? == 124 ? 0 : $?))") + langgraph build -t langgraph-test-d --base-image "langchain/langgraph-trial" + timeout 60 python ../../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-d + + - name: Build JS service + if: steps.changed-files.outputs.all + working-directory: libs/cli/js-examples + run: | + langgraph build -t langgraph-test-e + \ No newline at end of file diff --git a/.github/workflows/_test.yml b/.github/workflows/_test.yml index 3a4f0d4d2..29eab4cd3 100644 --- a/.github/workflows/_test.yml +++ b/.github/workflows/_test.yml @@ -35,6 +35,7 @@ jobs: cache-key: test-${{ inputs.working-directory }} - name: Login to Docker Hub uses: docker/login-action@v3 + if: ${{ !github.event.pull_request.head.repo.fork }} with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_RO_TOKEN }} diff --git a/.github/workflows/_test_langgraph.yml b/.github/workflows/_test_langgraph.yml index 2aad17993..5c3f5182e 100644 --- a/.github/workflows/_test_langgraph.yml +++ b/.github/workflows/_test_langgraph.yml @@ -19,14 +19,19 @@ jobs: - "3.13" core-version: - "latest" + ff-send-v2: + - "false" include: - python-version: "3.11" - core-version: ">=0.2.39,<0.3.0" + core-version: ">=0.2.42,<0.3.0" + - python-version: "3.11" + core-version: "latest" + ff-send-v2: "true" defaults: run: working-directory: libs/langgraph - name: "test #${{ matrix.python-version }} (langchain-core: ${{ matrix.core-version }})" + name: "test #${{ matrix.python-version }} (langchain-core: ${{ matrix.core-version }}, ff-send-v2: ${{ matrix.ff-send-v2 }})" steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }} @@ -37,6 +42,7 @@ jobs: cache-key: test-langgraph - name: Login to Docker Hub uses: docker/login-action@v3 + if: ${{ !github.event.pull_request.head.repo.fork }} with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_RO_TOKEN }} @@ -51,6 +57,8 @@ jobs: - name: Run tests shell: bash + env: + LANGGRAPH_FF_SEND_V2: ${{ matrix.ff-send-v2 }} run: | make test diff --git a/.github/workflows/_test_release.yml b/.github/workflows/_test_release.yml index 39be5ea10..a4d81e1e2 100644 --- a/.github/workflows/_test_release.yml +++ b/.github/workflows/_test_release.yml @@ -93,3 +93,5 @@ jobs: # This is *only for CI use* and is *extremely dangerous* otherwise! # https://github.com/pypa/gh-action-pypi-publish#tolerating-release-package-file-duplicates skip-existing: true + # Temp workaround since attestations are on by default as of gh-action-pypi-publish v1.11.0 + attestations: false diff --git a/.github/workflows/_test_scheduler_kafka.yml b/.github/workflows/_test_scheduler_kafka.yml index da01a9bd8..1f0edf420 100644 --- a/.github/workflows/_test_scheduler_kafka.yml +++ b/.github/workflows/_test_scheduler_kafka.yml @@ -29,6 +29,7 @@ jobs: cache-key: test-scheduler-kafka - name: Login to Docker Hub uses: docker/login-action@v3 + if: ${{ !github.event.pull_request.head.repo.fork }} with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_RO_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1af6acc7c..c9bfb9e9a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,7 @@ jobs: "libs/cli", "libs/checkpoint", "libs/checkpoint-sqlite", + "libs/checkpoint-duckdb", "libs/checkpoint-postgres", "libs/scheduler-kafka", ] @@ -47,6 +48,7 @@ jobs: "libs/cli", "libs/checkpoint", "libs/checkpoint-sqlite", + "libs/checkpoint-duckdb", "libs/checkpoint-postgres" ] uses: ./.github/workflows/_test.yml diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml index a572429d4..721bb4117 100644 --- a/.github/workflows/deploy_docs.yml +++ b/.github/workflows/deploy_docs.yml @@ -44,6 +44,8 @@ jobs: deploy: # needs: run-changed-notebooks runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.MKDOCS_GITHUB_TOKEN }} steps: - uses: actions/checkout@v4 with: @@ -58,8 +60,14 @@ jobs: - name: Install dependencies run: | - poetry install --with test - poetry run pip install -U pytest pytest-check-links langsmith langchain GitPython + poetry install --with test --no-root + poetry run pip install -U \ + pytest \ + pytest-check-links \ + langsmith \ + langchain \ + GitPython \ + "git+https://${GITHUB_TOKEN}@github.com/langchain-ai/mkdocs-material-insiders.git" - name: Lint Docs # This step lints the docs using the existing linting set up. @@ -81,7 +89,11 @@ jobs: --check-links-ignore "https://x.com/.*" \ --check-links-ignore "https://github\.com/.*" \ --check-links-ignore "/.*\.(ipynb|html)$" \ - --check-links $(find docs/site -name "index.html" | grep -v 'storm/index.html') + --check-links-ignore "https://python\.langchain\.com/.*" \ + --check-links-ignore "https://openai\.com/.*" \ + --check-links-ignore "https://pepy\.tech/.*" \ + --check-links $(find docs/site -name "index.html" | grep -v 'storm/index.html') + else echo "Fetching changes from origin/main..." git fetch origin main diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 460574b36..d3d8626aa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -270,6 +270,8 @@ jobs: packages-dir: ${{ inputs.working-directory }}/dist/ verbose: true print-hash: true + # Temp workaround since attestations are on by default as of gh-action-pypi-publish v1.11.0 + attestations: false mark-release: needs: diff --git a/README.md b/README.md index a375771f0..6f7b62676 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ LangGraph is inspired by [Pregel](https://research.google/pubs/pub37252/) and [Apache Beam](https://beam.apache.org/). The public interface draws inspiration from [NetworkX](https://networkx.org/documentation/latest/). LangGraph is built by LangChain Inc, the creators of LangChain, but can be used without LangChain. +[LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform) is infrastructure for deploying LangGraph agents. It is a commercial solution for deploying agentic applications to production, built on the open-source LangGraph framework. The LangGraph Platform consists of several components that work together to support the development, deployment, debugging, and monitoring of LangGraph applications: [LangGraph Server](https://langchain-ai.github.io/langgraph/concepts/langgraph_server) (APIs), [LangGraph SDKs](https://langchain-ai.github.io/langgraph/concepts/sdk) (clients for the APIs), [LangGraph CLI](https://langchain-ai.github.io/langgraph/concepts/langgraph_cli) (command line tool for building the server), [LangGraph Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio) (UI/debugger), + To learn more about LangGraph, check out our first LangChain Academy course, *Introduction to LangGraph*, available for free [here](https://academy.langchain.com/courses/intro-to-langgraph). ### Key Features @@ -26,6 +28,16 @@ To learn more about LangGraph, check out our first LangChain Academy course, *In - **Streaming Support**: Stream outputs as they are produced by each node (including token streaming). - **Integration with LangChain**: LangGraph integrates seamlessly with [LangChain](https://github.com/langchain-ai/langchain/) and [LangSmith](https://docs.smith.langchain.com/) (but does not require them). +### LangGraph Platform + +LangGraph Platform is a commercial solution for deploying agentic applications to production, built on the open-source LangGraph framework. +Here are some common issues that arise in complex deployments, which LangGraph Platform addresses: + +- **Streaming support**: LangGraph Server provides [multiple streaming modes](https://langchain-ai.github.io/langgraph/concepts/streaming) optimized for various application needs +- **Background runs**: Runs agents asynchronously in the background +- **Support for long running agents**: Infrastructure that can handle long running processes +- **[Double texting](https://langchain-ai.github.io/langgraph/concepts/double_texting)**: Handle the case where you get two messages from the user before the agent can respond +- **Handle burstiness**: Task queue for ensuring requests are handled consistently without loss, even under heavy loads ## Installation diff --git a/docs/_scripts/prepare_notebooks_for_ci.py b/docs/_scripts/prepare_notebooks_for_ci.py index ed460a10f..dfa1e1c70 100644 --- a/docs/_scripts/prepare_notebooks_for_ci.py +++ b/docs/_scripts/prepare_notebooks_for_ci.py @@ -36,10 +36,11 @@ NOTEBOOKS_NO_EXECUTION = [ "docs/docs/tutorials/rag/langgraph_self_rag_local.ipynb", # this loads a massive dataset from gcp "docs/docs/tutorials/usaco/usaco.ipynb", + # TODO: figure out why autogen notebook is not runnable (they are just hanging. possible due to code execution?) + "docs/docs/how-tos/autogen-integration.ipynb", # TODO: need to update these notebooks to make sure they are runnable in CI "docs/docs/tutorials/storm/storm.ipynb", # issues only when running with VCR "docs/docs/tutorials/lats/lats.ipynb", # issues only when running with VCR - "docs/docs/tutorials/multi_agent/hierarchical_agent_teams.ipynb", # taking a very long time to run "docs/docs/tutorials/rag/langgraph_crag.ipynb", # flakiness from tavily "docs/docs/tutorials/rag/langgraph_adaptive_rag.ipynb", # Cannot create a consistent method resolution error from VCR "docs/docs/how-tos/map-reduce.ipynb" # flakiness from structured output, only when running with VCR diff --git a/docs/cassettes/agent_supervisor_45a92dfd-0e11-47f5-aad4-b68d24990e34.msgpack.zlib b/docs/cassettes/agent_supervisor_45a92dfd-0e11-47f5-aad4-b68d24990e34.msgpack.zlib index 7e32556a6..847bd700c 100644 --- a/docs/cassettes/agent_supervisor_45a92dfd-0e11-47f5-aad4-b68d24990e34.msgpack.zlib +++ b/docs/cassettes/agent_supervisor_45a92dfd-0e11-47f5-aad4-b68d24990e34.msgpack.zlib @@ -1 +1 @@ -eNrtXQdYFEf7R1GsKInGYGwrsaF3cJW7wyggIE2KVEER9/b2uIW722N3DzgQu0kkJnp2LFhoCgKKXWKPXcGCvWv8NNbYO/xn9g4BNV+S7zHP/8uX47Hc7bwz885bf/POjk4oSMYpmiC1jVYSWganUIwBX+i5EwooPEmP08ykfA3OqEhFbkhwWHiOniLO9FUxjI52dXZGdYQTqcO1KOGEkRrnZL4zpkIZZ/BZp8bZYXLlpMJw1np/uoMGp2k0HqcdXJHh6Q4YCebSMuCLQzSpR1AKR1CE1utwKpmgSQphUDoRVyApBKNCNKgWjSe08YACdIPconBsRI4zKTiuRRgVjihJtZpMgUQpJJUISFwRJBSncZTCVDjFQTxJBU45IT5E8nsd9DROIea1csAHWkdqzTNDQtN4CEMiQDKIFk9lnBBvFFPVNqQQajUC+FaSlAZwCBlHUDDAuwMRcBJar2ZotplmUEZPOyFRKsCQktAStApXvDP9YL8gvzBfJwcO4kCRahwKizbQDK5xyOAgDWQYRREMlKCcInAlHIRdOPigIykGAbLSEYko3WAkuOz3xqmTTwNJo3IyGecgKSoSoVWkXq14Kws3JJiqfZaCmzl2Q8JwNY7BiXGEVAKF9zY19OYgveu0Ar+xeukd+6ElxoJnGtCshg/jdQxXREIyLfjKB3/rUApVq3F1HEOS6jgMfIampUTVNODUgWYoHNXUewCGBEoCMqfgJDwnCXzG9lSRBAafpTswBh3LgVKvZZ0ATvf2MyTQohqWgCL1DB7KqorGHTIyzGOZbfs/HAZQKXAaowidmdDBwbxM4H9AFWxXHQX8jWII3PQVqoD9gGv1cLXDHUyChj3rBA2/sYJ2gDKtZQ+ICJi/iXto/gSFK9gh2EHrU5LyBKBNQJkRm1GgwlEwEn3Ryi5XRdKMsaRhEChFMQwHysK1GKkA4xuL49MIHQdR4Eo1yuCFwK60OCsKY2Eijuu4qBrYXL6pl3EVqtOpCYy1OucEmtSuNBsnF/LyfnMhtGIuCCtaxrg2GDDh4eccYgDRSovwnURSJ96qVC7wNEKrBtGHq0YBP/k6tr28foMOxRLBIFxzJDTmmzqX1KchaWNeIIoFhzUYEgrYmIdSGhfRmvrPKb2WITS4scAz5P3pzI110wmd+Hwn2eoGA9MGLWbMY813Q4POOEMZuBgJxjAu5eVjJJlI4MYzD+PiMGWcXDMg3EesJYb5i0Pw1CGemhRNkjg0zYeOoqMMggR9JBkdEBVPD8NCvAZ5S7l8iUAidBHyZHwu34nnxHficwNQ/ySJgKGjDXiwQkfFKHRqp5jUtCBpMuajHqL3HZQQhRtiApXK1EheOIb7+oUlR6RFeWuC1SnR8SlkCqoVpQT4S0RUmjIwOcArglCHRQyN748A7vTJhGIA5euRKI2KDBkq4Puj8hg8MTUu1UMlDvTgC0LU0mEKPIrwF6aqiQTD0HrsiXkSLs/MoQtPJOXBn5Ja21Dj2nhGZcyRuciWU2Z3mphvirATcoEd4of2FZjzz7LggDoT/izXC9ikcUu4Ss9BBC4gbOkQAU8gQvhiV57IVSxGfALDV3qapwn/oAmuDqdQLa0EZuhda/IFmEqvBemr0PODxr4FGjvQJGQfBD0unqojaZxr5sq4chg31JSNuH5ea0yexSWpeFRLpLHTGldAQwaZltCuNTeDsACHBJNzNbQxRyh0KTG31NpYIVgXj8vncXn8TdDzMeBSkHGYILg0joG8zhiMZzgaNBX60wAhXyx0AULujxBaTK1X4GF6uRepAXPS/REdhatJVLE5lQsiKq4mNARQAvunOY8CX+FDFW18n4IhE3EtbVwu5Jl+ttYnoXA4A1zG24FyZeDnxw8T1Y4lgDRSF9nmhmQ0Xo+hHBcNvfH9dvMQy3j0ytRaYi6hMJ7pAb7EyZSoQsQXo5hAKMSVGKbEJXy5hCdUCMUKkZgnKvUczPUEYADnhrHWZizwig7yCPTzXD+MW99suMFsUAftWpLWEkplfhhAOjhlLMTUpF4BQiOF54OxQj2ijWulmEwkcBHylUqlmC/FeFzvqNBVtaO9NbJcGFdZeDU+3xTKdzfa0e275lbsjzX4XVPDhMYmnuO1z6gstXsm61rte+wk2SVnlX5tXk7wYV4ZpUk0MpN7eA7aN7bb+rBztgu6Tdq494anjyvX3nrTxIXjVal2ZwtvVt+ujg9dtjXCb9HuaYf/VSxqWxn98qFqa/qw5NZ9a5Y077bSecLaCPseWY06tDFsDxJv8iPLAjtcKk53P94mwqdI821k+7ykzZlX9bMUn8s9Q5d8s3CLsz12p6o01mvNLuufOSPHDBGlVSZOGv16Cl+Xu7mF7n5e2/NNhgUtQdodYVYEFlW1bu5VaDdixf7U50Fr3lS1iyrUPfzVwbc0NL20a/az68ePl3Yjnzw4d4icPvDswJATX/CfCnftNYzB99jbl6SX5Lhc7pcWsOfabCtse+yFBNHSVm0LuPZfTeL+PDzVe9r5KTYXOlT8OiV55OvEmvSTU3YVPhkRe2DzoUrsVWbC7eWPo/BBPb9/HHhl+qvqkMvWZ3aUP/qly5I3TNKoLkSnTPd00aJRZVs7PR6VFtVu86ZR4xtHCY8p37iFrt61HR+76Vr6F439w4JbPphX5tbreU7b5d/5z/9ikb5srCzkjMMU8trKn3L7okkjXzeFerK2Gv1kR89BQGkfE3U3/urfoO7/GDH+Diz7w8Drz4IlBk0m1IY4E59xZigdByPwB5CTB2JeD8gMBAShoEVDpIEdBYDpCJQUhQPgTRMQ1oJwrIdhgcMCc4bSA/ypqAXrTkgEjSv1arZjCgTrBrBf0eKAAm4JtHQK2AOwGoOCh0hZzyBgPArIGcEBmoZD+Gl14KkZJsvZrY6JPdCRMjj9Ps5j6dhP7yy0/jiQITWAA4he5/BHcJ5pUAvQswC9vxvQE0sEHxHogV/8vyfQyxXzXPj/PKQnkwg/OtJT4jjmIuLzpTJcoZTKBZiLBBXwRQqxGJXywMbir0R6IqVcofxTSK/xkHeQ3qHwQPKsu92WR/0i6a8PzCO105uL/WO8ZhxbnlfWIZ2XeLL84L15k5DqdHzmuJmFEW1llyeu6195//ChmU+SP9vj7nUk3s5JNnmFcd0Onf7atFNdxIFOCx48lpcZVsVV3UgN7bZRHE5/nd3b/li/22HLw740TopsJl27rwV9Im3vfdFZ29JTTuPCffrQMerlJ6vElx4tEJdGFElGfhvAHcQciXfrN5W/vt/FF9/23dn7prD0ZYzfUVnXLx9P68a3iTz4Y+Pzsivf9ptwq0XrdQ7hL6r6zixrMv2koPmmWQV7vomPcl08+mqozS+Pdo4OuPd19cuS/vcfXBn1JIN5cvPSnJddcjfk26uMK369GdzC7eSr+/P5NpkFTec+H7xPc3n6OV3HYUxWj8973Gsr3jPunDRg6NCtLe4oV1Uc0cYfiRfy5Lu7DH+4CQmaWvTt4aT9kkHnOt658v2Rrcy5qJqD088F4Npf8rm34kq77i1+NcxtwMNfEqinX3s1/uXL1hvtC8tvZjpkXLg1ctqub7KfLew58+LLO7bN11EqyVfkgc5JB877PDc+z/nmRd/o5B62m3Mcf3lwvG3B4d1nXl1p/XXjR92wiSdbnK0+9/21F1GOQzlOgr3XA6rCL3ZsFrp3z61N6Z97r7Lz3zi60b3MyQf3hxvuRj4efyfw+fjVruL200cuPYCXX5jYZ+GyF01Obplr3/TYT3iL5/25307f2OfTh/YHvpFNzgxueTHpkHy4brjPPJtWS+Vr92Q6jEHRxmomxnbbcHL90rLS1zYm0Hk1ecuGb2z+HejcUh9zmhAYizlNsIO1yTPP0h1Aa1wibmBhWrLawPVVSoKl4iQJHoEFRYkSsQAK1UuVCl6Mi7dMmOjHAsZaSOPAAk1EQeAMfAyiUi24A41iTi3EiVMA2KCC9KgiGdViAMOANnPEilOY4hWENRDP4KkffFxLbUJt9TBqbQOFpsTVYeR3WwmNGUmzDW8B0teFERCbeLDYZL0JdLwNRs4CJyH4VephAkveHwZL+aZmY05f576/kUtKasPCEHMOFIgkv5OwfiNFHm5UXJsjV4eTJBKIag2IORHRtYuaXBu28iCATn1vdj7v92b/jXX8kUTcINadSYSgF6AisOFId8ApigSacwgBmRHxCPFD9HAdCBvnAWSHAVrhhISocZTGAQ6Op8BqIGKnEB3swYJ/LQOr17ReBzOie51VA9z7MbdfzTb+FduvhgV7rV6trkeCAmQAVK1lPalBXfw3dloE3Ak4QKI4TZpSF5EQNkiQFCaVy9BBHolRg/1SBKH/4YYMpeL1Grj7gaTpI0zuPgJ8GWF2eBUqJ4BVsnsuOa4Cg5HUCIcMdp/yu8yGeml1ocwQF4If5JcSLA6PJ1NT0MjEoX8VszA6/WHmPAdpwxR+jIIMEEsDgrFhMl6aPmGoLOmvYg6DPk4lm45qTJ7+h3lNEAweJk9MUEnUjId0cCRDBqfG0KTO86/ildbhGNjUAoGycJUxmDiNffcgyjc8PMQb+nqf3iKBDPFUE3A/zT5xRd6LWuzmXE+pXZF/m696O9b3J+gfDfwk7o94w38bo7/pCf9tjP6mV/y3MfqbLmEpfVlKX5bSl6X09f9Z+srlyyQuH7f2Jfmb1r5ceHzhP/CUU8r76LUvF5FcppQpBDyeWMnjySQiTOailIpwqULmIpEI/8pTTrGLTOEi/FO1Lxv03VPOqFiyy17bjFOlSEmcfFTPfTNiC8asuWYT2aXH94sKIidv73/wAi7qf36GsF9cs+r7TeOfjfHVjGxtkAl7VHZva6iRWp/WlRusF/BGtbnWvvLS1vKaX/Tza7qOefPkWuXigVs7jVmYElWzdsG0K7supR5/bnumOqj7fTK2SccC36ye3H8tmZT1+sTnpaUj42quv9x9/bsE+7y8m6deOi7eNutR0cGiuTfmllbEjSj9vNkV9/UG6xpXa6vnOdW3u+fP2aq+uB1fUyOY0fHpWgMtbbJq6JRqfKJS3THsosgHmz7L8cRyUmB/VDW92SGd9awJs2b5rWg6ZLty8mX0su842XEpHjRA0Shv5RBho4Igxquyo+vsTx66j5r0hc2SKnpZq+dIW9FMF511o3bD/mWPhmZt6ul+09Eob5LlF5/fbHqbXlt9JnK/WIe0uNlquD5j8c/dQ88d363ZHFiZPWBi8Nq0V14zVU+P1/x6sOhNx/O5K4/Nudz9Yn6CdXjjTvw9NjbHuJXLQlsP7uwz1WZ45tXVp8IHLG3j80P/5z1bz+iZ9F2VlINaucw7ciH5yRJVQEs31Dnnpuhy09cuq+7ex0SNgrrPmdO6/YbhFTbzYuxD9jtdtl/Ue/6Sdk/io5cWW006JGwU1G6pvGWVu30IXxFa7c9Vp9x+ML+z/dg8m3D/hBVNB63LT/vO/t4A+aSs7zcYkS99ps1OPlHOZJ1P9+PqGk9t17swqlh9qsbxCWe/1cTMjq8585ul9p9BbU97XMwf2Vn1fNzwinlF17vYStfczP7Bz+ClnyS1HdMmfOebNpwDXq3cc071pKj5kZVBjRwNe8pjhnVJH2e9e0hLaeYjSUjGwaAZnTZ2Wzi1Q8GQ4rDOPda7E8rZotAY2YM1mf5JGxyu4A9CFh/yb7K6w7PFYXP8Zq/pg026O/9BY7dTe5I2zJh38l7/q/cEDuse9l/inNU3PbHFk754WcvbR4VO+qMdrotvdEj5Nfagd7s9N75RZw+WBmzfF5f47OzWZf0W7lY1UnYOuDou6/L4R1aTUKv91twsxfhmVeH9I1fs5qRlMsXPCoLahjSZaozZeKH1xk4PssMundd2TYqZQ/2y2nXhioIf+wR2WMXxf3KmWfLuCcj+zH6zcrETW+IPFPjeLc8oLsSu6Nf2sd059cCsFHHBt467vr040r44Ov2Ux73PDu/r8Ob0hs+u3Il9Xb1v4uwk5nrRi5UGxuHusQBx04qRrazv9frh8bzJMekzgqbEzuzS8/qOzivX/rjXpkOIdXEm7Z5lO2HOZe/Un7dnVnyFjfiqyNbOIT07edK9Zn6DXZ3Hja2Y992F/Es2he7dWkfa+uwvudlxrWhoobtseJufvMZHVc2t3jKuUtC1te2wFtZLThwvmpr7ss/oDbecXOMcjygqFxzI99mpWRQZPer2m2s9hiUHIIG2ow5NnDWm6dpbA3teO+s88rJ1fvahpF8fDM7c1Kd4xo/NIw/m4479nqMexBabi3k2zfdkXK2wrpjerNvL6uSmuQ9mzSp+mPJ4VMSqWWc/TVhtXbHvkw2dDY4D+i08Ottge23SvMp+s+4beokryv2/87nbUjBz55Prje4P4B8+tu1ewk+XvC8c9rhUFOrM9Ig+f+9+6fFbY6NXiO9+2uHog7GDqh4u6je0pJ31jsCfCwqkZTPRyiJl56DUxSdWHFjqVbUncdgD9+8+lxRHRNxq/K/ogYX+t1yPqo/MOVAWfGbfl+XnXyERF+ekVXRTO3oFVVT2CJuS2he52KR7h/QyV0Y1vt3J7xXbhOGpAVdr2scMPrissGj9Xj019eDgpXtbHHGpIddHuAf9VDZ+jqNkWks69JavY9MyPD/J7ed1O/bHB1TuGD768ZZ9GyZbf4bqfimeHHdu2a6QXTvyVg0obnb09MivOowfUNimUdbQAyV9o8fNmO29dtqBMo7X+nvRq7gvhi6oOnG609JBb3I25jp6BGydcmTZNNy1krqYtmvRyixbxrto+q0xT4oecZsIimbuCNhwzarDaeyx2+whma6h+sKZbde7RjkHSbC8EvuX8mz/qbefkd9E9Pd3W6HWrOu2NWXh6a/79xT5bY0cPocIK75UYVUdG/XyoSrg0pC+12bdLzsf9rBZafVLykDSrz9JWrt1c/WYdcr99tO+vl9aWn3n7IUJeYteC05zulO3pjmP6Hp15RStOj5rT/kPYcG30oaP7cMcv1DUb/SFLT+eH6mVxF+qqG5lKojHiY7M4tt+3Lcw2lpb3n3+W7z77Ae2qTiuoQHDKIP4IXB3p4dvxANdoFqEoGk9kIiKUIPJGLi1Z6AEgWzgboUA+12AIaEgTApSUqSGlWGD3bgT4kumAFKgFT8EQ2G5jhUhRYJ9A1wE2OXhFKpGyGRoDXgKQipN/CNylAaMgJHxVIJmp07UkilqXBGPO43QjtB++eWXb7UOPtQuPgR2Nrd/CXbjAIwr9CxaH6Fl21j7ozUoLCQDc5ETySRF6mlgeRrwEMwLQLA23rxU1npQDaE2IMGYimRILaFA2TfDCTArQSNgL89AniEhSQG7Q4ag8aSGpHQq1AkJVwESc38wNlmLummEQuVywmweKsARsI467uBCtWwVwmRLClYAYBFA5mCnApYMD4Q4CHBAeH0Bp9hyh9xgJmcXh9DgIbAxoFIFaAR9aFPRQw32xZBjFAHeR8iBduFZA+QVN7Czk0oGminsCPgFfOtgWYW1Y73c/I3C4001EIwiaRoJAsJXIR4awAmGsqQeNIE6vVWDb73a96Da2rdZHTqK0KAUlBChZWvkgGcs0YDQahBtTALSQLsEmwOoJvPMUAXAUuGKDaxhAbcCDKpwNRAHgmpIqD+gEzgWXW9xKiJeBZWhQHWMqa6DkWogHm0yQZFatpTLTglpKfAHoDVJAppFCjskQ+HwAAYa92+rDHAB3JSkCGDbtdV+k/gJ1nHgtMCYcLP31ckdxlOStXwlsHzwBGrPbELxQJs6mhVr3cyoyS4Ueoowr1mBGpA+CkJPaVG149vVvMthMomBjatp80nXWjQrThAtTILRaPRauCvGTSEKnjYhJCAxrQSQpKCUlvVYEDNQsNr6kmbt/d1J34oCVh7BwkY4qFCDBk0ErHNGODTQqd5kLvEoO2EyHo8zpkgD5qXBXLipTMfe4WGPBPQadjdWZ3Ve8BCjnqRqvd1sVXWGp8RNkQa4BHiAMwZ2TWA/zoCoAN0LrIVj9l0o5HgKpWmcNsWPWrfC0WToxV51egAcaWDkY9djXoeSJBXmnACXAKwd2AKQgQ6E2YZaYG0PMFVPsebFAuNmVLSpQsmOB4IMjYE4iJsjzlspg88YpcegEdbpgNaDSJsMHrEzU7QKwcDWGayTrhOdZ/0jFtMmeIQ23HRN5t2zl7qQzUqPRhQ4gAmsoEhT/q09BDGlSui/TkgYqXnbwAE8gWWjNEttDiOmPIb0McddFOoLRD0d7chhtQk5ATmEMnmxHPoPVALNwBYMp7SATA2wjD7eLFA27sNCMOsv0MBVqBaAFCiHepVrc0g2BSwQeSm9yaxgYR5KydwNgV5KQyBDxIOtP2SYAfNQOOgFxmYFwoFLogia1V/dDDQbsADjeow1dgRNBhEYzKgmTKZH68HkMDKb2ainmDCzKL3enidBtUCuYQQBzxAh7628axXDqUM4GjSBhDWhuvjORmqTOrSkadra/uZs9b5WOOYrU2pY4VC8qyjTc4JVFIwTgNRPTbxLRcBzDhpQsTHjLQX0MzarEpgejMKBNkyhb9UCjduM6kA2SmAvXSkbWiWuBLYOwrhCz2oZZlm2NAQYhYmUIuR60yUvMKACB7YPi05AnTowX/0AAp0ArJ9+BzooURojtCiLSGoxAyteECsBrjSlFlNUNcd2tTlzaEyIgqrLAAAEfiC3sQKpFyw57HEsp4FhgoirBGoHhk6bchr4zZgdHIe5JR4oC7i4BuIiiBbeqgKYrxoWV9nUA9xCiWIsO6jJsM2wWq1wahgEaqUKRQDQPUiUMKqABQEl6k1Buy6ymAARbcZZiByeiNRzEwx6CXQEVtSDSRjSwBCmtx5wU3zU67gMyVVAh6sHMzmIUk+xgfQt7mXzG1t7ZTMDNF0wDQiI8BiN/QhIST0FJcVmdRwmNjavshi59tCp4fUxy61By61By61By4na/+CJmlAqEn7cEzXp3/TaoJj/D3yZnO8i/c8P1HL4mg+eqKFyVKbEBHyBi4SPCcRSoUAslElRGSqXKQUiufgvPFGTKPgyofTP3Rvc+f69QXIBr33Gjahhz2RD7B6KL571PlGlzpi3OSFW2H3viG87YcOf4gmfXd8W7N5SdCt11cp0abeBT/mjWivaOFqvvtGjb9HOotu7Njy5U52/ObY4+1HHFc2MbmcXX3pylX7QqpftrjvZQ490uj2TNzvC99jNNh3ayNCcL5wLDiqMPx0ta9p3Zk6CKi9Pp9xJiBZNPvrV1JnCJDLXva1jekt77M7pkkCvRdda5XLm/xjY93H23OZETTvs8tSEXtu3zM3e2Gte6yW8+SGSFfriquYIFt5rY+jEpGddfF4t7V0S/jQlIu7S2MOk5Kluw9Yzr0e63bnXVfIy4edTftvOuoQMTTz6TLjzlSEN3WOvGrluRHzCzm43i492dW5ib33vwOZBgYGpUq3HtVaa7hd2fj4z59zOFhf5QTt8XrnluHbusO7GrfPHDWNufReVv7bizmvvaSG+3DcD6X0Z6V1/VahvXHi+tk3ItnLhqorwOQJBs6rvLwv8XQfYZ1eNq6i073g1YUjMm2mzrvuOWP/srFue05Kq60vsnjiewyaMfTIpItI3dtmaoPXTbFou6PlIsenbz8ul4y+3f21tKlmfnHHOesBHvjho6/7Xv7lqKbdayq2Wcqul3Gopt1rKrZZyq6Xcaim3WsqtlnLrh8ut/1/3DkxFqLhQ75AhH7xoYK6ZQdijZi8LALETrBzwVBxkUBClTBRwyUBQH7wOABRGKN4hBHRK9rJBChvWSLhTMGFcPQP7m/AjqtaD1UIy83gwRDMwqcGLCKxTjGIf9XFycnIc9afuGlhuEFjqnf+r9U6hTGCpd7L1TjHvn1fvFEhEH7/eKUElUlwo50mFmEDmggqVYoWcp+S7iJQiXC6W8f/CeqeUL1Cggj9X7zz+Xr0zcNpxnt2WZ1FTRwdFacLOXhhph7TxVcmQcLtBJ6qycz27TLueNOduydNtOT0u5Hfe+PzX7EsLJN0q21ntqlBvP22tPOfllvGs+vmLazOfxI1VR90bbpiVWrPlZ821bvfcRvO9C6a/IcteROixvCnZggB71YLmQn3Joyfy2JIDL+6s7/1F0s/9o7s8YhyP71avXlhxcciN69GpdutzldZuva2sKt+c8279Q9fDNsrd/Y5N73tS4TI32KrXlPt2EwtXdvR02e0b0yJr0OKXYU17E93tZKoen0gP7MvuPEq3xMiJUo/37LliR8nqxtV2w1pffj5+nOccj9M13pXBP7vnBc5p32P8rqnp4cc6nfa/HRtX8ciItDqCPd5yUjD5fNTOWWfSjjXelzl3Pvfl0dybHP5C94fjj80j+zSfndv9UMTI6VbGacvJUp/pyxM+K7eddPKVsJrKWjHXtdeZ21nbLn/W7OQup08KJ2nHzO85Pzsjy+r1rZRT1WvatJsp4t8v6+zIfF1EDXye9SBmcHJ09n5OxzezE7a8XpwguTOk1Gl76xayqpxddz5dc//4TT+huDzTM9CxV9c2yzLLyxNWl3a+endR1o9DqhubCqCSWzusOn7kAqhdd8s7u5Z3di1FZEsR2VJEthSRLUVkSxHZUkS2FJEtRWRLEfl/5p1dc4GV/ddc3uf8LQsN+E7BWa9XmJQPOGU7srQwDYO+tUwCcYBloxD/wj0DW52F06mJRNxUH9ap2b51K2TdhU0g9XT/dhAOwmIPgzl314mInZDNDCwz9eF87TbCrb6ITG+0Wt5otrzRbHmj2VLh/9+s8Iv4AtlHrfDzeX/TfyOIL5D8A/8nFD5P9vFL/EqhhCfAFHwBypPLMKELqlQq+XK+Ugk+yxU87K8s8YvlcqHsz5X4t9aV+Bu8zRxuN3/Vw/W+KzpoAk54327PbUddHrTwi3NlzqHn+tRkNA5dcnTPRPzCrhe/rg7uCmCXr72iWVA0nltjePBasykjVVoy59KGB7eGbRngdDLl7GSv26U8wj5t7E9TL0ZLskqmen0ao9cpje1cCtGvvs8xtLdZHLPkmOzA15FfPCTXudnmbxjDe7Tr5qCvVMalEye9eQr/I5SSQK/Z11r14Mz7Mbgv88Pc5n417bBR69S9ti9q98NGh3mtR+V/tjQhtCjmwCfuMQdF10Lb7LmQ8sXdb7I3zvqlU4ejcs3VvFfrNk3Lf6V5oRi7OXDgw9u5cYGdli+POMm+zEzhe+z7jFw3oihxZ7ebUyPblDaxt9Ye3DzIOzBVetf9Squ7yJkdbxK2nB19ZkfHgSEVF7c9HTs4YxgnYe/1vOUHX5+XOOuJlRfLixyW2vlv9Ewwlj91v+y7oKL8AdFlSbWOGLWypHVe7uKh4gkPOHcej0oNs3bdNip3ykG+Y3U3x8PGjSrn9WMDYt2XHvDofjNs/d4mz241L1prV+iVuFIVZ9u+1/fKoV+1vfGK1/j+nTfm/wfF8/ocfKC1ldX/AXrJPDc= \ No newline at end of file +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 \ No newline at end of file diff --git a/docs/cassettes/agent_supervisor_56ba78e9-d9c1-457c-a073-d606d5d3e013.msgpack.zlib b/docs/cassettes/agent_supervisor_56ba78e9-d9c1-457c-a073-d606d5d3e013.msgpack.zlib index 59d984686..56d98e321 100644 --- a/docs/cassettes/agent_supervisor_56ba78e9-d9c1-457c-a073-d606d5d3e013.msgpack.zlib +++ b/docs/cassettes/agent_supervisor_56ba78e9-d9c1-457c-a073-d606d5d3e013.msgpack.zlib @@ -1 +1 @@ -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9BKZklBqZfI/c68gJeRqQvrf3nu4d0bumzo9Y33sP0+UGp1dfQeGzxosdk1JCeGHzn8usabzjqaM77uFYCn93LRB62RL//n6jl4zO2k7kJ8OqynmVf0zaF8uc/LNr186sSCxy5envs/tdmdP/61ruiSuNTxfsGH3gozkxpj0518dWHv+69qxEccsP39Z7502/eStd12Gv7WcIqcPUB203XxA0/36zizvMSxlZsO6Hb2IyssUWt2pS1cZ81NK1ecpGcjObyOL/WtAqhNds3bnc/Va59gat/gLKYqY/e0Opgwetoh99vK2QxdHnt1ZHlbktfuTReMKE+82HL+N7TgePuB6JFe39ecv+FeujC0nRsomrdrdU1s85Zv43TXmgLwZRw3gp1In4qMLpVVR0TkeEalRecvQnam/TLr683d3C57OygvQ+DRtalp6Npo4mSSeseyVm/lhGee3Hy7I8iqaljYU31s2lt7cccogY8GlhMrYl7/b9/lHhzdJF8yb7H9E2WkLvl1y6fC01R2K5RM0Q2ruBoVKB4o31c/ZfLiPe/Vi77wVey+/OX7uwrWh7PKnVoWjm2qmnBj4tTRtze0j+7vUa3xectx7YIxXT42EcfsX4s5VGQ== \ No newline at end of file +eNrtWXtUE1caF6wuba0Lsp621uqYVUAlIYEEEuoKGsNDBBGiEAXCMLlhBiYzYeaGECjbVotttS4NWvf46G5FBEvVAmXFHhV84LoVsR6qtfjco+2p1gOeta1b6oO9MwnykD48q3s8Wzj8MZn73e/93d9vZpZW5QOOp1jGYxvFQMDhBEQ/+DVLqziQZwM8fLXSAiDJmioS5yfrN9s4qn0KCaGVDw8Kwq2UDGcgybFWipARrCUoXxFkATyPZwO+Ios1OU57dhZJLHiBEbK5gOEl4ZhCHqwMxCQ9UujOkiIJx9IAXUlsPOAkaJVgkSsMFG6lkDj05zFIAozPs+EcwDiWhRhrxpTBEZLidEEXawK0IEvQuM0EpCFSlZRnGQZAKY1DFIKgknfwEFgEKQNrwwQ9OMbbrIDLp3iWwyDO5wITZqcgiVlwBs+mmGwkgRwR0oMLOcGyALQDwIi+mFmaZu2CkJ3lcpEIisOfAzzAOYIEnH8g5k8gtzj/dBkWTeXfs0uIFHNnOBBd8FaWcZsXBF1KMchiqB4YAwqgDNPhBNmzYKdoGkPOm1nOgtwUvMdwpGCgIkowwttoyIvLPMShjZdhKSRyyEwxFE8C0wDzUbEJsckxMiFnkGVpI0GyFCFUp0gCHVaxTMJ9YZ3BLeLvJNaGOkdS7N7iLuqAVbRoAjzBUVYhmWJp7wbJCSLChSvSWDPGsD2JRfeASfSyR6iPhxRjtUEjj1Juwfu5yGblAEIsvBV1J+AgJfYa8gpZEC8AYxPaYYmkt2qu1jO5LlxWJEKD9SjlIYdqJykWAhVqR3HAJKoQlaYXF6cXV5EARwr488O8K0iWh87ae4bkA5wggBVKAYNsIX3O7dmFlDUQMwGz0K7VhNC64hQ6q3MBsEpxGjVQpWuXswa3WmmKEDsyKAe1+Tb3sEgFL+9drhY6TYpGjYHOhlk9fgQlOtBMM5hcFqKRyWsKpKgzKIZGQ4lGBrlUaRXXd/ddsOJELtIjdZ8XzkrX5h19ZVjeuSUeJ+Yn91MpZNe5BecsocoP+97nbAykLMBZpU2815x7sddciEyhkGlq+ynmHQzh3GLGaR7U3k3y3S3VwfLgEKk8VCpXNPRTDSDnkBIssuDcJN/Rk0AaMNmQdG4OU8m3ukaCB8sqXTOztEJojKP/qHIfXOXz43pL7VMxBxXOuVdvA4GYQoMlsPkYMq3E5MHhcnW4UoNFx+u3ad1m9IPWqVbP4QxvRrXS9fRFFUHaGHQqVWsH7Yh2SW/EHLJPUxYKSt1nCqqj8NNZoZTL5e1+PynJoeFBZwGyWBGi0Wh+Rq8wLtBZL8QnVSikCo3eHaV6cTs22E7X0e/2p1LwB3k0+Scke/3pkcZ+UvpH/NEsrnY7LaVMzj3o2ihXqFLz9XmopPEkm6QPLShYnBAValBuzqdwZ7VCpsCyWTabBh9oo6RadNwCabJYfWfVHEPCrPhY7bZUaRKbxaI06HGULoZlQGUywhDAOasJmrWZ0AhzoBJtT5plcNargZLAw9RECKFSESEASHUpSTU9fXC3zhXC/Itw+Uql64hp7pq40muY+DdcX3pubnPkqJILU04lk7vT8a8+Az7fzn6i7vWYdR3N575WUhvejFjTuHue7njLxi9e1GQvP7asJONYfEbaxL8vykhPb7l8++btmojG8QmlmZFNI+elBFw85jXSb3SsVfLO1t+PGP6HolGZm56SFR4NbQ/19ZvdemTPthV7AvdHtlTEPHGDulMyjSfLJ389bkpr7ccrLwH7Jfbqa9fnbE2vLZ1oL2xf6sEVXz85fmzVF+WTpn3+rFeEakRmqW9gy4ceCyasf/wd+e3uetljh18/IUldMemZ13M1L3R7P/dl2GSPJekdo6wnmk5XLl7Lohi7u4cPG7t8n/kFj2HDHhTv8H1EeMcggOg6W40csNLGHigdAI0LeYR0JMULcAcKACEgn2sbJpxDIpibWMRUICnCpQNxGjtKiSDPAyC6jeASgaPgMY7l4zQ6m9IYzP0nyPMka6NNmBU1HMQoKMi7GECmeCtAJpNNzZRhesEN9I94EpVFiwAsaBdSNjgEDwBcwV/xYkCIerJ/SH0iRZcIr4Aw6oKnHEaQOAddlOTnoFg01xe03UxgCJz/P8BZFaJ4oOCsUvwqwFnzaIGzSjEYOKfk6hLy8uKj4/Tq3LB4OzCzfJ6Be7jgrAmRgzDz/YDzIY/YPuicaJh/INL7zvQNDp+y2e9GNjdKMiYtjyy/6DRbohe3d9L2bN/aKy+FLSFOVH3USJjHBfAx73I3oXKmrqvwVtpnDfbvcvjGgvr3Nxz80yTPmeNGBcCETh9JTcVsbXhK+NrwzGlHFszyXFc28/B+k/w5dds4h3fpxSsFvzvsPStq/YKQyfNbSwMuRWf7zPY85T/3Dc+3faKvS1ZP+2Zu8BOyxF2n41YeGFP7bphiVYl9y/mV3zmfbNOSm40LJG8XPr2oo/PJv82PvtRV9sn5IPvKDnPWB52ndhV9XvbRy1712zw/gUnv6Ta37Xv8RcsMbVtklOPlNW9PaikvS5uRS8S17n3rt+mWj8JW3Swmz/zZa/zqWOnc6B+6Z57bh3csyPZ648Zc3T93N/m1Niw5AiM7sHUJZp+3vmra6zi70+hG+x3WRZ/ufHBo/9iYh4v2gVjvbpznKXTsMLC/CqQfuh4tJbH+6OGcBLRVhFkCpwmbgB2Da0dAKrwPcJ2x/SBOVCea7vvcbUTe9332HoxMiJDcF3wllMXKclDkC2mMC95F7sDncTBAGTx1qvjwTpl6rNjQNKp1UJMTbdAmJ4VwMSmJwJCazFqjEPXpl457k7lkgMOuFxADEp5sQ4jJ82YbTTt6cN8UnsZkZma6IkpjftZnUTqNSYYmRF3CsVCZUi0PU8pDNWqlPEwdmsbcfYeBnDT+ouCEJPBGwHEsh6RF6ELxDpG9IbI3RPb+92SvQiFXBj9Ythf8a2B7iF09WmwveDC2p9QCIsHMk3Z7qsq+KJTMUsWQMO4hsz0zrjEF39ermJu9ZG9eaWvOQbn3q0bdUcPouovqNxjpq7OLrmz6ImmFzw2/vNy4nfYRscsu7J1cf/zTfWER8pT6p8YXzlzkdyHs9vd1Zyd8efPbGZ0TWs599enVOx7juGcvr5WYX5pMzqvd7fHJ2brvseOXCe3z478+VrW8Duw8ol4eWWYgVp5NfXJV+MhNmPXaqaYFm77Z/senX2vwnTph9V9j4hUrVvxmYerJuusH5Yf4M0WbOsuP6GoVVw+sOROPf7aBe6VLu2zK6JNz/kK8PLykskw54ta4eUR5om9aTHqVs9N4dqzlmTL9mKYA+dnqo6qssu6jpx13PFz0zHRC9cO0B0fPhmf+d/SsL6MQSFEgdpdr/QLi9mNb9YPSMQR+6Dzg2ALKIrS34x5aIZPcDyMY+uw09Nlp6LPTENn5BW+21KHKB8t1Qn4VXCf4EeM6IYNxHa0qK8ag5rWpUaZFdkM+BfMK8ATLw+U6eKg8C03v/XCd230/OxnjDkZ6Nze1zDBi3fSX539IrPVeWr/Q2+ldtmWt325VQ8r2SRfSZZLLl4513jLnN9eW6K6NXrhxTXv0v6xzm9Zf5Fo3HDqT07VL1iA/fnXdgbGalIleI5fBWVvfeSF8uwE/Hhf3fOD6HTekOgP01eB+q9rUravV8fhxr+m1BxrfbEoce2XaNU/bjjFm46GPM9SGINkpvGtjp+nmnOnns0eM2tW5jJ2eQXnJZhaVPL//2/dqdp1Zs+HcHyqvnNz33sb3X8nxvxWRk6Eu/HfzU/vrroUPj3gp+/BE0xZOkl/T1dEWuefOyboX1ze62U4b87hyBrr+D6cPbus= \ No newline at end of file diff --git a/docs/cassettes/hierarchical_agent_teams_6b8badbf-d728-44bd-a2a7-5b4e587c92fe.msgpack.zlib b/docs/cassettes/hierarchical_agent_teams_6b8badbf-d728-44bd-a2a7-5b4e587c92fe.msgpack.zlib new file mode 100644 index 000000000..e2db05d79 --- /dev/null +++ b/docs/cassettes/hierarchical_agent_teams_6b8badbf-d728-44bd-a2a7-5b4e587c92fe.msgpack.zlib @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/cassettes/hierarchical_agent_teams_912b0604-a178-4246-a36f-2dedae606680.msgpack.zlib b/docs/cassettes/hierarchical_agent_teams_912b0604-a178-4246-a36f-2dedae606680.msgpack.zlib new file mode 100644 index 000000000..d9d1c4c89 --- /dev/null +++ b/docs/cassettes/hierarchical_agent_teams_912b0604-a178-4246-a36f-2dedae606680.msgpack.zlib @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/cassettes/hierarchical_agent_teams_9860fd46-c24d-40a5-a6ba-e8fddcd43369.msgpack.zlib b/docs/cassettes/hierarchical_agent_teams_9860fd46-c24d-40a5-a6ba-e8fddcd43369.msgpack.zlib new file mode 100644 index 000000000..85c0a365b --- /dev/null +++ b/docs/cassettes/hierarchical_agent_teams_9860fd46-c24d-40a5-a6ba-e8fddcd43369.msgpack.zlib @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/cassettes/multi-agent-collaboration_176a99b0-b457-45cf-8901-90facaa852da.msgpack.zlib b/docs/cassettes/multi-agent-collaboration_176a99b0-b457-45cf-8901-90facaa852da.msgpack.zlib deleted file mode 100644 index d65abce41..000000000 --- a/docs/cassettes/multi-agent-collaboration_176a99b0-b457-45cf-8901-90facaa852da.msgpack.zlib +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/docs/cassettes/multi-agent-collaboration_97f8e0eb.msgpack.zlib b/docs/cassettes/multi-agent-collaboration_97f8e0eb.msgpack.zlib deleted file mode 100644 index a0a798f20..000000000 --- a/docs/cassettes/multi-agent-collaboration_97f8e0eb.msgpack.zlib +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/docs/cassettes/multi-agent-collaboration_9f478b05-3f09-447f-a9f4-1b2eae73f5ef.msgpack.zlib b/docs/cassettes/multi-agent-collaboration_9f478b05-3f09-447f-a9f4-1b2eae73f5ef.msgpack.zlib new file mode 100644 index 000000000..00a3706a9 --- /dev/null +++ b/docs/cassettes/multi-agent-collaboration_9f478b05-3f09-447f-a9f4-1b2eae73f5ef.msgpack.zlib @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/docs/cloud/concepts/api.md b/docs/docs/cloud/concepts/api.md deleted file mode 100644 index f8e948285..000000000 --- a/docs/docs/cloud/concepts/api.md +++ /dev/null @@ -1,216 +0,0 @@ -# API Concepts - -This page describes the high-level concepts of the LangGraph Cloud API. The conceptual guide of LangGraph (Python library) is [here](../../concepts/high_level.md). - -## Data Models - -The LangGraph Cloud API consists of a few core data models: [Assistants](#assistants), [Threads](#threads), [Runs](#runs), and [Cron Jobs](#cron-jobs). - -### Assistants - -When building agents, it is fairly common to make rapid changes that *do not* alter the graph logic. For example, simply changing prompts or the LLM selection can have significant impacts on the behavior of the agents. Assistants offer an easy way to make and save these types of changes to agent configuration. This can have at least two use-cases: - -* Assistants give developers a quick and easy way to modify and version graph version for experimentation. -* Assistants can be modified via LangGraph Studio, offering a no-code way to configure agents (e.g., for business users). - -#### Configuring Assistants - -In practice, an assistant is just an *instance* of a graph with a specific configuration. Because of this, multiple assistants can reference the same graph but can contain different configurations, such as prompts, models, and other graph configuration options. The LangGraph Cloud API provides several endpoints for creating and managing assistants. See the [API reference](../reference/api/api_ref.html#tag/assistantscreate) and [this how-to](../how-tos/configuration_cloud.md) for more details on how to create assistants. - -#### Versioning Assistants - -![assistant versions](./assistant_version.png) - -Once you've created an assistant, you can save and version it to track changes to the configuration over time. You can think about this at three levels: - -1) The graph lays out the general agent application logic -2) The agent configuration options represent parameters that can be changed -3) Assistant versions save and track specific settings of the agent configuration options - -For example, if you have an agent that helps for planning trips, you can create a new assistant *for each user* that passes specific user preferences (e.g., desired airline and car service). As each user interacts with their own assistant, assistant versions can be saved that track the specific desires of the user. Read [this how-to](../how-tos/assistant_versioning.md) to learn how you can use assistant versioning through both the [Studio](../how-tos/index.md/#langgraph-studio) and the SDK. - -### Threads - -A thread contains the accumulated state of a group of runs. If a run is executed on a thread, then the [state][state] of the underlying graph of the assistant will be persisted to the thread. A thread's current and historical state can be retrieved. To persist state, a thread must be created prior to executing a run. - -The state of a thread at a particular point in time is called a checkpoint. - -For more on threads and checkpoints, see this section of the [LangGraph conceptual guide](../../concepts/low_level.md#persistence). - -The LangGraph Cloud API provides several endpoints for creating and managing threads and thread state. See the [API reference](../reference/api/api_ref.html#tag/threadscreate) for more details. - -### Runs - -A run is an invocation of an assistant. Each run may have its own input, configuration, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a thread. - -The LangGraph Cloud API provides several endpoints for creating and managing runs. See the [API reference](../reference/api/api_ref.html#tag/runscreate) for more details. - -### Cron Jobs - -It's often useful to run graphs on some schedule. LangGraph Cloud supports cron jobs, which run on a user defined schedule. The user specifies a schedule, an assistant, and some input. After than, on the specified schedule LangGraph cloud will: - -- Create a new thread with the specified assistant -- Send the specified input to that thread - -Note that this sends the same input to the thread every time. See the [how-to guide](../how-tos/cron_jobs.md) for creating cron jobs. - -The LangGraph Cloud API provides several endpoints for creating and managing cron jobs. See the [API reference](../reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/crons) for more details. - -## Features - -The LangGraph Cloud API offers several features to support complex agent architectures. - -### Streaming - -Streaming is critical for making LLM applications feel responsive to end users. When creating a streaming run, the streaming mode determines what data is streamed back to the API client. The LangGraph Cloud API supports five streaming modes. - -- `values`: Stream the full state of the graph after each [super-step](https://langchain-ai.github.io/langgraph/concepts/low_level/#graphs) is executed. See the [how-to guide](../how-tos/stream_values.md) for streaming values. -- `messages`: Stream complete messages (at the end of node execution) as well as tokens for any messages generated inside a node. This mode is primarily meant for powering chat applications. This is only an option if your graph contains a `messages` key. See the [how-to guide](../how-tos/stream_messages.md) for streaming messages. -- `updates`: Streams updates to the state of the graph after each node is executed. See the [how-to guide](../how-tos/stream_updates.md) for streaming updates. -- `events`: Stream all events (including the state of the graph) that occur during graph execution. See the [how-to guide](../how-tos/stream_events.md) for streaming events. This can be used to do token-by-token streaming for LLMs. -- `debug`: Stream debug events throughout graph execution. See the [how-to guide](../how-tos/stream_debug.md) for streaming debug events. - -You can also specify multiple streaming modes at the same time. See the [how-to guide](../how-tos/stream_multiple.md) for configuring multiple streaming modes at the same time. - -See the [API reference](../reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/stream) for how to create streaming runs. - -Streaming modes `values`, `updates`, and `debug` are very similar to modes available in the LangGraph library - for a deeper conceptual explanation of those, you can see the LangGraph library documentation [here](../../concepts/low_level.md#streaming). - -Streaming mode `events` is the same as using `.astream_events` in the LangGraph library - for a deeper conceptual explanation of this, you can see the LangGraph library documentation [here](../../concepts/low_level.md#streaming). - -#### `mode="messages"` -Streaming mode `messages` is a new streaming mode, currently only available in the API. What does this mode enable? - -This mode is focused on streaming back messages. It currently assumes that you have a `messages` key in your graph that is a list of messages. Assuming we have a simple react agent deployed, what does this stream look like? - -All events emitted have two attributes: - -- `event`: This is the name of the event -- `data`: This is data associated with the event - -Let's run it on a question that should trigger a tool call: - -```python -thread = await client.threads.create() -input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]} - -events = [] -async for event in client.runs.stream( - thread["thread_id"], - assistant_id="agent", # This may need to change depending on the graph you deployed - input=input, - stream_mode="messages", -): - print(event.event) -``` -```shell -metadata -messages/complete -messages/metadata -messages/partial -... -messages/partial -messages/complete -messages/complete -messages/metadata -messages/partial -... -messages/partial -messages/complete -end -``` - -We first get some `metadata` - this is metadata about the run. - -```python -StreamPart(event='metadata', data={'run_id': '1ef657cf-ae55-6f65-97d4-f4ed1dbdabc6'}) -``` - -We then get a `messages/complete` event - this a fully formed message getting emitted. In this case, -this was the just the input message we sent in. - -```python -StreamPart(event='messages/complete', data=[{'content': 'hi!', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '833c09a3-bb19-46c9-81d9-1e5954ec5f92', 'example': False}]) -``` - -We then get a `messages/metadata` - this is just letting us know that a new message is starting. - -```python -StreamPart(event='messages/metadata', data={'run-985c0f14-9f43-40d4-a505-4637fc58e333': {'metadata': {'created_by': 'system', 'run_id': '1ef657de-7594-66df-8eb2-31518e4a1ee2', 'graph_id': 'agent', 'thread_id': 'c178eab5-e293-423c-8e7d-1d113ffe7cd9', 'model_name': 'openai', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.0}}}) -``` - -We then get a BUNCH of `messages/partial` events - these are the individual tokens from the LLM! In the case below, we can see the START of a tool call. - -```python -StreamPart(event='messages/partial', data=[{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs', 'function': {'arguments': '', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-985c0f14-9f43-40d4-a505-4637fc58e333', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [{'name': 'tavily_search_results_json', 'args': '', 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs', 'error': None}], 'usage_metadata': None}]) -``` - -After that, we get a `messages/complete` event - this is the AIMessage finishing. It's now a complete tool call: - -```python -StreamPart(event='messages/complete', data=[{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs', 'function': {'arguments': '{"query":"current weather in San Francisco"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-985c0f14-9f43-40d4-a505-4637fc58e333', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'current weather in San Francisco'}, 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs'}], 'invalid_tool_calls': [], 'usage_metadata': None}]) -``` - -After that, we get ANOTHER `messages/complete` event. This is a tool message - our agent has called a tool, gotten a response, and now inserting it into the state in the form of a tool message. - -```python -StreamPart(event='messages/complete', data=[{'content': '[{"url": "https://www.weatherapi.com/", "content": "{\'location\': {\'name\': \'San Francisco\', \'region\': \'California\', \'country\': \'United States of America\', \'lat\': 37.78, \'lon\': -122.42, \'tz_id\': \'America/Los_Angeles\', \'localtime_epoch\': 1724877689, \'localtime\': \'2024-08-28 13:41\'}, \'current\': {\'last_updated_epoch\': 1724877000, \'last_updated\': \'2024-08-28 13:30\', \'temp_c\': 23.3, \'temp_f\': 73.9, \'is_day\': 1, \'condition\': {\'text\': \'Partly cloudy\', \'icon\': \'//cdn.weatherapi.com/weather/64x64/day/116.png\', \'code\': 1003}, \'wind_mph\': 15.0, \'wind_kph\': 24.1, \'wind_degree\': 310, \'wind_dir\': \'NW\', \'pressure_mb\': 1014.0, \'pressure_in\': 29.93, \'precip_mm\': 0.0, \'precip_in\': 0.0, \'humidity\': 57, \'cloud\': 25, \'feelslike_c\': 25.0, \'feelslike_f\': 77.1, \'windchill_c\': 20.9, \'windchill_f\': 69.6, \'heatindex_c\': 23.3, \'heatindex_f\': 74.0, \'dewpoint_c\': 12.9, \'dewpoint_f\': 55.2, \'vis_km\': 16.0, \'vis_miles\': 9.0, \'uv\': 6.0, \'gust_mph\': 19.5, \'gust_kph\': 31.3}}"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': '0112eba5-7660-4375-9f24-c7a1d6777b97', 'tool_call_id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs'}]) -``` - -After that, we see the agent doing another LLM call and streaming back a response. We then get an `end` event: - -```python -StreamPart(event='end', data=None) -``` - -And that's it! This is more focused streaming mode specifically focused on streaming back messages. See this [how-to guide](../how-tos/stream_messages.md) for more information. - - -### Human-in-the-Loop - -There are many occasions where the graph cannot run completely autonomously. For instance, the user might need to input some additional arguments to a function call, or select the next edge for the graph to continue on. In these instances, we need to insert some human in the loop interaction, which you can learn about in the [human in the loop how-tos](../how-tos/index.md#human-in-the-loop). - -### Double Texting - -Many times users might interact with your graph in unintended ways. For instance, a user may send one message and before the graph has finished running send a second message. To solve this issue of "double-texting" (i.e. prompting the graph a second time before the first run has finished), LangGraph has provided four different solutions, all of which are covered in the [Double Texting how-tos](../how-tos/index.md#double-texting). These options are: - -- `reject`: This is the simplest option, this just rejects any follow up runs and does not allow double texting. See the [how-to guide](../how-tos/reject_concurrent.md) for configuring the reject double text option. -- `enqueue`: This is a relatively simple option which continues the first run until it completes the whole run, then sends the new input as a separate run. See the [how-to guide](../how-tos/enqueue_concurrent.md) for configuring the enqueue double text option. -- `interrupt`: This option interrupts the current execution but saves all the work done up until that point. It then inserts the user input and continues from there. If you enable this option, your graph should be able to handle weird edge cases that may arise. See the [how-to guide](../how-tos/interrupt_concurrent.md) for configuring the interrupt double text option. -- `rollback`: This option rolls back all work done up until that point. It then sends the user input in, basically as if it just followed the original run input. See the [how-to guide](../how-tos/rollback_concurrent.md) for configuring the rollback double text option. - -### Stateless Runs - -All runs use the built-in checkpointer to store checkpoints for runs. However, it can often be useful to just kick off a run without worrying about explicitly creating a thread and without wanting to keep those checkpointers around. Stateless runs allow you to do this by exposing an endpoint that: - -- Takes in user input -- Under the hood, creates a thread -- Runs the agent but skips all checkpointing steps -- Cleans up the thread afterwards - -Stateless runs are still retried as regular retries are per node, while everything still in memory, so doesn't use checkpoints. - -The only difference is in stateless background runs, if the task worker dies halfway (not because the run itself failed, for some external reason) then the whole run will be retried like any background run, but - -- whereas a stateful background run would retry from the last successful checkpoint -- a stateless background run would retry from the beginning - -See the [how-to guide](../how-tos/stateless_runs.md) for creating stateless runs. - -### Webhooks - -For all types of runs, langgraph cloud supports completion webhooks. When you create the run you can pass a webhook URL to be called when the completes (successfully or not). This is especially useful for background runs and cron jobs, as the webhook can give you an indication the run has completed and you can perform further actions for your appilcation. - -See this [how-to guide](../how-tos/webhooks.md) to learn about how to use webhooks with LangGraph Cloud. - -## Deployment - -The LangGraph Cloud offers several features to support secure and robost deployments. - -### Authentication - -LangGraph applications deployed to LangGraph Cloud are automatically configured with LangSmith authentication. In order to call the API, a valid LangSmith API key is required. - -### Local Testing - -Before deploying your app in production to LangGraph Cloud, you may wish to test out your graph locally in order to ensure that everything is running as expected. Luckily, LangGraph makes this easy for you through use of the LangGraph CLI. Read more in this [how-to guide](../deployment/test_locally.md) or look at the [CLI reference](../reference/cli.md) to learn more. diff --git a/docs/docs/cloud/concepts/assistant_version.png b/docs/docs/cloud/concepts/assistant_version.png deleted file mode 100644 index 3406673fc..000000000 Binary files a/docs/docs/cloud/concepts/assistant_version.png and /dev/null differ diff --git a/docs/docs/cloud/concepts/cloud.md b/docs/docs/cloud/concepts/cloud.md deleted file mode 100644 index a6ddd3f92..000000000 --- a/docs/docs/cloud/concepts/cloud.md +++ /dev/null @@ -1,28 +0,0 @@ -# Cloud Concepts - -This page describes the high-level concepts of the LangGraph Cloud deployment. - -## Deployment - -A deployment is an instance of a LangGraph API. A single deployment can have many [revisions](#revision). When a deployment is created, all of the necessary infrastructure (e.g. database, containers, secrets store) are automatically provisioned. See the [architecture diagram](#architecture) below for more details. - -See the [how-to guide](../deployment/cloud.md#create-new-deployment) for creating a new deployment. - -## Revision - -A revision is an iteration of a [deployment](#deployment). When a new deployment is created, an initial revision is automatically created. To deploy new code changes or update environment variable configurations for a deployment, a new revision must be created. When a revision is created, a new container image is built automatically. - -See the [how-to guide](../deployment/cloud.md#create-new-revision) for creating a new revision. - -## Asynchronous Deployment - -Infrastructure for [deployments](#deployment) and [revisions](#revision) are provisioned and deployed asynchronously. They are not deployed immediately after submission. Currently, deployment can take up to several minutes. - -## Architecture - -!!! warning "Subject to Change" - The LangGraph Cloud deployment architecture may change in the future. - -A high-level diagram of a LangGraph Cloud deployment. - -![diagram](langgraph_cloud_architecture.png) diff --git a/docs/docs/cloud/deployment/cloud.md b/docs/docs/cloud/deployment/cloud.md index 49720b749..6c67f1da9 100644 --- a/docs/docs/cloud/deployment/cloud.md +++ b/docs/docs/cloud/deployment/cloud.md @@ -11,7 +11,7 @@ LangGraph Cloud is available within LangSmith UI... -1. In the left-hand navigation panel, select `Deployments`. The `Deployments` view contains a list of existing LangGraph Cloud deployments. +1. In the left-hand navigation panel, select `LangGraph Cloud`. The `LangGraph Cloud` view contains a list of existing LangGraph Cloud deployments. 1. In the top-right corner, select `+ New Deployment` to create a new deployment. 1. In the `Create New Deployment` panel, fill out the required fields. 1. `Deployment details` @@ -38,7 +38,7 @@ When [creating a new deployment](#create-new-deployment), a new revision is crea Starting from the LangSmith UI... -1. In the left-hand navigation panel, select `Deployments`. The `Deployments` view contains a list of existing LangGraph Cloud deployments. +1. In the left-hand navigation panel, select `LangGraph Cloud`. The `LangGraph Cloud` view contains a list of existing LangGraph Cloud deployments. 1. Select an existing deployment to create a new revision for. 1. In the `Deployment` view, in the top-right corner, select `+ New Revision`. 1. In the `New Revision` modal, fill out the required fields. @@ -56,7 +56,7 @@ Starting from the LangSmi Build and deployment logs are available for each revision. -Starting from the `Deployment` view... +Starting from the `LangGraph Cloud` view... 1. Select the desired revision from the `Revisions` table. A panel slides open from the right-hand side and the `Build` tab is selected by default, which displays build logs for the revision. 1. In the panel, select the `Deploy` tab to view deployment logs for the revision. @@ -69,7 +69,7 @@ Interrupting a revision will stop deployment of the revision. !!! warning "Undefined Behavior" Interrupted revisions have undefined behavior. This is only useful if you need to deploy a new revision and you already have a revision "stuck" in progress. In the future, this feature may be removed. -Starting from the `Deployment` view... +Starting from the `LangGraph Cloud` view... 1. Select the menu icon (three dots) on the right-hand side of the row for the desired revision from the `Revisions` table. 1. Select `Interrupt` from the menu. @@ -79,13 +79,13 @@ Starting from the `Deployment` view... Starting from the LangSmith UI... -1. In the left-hand navigation panel, select `Deployments`. The `Deployments` view contains a list of existing LangGraph Cloud deployments. +1. In the left-hand navigation panel, select `LangGraph Cloud`. The `LangGraph Cloud` view contains a list of existing LangGraph Cloud deployments. 1. Select the menu icon (three dots) on the right-hand side of the row for the desired deployment and select `Delete`. 1. A `Confirmation` modal will appear. Select `Delete`. ## Deployment Settings -Starting from the `Deployment` view... +Starting from the `LangGraph Cloud` view... 1. In the top-right corner, select the gear icon (`Deployment Settings`). 1. Update the `Git Branch` to the desired branch. diff --git a/docs/docs/cloud/deployment/img/api_page.png b/docs/docs/cloud/deployment/img/api_page.png deleted file mode 100644 index 589f565d2..000000000 Binary files a/docs/docs/cloud/deployment/img/api_page.png and /dev/null differ diff --git a/docs/docs/cloud/deployment/img/cloud_deployment.png b/docs/docs/cloud/deployment/img/cloud_deployment.png new file mode 100644 index 000000000..677297238 Binary files /dev/null and b/docs/docs/cloud/deployment/img/cloud_deployment.png differ diff --git a/docs/docs/cloud/deployment/img/deploy_filled_out.png b/docs/docs/cloud/deployment/img/deploy_filled_out.png deleted file mode 100644 index 4c42ee134..000000000 Binary files a/docs/docs/cloud/deployment/img/deploy_filled_out.png and /dev/null differ diff --git a/docs/docs/cloud/deployment/img/deployed_page.png b/docs/docs/cloud/deployment/img/deployed_page.png index ccae96a1d..e33c6492f 100644 Binary files a/docs/docs/cloud/deployment/img/deployed_page.png and b/docs/docs/cloud/deployment/img/deployed_page.png differ diff --git a/docs/docs/cloud/deployment/img/deployment_page.png b/docs/docs/cloud/deployment/img/deployment_page.png index 606436ac8..165d3c34a 100644 Binary files a/docs/docs/cloud/deployment/img/deployment_page.png and b/docs/docs/cloud/deployment/img/deployment_page.png differ diff --git a/docs/docs/cloud/deployment/img/graph_run.png b/docs/docs/cloud/deployment/img/graph_run.png index 372ab22b1..03d3f9ac2 100644 Binary files a/docs/docs/cloud/deployment/img/graph_run.png and b/docs/docs/cloud/deployment/img/graph_run.png differ diff --git a/docs/docs/cloud/deployment/img/graph_visualization.png b/docs/docs/cloud/deployment/img/graph_visualization.png deleted file mode 100644 index 53d99b74d..000000000 Binary files a/docs/docs/cloud/deployment/img/graph_visualization.png and /dev/null differ diff --git a/docs/docs/cloud/deployment/img/quick_start_studio.png b/docs/docs/cloud/deployment/img/quick_start_studio.png new file mode 100644 index 000000000..54160b400 Binary files /dev/null and b/docs/docs/cloud/deployment/img/quick_start_studio.png differ diff --git a/docs/docs/cloud/deployment/test_locally.md b/docs/docs/cloud/deployment/test_locally.md index eec829bfa..43361214f 100644 --- a/docs/docs/cloud/deployment/test_locally.md +++ b/docs/docs/cloud/deployment/test_locally.md @@ -8,19 +8,25 @@ Testing locally ensures that there are no errors or conflicts with Python depend Install the proper packages: -```shell -pip install langgraph-cli -``` -Ensure you have an API key, which you can create from the LangSmith UI (Settings > API Keys). This is required to authenticate that you have LangGraph Cloud access. After you have saved the key to a safe place, place the following line in your `.env` file: +=== "pip" + ```bash + pip install -U langgraph-cli + ``` +=== "Homebrew (macOS only)" + ```bash + brew install langgraph-cli + ``` + +Ensure you have an API key, which you can create from the [LangSmith UI](https://smith.langchain.com) (Settings > API Keys). This is required to authenticate that you have LangGraph Cloud access. After you have saved the key to a safe place, place the following line in your `.env` file: ```python -LANGCHAIN_API_KEY = ********* +LANGSMITH_API_KEY = ********* ``` ## Start the API server -Once you have downloaded the CLI, you can run the following command to start the API server for local testing: +Once you have installed the CLI, you can run the following command to start the API server for local testing: ```shell langgraph up @@ -48,7 +54,7 @@ You can either initialize by passing authentication or by setting an environment from langgraph_sdk import get_client # only pass the url argument to get_client() if you changed the default port when calling langgraph up - client = get_client(url=,api_key=) + client = get_client(url=,api_key=) # Using the graph deployed with the name "agent" assistant_id = "agent" thread = await client.threads.create() @@ -60,7 +66,7 @@ You can either initialize by passing authentication or by setting an environment import { Client } from "@langchain/langgraph-sdk"; // only set the apiUrl if you changed the default port when calling langgraph up - const client = new Client({ apiUrl: , apiKey: }); + const client = new Client({ apiUrl: , apiKey: }); // Using the graph deployed with the name "agent" const assistantId = "agent"; const thread = await client.threads.create(); @@ -72,13 +78,13 @@ You can either initialize by passing authentication or by setting an environment curl --request POST \ --url /threads \ --header 'Content-Type: application/json' - --header 'x-api-key: ' + --header 'x-api-key: ' ``` #### Initialize with environment variables -If you have a `LANGCHAIN_API_KEY` set in your environment, you do not need to explicitly pass authentication to the client +If you have a `LANGSMITH_API_KEY` set in your environment, you do not need to explicitly pass authentication to the client === "Python" @@ -148,7 +154,7 @@ Now we can invoke our graph to ensure it is working. Make sure to change the inp } ``` - === "CURL" +=== "CURL" ```bash curl --request POST \ @@ -183,4 +189,4 @@ Now we can invoke our graph to ensure it is working. Make sure to change the inp ' ``` -If your graph works correctly, you should see your graph output displayed in the console. Of course, there are many more ways you might need to test your graph, for a full list of commands you can send with the SDK, see the [Python](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/) and [JS/TS](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/) references. \ No newline at end of file +If your graph works correctly, you should see your graph output displayed in the console. Of course, there are many more ways you might need to test your graph, for a full list of commands you can send with the SDK, see the [Python](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/) and [JS/TS](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/) references. diff --git a/docs/docs/cloud/how-tos/assistant_versioning.md b/docs/docs/cloud/how-tos/assistant_versioning.md index 3fe4fc7a7..9b1fccc37 100644 --- a/docs/docs/cloud/how-tos/assistant_versioning.md +++ b/docs/docs/cloud/how-tos/assistant_versioning.md @@ -1,6 +1,6 @@ # How to version assistants -In this how-to guide we will walk through how you can create and manage different assistant versions. If you haven't already, you can read [this](../concepts/api.md/#versioning-assistants) conceptual guide to gain a better understanding of what assistant versioning is. This how-to assumes you have a graph that is configurable, which means you have defined a config schema and passed it to your graph as follows: +In this how-to guide we will walk through how you can create and manage different assistant versions. If you haven't already, you can read [this](../../concepts/assistants.md#versioning-assistants) conceptual guide to gain a better understanding of what assistant versioning is. This how-to assumes you have a graph that is configurable, which means you have defined a config schema and passed it to your graph as follows: === "Python" @@ -86,19 +86,19 @@ To create an assistant using the studio do the following steps: 1. Click on the "Create New Assistant" button: -![click create](./img/click_create_assistant.png) + ![click create](./img/click_create_assistant.png) -2. Use the create assistant pane to enter info for the assistant you wish to create, and then click create: +1. Use the create assistant pane to enter info for the assistant you wish to create, and then click create: -![create](./img/create_assistant.png) + ![create](./img/create_assistant.png) -3. See that your assistant was created and is displayed in the Studio +1. See that your assistant was created and is displayed in the Studio -![view create](./img/create_assistant_view.png) + ![view create](./img/create_assistant_view.png) -4. Click on the edit button next to the selected assistant to manage your created assistant: +1. Click on the edit button next to the selected assistant to manage your created assistant: -![create edit](./img/edit_created_assistant.png) + ![create edit](./img/edit_created_assistant.png) ## Create a new version for your assistant @@ -131,15 +131,15 @@ Let's now say we wanted to add a system prompt to our assistant. We can do this 1. First, click on the edit button next to the `openai_assistant`. Then, add a system prompt and click "Save New Version": -![create new version](./img/create_new_version.png) + ![create new version](./img/create_new_version.png) -2. Then you can see it is selected in the assistant dropdown: +1. Then you can see it is selected in the assistant dropdown: -![see version dropdown](./img/see_new_version.png) + ![see version dropdown](./img/see_new_version.png) -3. And you can see all the version history in the edit pane for the assistant: +1. And you can see all the version history in the edit pane for the assistant: -![see versions](./img/see_version_history.png) + ![see versions](./img/see_version_history.png) ## Point your assistant to a different version diff --git a/docs/docs/cloud/how-tos/copy_threads.md b/docs/docs/cloud/how-tos/copy_threads.md index 22586ed4d..2e83ae0e4 100644 --- a/docs/docs/cloud/how-tos/copy_threads.md +++ b/docs/docs/cloud/how-tos/copy_threads.md @@ -4,7 +4,7 @@ You may wish to copy (i.e. "fork") an existing thread in order to keep the exist ## Setup -This code assumes you already have a thread to copy. You can read about what a thread is [here](https://langchain-ai.github.io/langgraph/cloud/concepts/api/#threads) and learn how to stream a run on a thread in [these how-to guides](https://langchain-ai.github.io/langgraph/cloud/how-tos/#streaming). +This code assumes you already have a thread to copy. You can read about what a thread is [here](../../concepts/langgraph_server.md#threads) and learn how to stream a run on a thread in [these how-to guides](../../how-tos/index.md#streaming_1). ### SDK initialization diff --git a/docs/docs/cloud/how-tos/enqueue_concurrent.md b/docs/docs/cloud/how-tos/enqueue_concurrent.md index a8cb04e4c..4f10436bf 100644 --- a/docs/docs/cloud/how-tos/enqueue_concurrent.md +++ b/docs/docs/cloud/how-tos/enqueue_concurrent.md @@ -1,6 +1,6 @@ # Enqueue -This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../concepts/api.md#double-texting). +This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../../concepts/double_texting.md). The guide covers the `enqueue` option for double texting, which adds the interruptions to a queue and executes them in the order they are received by the client. Below is a quick example of using the `enqueue` option. diff --git a/docs/docs/cloud/how-tos/index.md b/docs/docs/cloud/how-tos/index.md deleted file mode 100644 index 0cf513075..000000000 --- a/docs/docs/cloud/how-tos/index.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -hide: - - toc ---- - -# How-to Guides - -Welcome to the LangGraph Cloud how-to guides! These guides provide practical, step-by-step instructions for accomplishing key tasks in LangGraph Cloud. - -## Setup - -LangGraph Cloud gives you best in class observability, testing, and hosting services. Learn how to setup your app for deployment to LangGraph Cloud in these how-to guides - -- [How to set up app for deployment (requirements.txt)](../deployment/setup.md) -- [How to set up app for deployment (pyproject.toml)](../deployment/setup_pyproject.md) -- [How to set up app for deployment (JavaScript)](../deployment/setup_javascript.md) -- [How to customize Dockerfile](../deployment/custom_docker.md) -- [How to test locally](../deployment/test_locally.md) - -## Deploy - -Learn how to deploy your app to LangGraph Cloud in these how to guides: - -- [How to deploy to LangGraph cloud](../deployment/cloud.md) - - -## Streaming - -Streaming the results of your LLM application is vital for ensuring a good user experience, especially when your graph may call multiple models and take a long time to fully complete a run. Read about how to stream values from your graph in these how to guides: - -- [How to stream values](./stream_values.md) -- [How to stream updates](./stream_updates.md) -- [How to stream messages](./stream_messages.md) -- [How to stream events](./stream_events.md) -- [How to stream in debug mode](./stream_debug.md) -- [How to stream multiple modes](./stream_multiple.md) - -## Double-texting - -Graph execution can take a while, and sometimes users may change their mind about the input they wanted to send before their original input has finished running. For example, a user might notice a typo in their original request and will edit the prompt and resend it. Deciding what to do in these cases is important for ensuring a smooth user experience and preventing your graphs from behaving in unexpected ways. The following how-to guides provide information on the various options LangGraph Cloud gives you for dealing with double-texting: - -- [How to use the interrupt option](./interrupt_concurrent.md) -- [How to use the rollback option](./rollback_concurrent.md) -- [How to use the reject option](./reject_concurrent.md) -- [How to use the enqueue option](./enqueue_concurrent.md) - -## Human-in-the-loop - -When creating complex graphs, leaving every decision up to the LLM can be dangerous, especially when the decisions involve invoking certain tools or accessing specific documents. To remedy this, LangGraph allows you to insert human-in-the-loop behavior to ensure your graph does not have undesired outcomes. Read more about the different ways you can add human-in-the-loop capabilities to your LangGraph Cloud projects in these how-to guides: - -- [How to add a breakpoint](./human_in_the_loop_breakpoint.md) -- [How to wait for user input](./human_in_the_loop_user_input.md) -- [How to edit graph state](./human_in_the_loop_edit_state.md) -- [How to replay and branch from prior states](./human_in_the_loop_time_travel.md) -- [How to review tool calls](./human_in_the_loop_review_tool_calls.md) - -## LangGraph Studio - -LangGraph Studio is a built-in UI for visualizing, testing, and debugging your agents. - -- [How to enter LangGraph Studio](./test_deployment.md) -- [How to enter LangGraph Studio for local deployment](./test_local_deployment.md) -- [How to test your graph in LangGraph Studio](./invoke_studio.md) -- [Interact with threads in LangGraph Studio](./threads_studio.md) - -## Different Types of Runs: - -LangGraph Cloud supports multiple types of runs besides streaming runs. - -- [How to run an agent in the background](./background_run.md) -- [How to run multiple agents in the same thread](./same-thread.md) -- [How to create cron jobs](./cron_jobs.md) -- [How to create stateless runs](./stateless_runs.md) - -## Other - -Other guides that may prove helpful! - -- [How to configure agents](./configuration_cloud.md) -- [How to version assistants](./assistant_versioning.md) -- [How to convert LangGraph calls to LangGraph cloud calls](./langgraph_to_langgraph_cloud.ipynb) -- [How to integrate webhooks](./webhooks.md) -- [How to copy threads](./copy_threads.md) -- [How to check status of your threads](./check_thread_status.md) \ No newline at end of file diff --git a/docs/docs/cloud/how-tos/interrupt_concurrent.md b/docs/docs/cloud/how-tos/interrupt_concurrent.md index e1e35ca53..02098c912 100644 --- a/docs/docs/cloud/how-tos/interrupt_concurrent.md +++ b/docs/docs/cloud/how-tos/interrupt_concurrent.md @@ -1,6 +1,6 @@ # Interrupt -This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../concepts/api.md#double-texting). +This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../../concepts/double_texting.md). The guide covers the `interrupt` option for double texting, which interrupts the prior run of the graph and starts a new one with the double-text. This option does not delete the first run, but rather keeps it in the database but sets its status to `interrupted`. Below is a quick example of using the `interrupt` option. @@ -94,6 +94,7 @@ Now we can start our two runs and join the second on euntil it has completed: assistant_id, input={"messages": [{"role": "user", "content": "what's the weather in sf?"}]}, ) + # sleep a bit to get partial outputs from the first run await asyncio.sleep(2) run = await client.runs.create( thread["thread_id"], @@ -114,6 +115,7 @@ Now we can start our two runs and join the second on euntil it has completed: assistantId, { input: { messages: [{ role: "human", content: "what's the weather in sf?" }] } } ); + // sleep a bit to get partial outputs from the first run await new Promise(resolve => setTimeout(resolve, 2000)); let run = await client.runs.create( diff --git a/docs/docs/cloud/how-tos/reject_concurrent.md b/docs/docs/cloud/how-tos/reject_concurrent.md index cfc406545..c954c7da6 100644 --- a/docs/docs/cloud/how-tos/reject_concurrent.md +++ b/docs/docs/cloud/how-tos/reject_concurrent.md @@ -1,6 +1,6 @@ # Reject -This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide][double-texting]. +This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../../concepts/double_texting.md). The guide covers the `reject` option for double texting, which rejects the new run of the graph by throwing an error and continues with the original run until completion. Below is a quick example of using the `reject` option. diff --git a/docs/docs/cloud/how-tos/rollback_concurrent.md b/docs/docs/cloud/how-tos/rollback_concurrent.md index a23643685..3387e068a 100644 --- a/docs/docs/cloud/how-tos/rollback_concurrent.md +++ b/docs/docs/cloud/how-tos/rollback_concurrent.md @@ -1,6 +1,6 @@ # Rollback -This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide][double-texting]. +This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../../concepts/double_texting.md). The guide covers the `rollback` option for double texting, which interrupts the prior run of the graph and starts a new one with the double-text. This option is very similar to the `interrupt` option, but in this case the first run is completely deleted from the database and cannot be restarted. Below is a quick example of using the `rollback` option. @@ -95,7 +95,6 @@ Now let's run a thread with the multitask parameter set to "rollback": assistant_id, input={"messages": [{"role": "user", "content": "what's the weather in sf?"}]}, ) - await asyncio.sleep(2) run = await client.runs.create( thread["thread_id"], assistant_id, @@ -115,7 +114,6 @@ Now let's run a thread with the multitask parameter set to "rollback": assistantId, { input: { messages: [{ role: "human", content: "what's the weather in sf?" }] } } ); - await new Promise(resolve => setTimeout(resolve, 2000)); let run = await client.runs.create( thread["thread_id"], @@ -139,7 +137,7 @@ Now let's run a thread with the multitask parameter set to "rollback": --data "{ \"assistant_id\": \"agent\", \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what\'s the weather in sf?\"}]}, - }" && sleep 2 && curl --request POST \ + }" && curl --request POST \ --url >/threads//runs \ --header 'Content-Type: application/json' \ --data "{ diff --git a/docs/docs/cloud/how-tos/stream_debug.md b/docs/docs/cloud/how-tos/stream_debug.md index 035b0ba96..0dee6a7cd 100644 --- a/docs/docs/cloud/how-tos/stream_debug.md +++ b/docs/docs/cloud/how-tos/stream_debug.md @@ -1,5 +1,8 @@ # How to stream debug events +!!! info "Prerequisites" + * [Streaming](../../concepts/streaming.md) + This guide covers how to stream debug events from your graph (`stream_mode="debug"`). Streaming debug events produces responses containing `type` and `timestamp` keys. Debug events correspond to different steps in the graph's execution, and there are three different types of steps that will get streamed back to you: - `checkpoint`: These events will get streamed anytime the graph saves its state, which occurs after every super-step. Read more about checkpoints [here](https://langchain-ai.github.io/langgraph/concepts/low_level/#checkpointer) diff --git a/docs/docs/cloud/how-tos/stream_events.md b/docs/docs/cloud/how-tos/stream_events.md index 5d3f8f796..a627f581b 100644 --- a/docs/docs/cloud/how-tos/stream_events.md +++ b/docs/docs/cloud/how-tos/stream_events.md @@ -1,6 +1,9 @@ # How to stream events -This guide covers how to stream events from your graph (`stream_mode="events"`). Depending on the use case and user experience of your LangGraph application, your application may process event types differently. Read more about events in this [conceptual guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#astream_events-for-streaming-tokens-of-llm-calls). +!!! info "Prerequisites" + * [Streaming](../../concepts/streaming.md#streaming-llm-tokens-and-events-astream_events) + +This guide covers how to stream events from your graph (`stream_mode="events"`). Depending on the use case and user experience of your LangGraph application, your application may process event types differently. ## Setup @@ -289,131 +292,4 @@ Output: Receiving new event of type: end... - None - - -## Token-by-Token Streaming - -Token-by-token streaming can be implemented with the `events` streaming mode. The `on_chat_model_stream` event type should be processed to stream LLM responses token-by-token. - -=== "Python" - - ```python - llm_response = "" - - # stream token-by-token - async for chunk in client.runs.stream( - thread_id=thread["thread_id"], - assistant_id=assistant_id, - input=input, - stream_mode="events", - ): - if ( - chunk.event == "events" and - chunk.data["event"] == "on_chat_model_stream" and - len(chunk.data["data"]["chunk"]["content"]) > 0 and - 'text' in chunk.data["data"]["chunk"]["content"][0] - ): - llm_response += chunk.data["data"]["chunk"]["content"][0]['text'] - print(llm_response) - ``` - -=== "Javascript" - - ```js - const llmResponse = ""; - // stream events - const streamResponse = client.runs.stream( - thread["thread_id"], - assistantID, - { - input, - streamMode: "events" - } - ); - for await (const chunk of streamResponse) { - if (chunk.event === "events" && chunk.data.event === "on_chat_model_stream" && chunk.data.chunk.content.length > 0 && 'text' in chunk.data.chunk.content[0]) { - llmResponse += chunk.data.data.chunk.content[0].text; - console.log(llmResponse); - } - } - ``` - -=== "CURL" - - ```bash - curl --request POST \ - --url /threads//runs/stream \ - --header 'Content-Type: application/json' \ - --data "{ - \"assistant_id\": \"agent\", - \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"What's the weather in sf\"}]}, - \"stream_mode\": [ - \"events\" - ] - }" | sed 's/\r$//' | awk ' - /^event:/ { event = $2 } - /^data:/ { - json_data = substr($0, index($0, $2)) - - if (event == "events") { - print json_data - } - }' | jq -r ' - select(.event == "on_chat_model_stream") | - .data.chunk.content[] | .text // empty - ' | awk ' - BEGIN { llm_response="" } - $0 != "" && $0 != "null" { - llm_response = llm_response $0 - print llm_response - }' - ``` - -Output: - - The - The search - The search results provide - The search results provide the current weather conditions - The search results provide the current weather conditions in San Francisco. - The search results provide the current weather conditions in San Francisco. According - The search results provide the current weather conditions in San Francisco. According to the data, - The search results provide the current weather conditions in San Francisco. According to the data, as - The search results provide the current weather conditions in San Francisco. According to the data, as of 3 - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60 - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60. - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F ( - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16° - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The win - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is bl - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west- - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13 - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 k - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall, it appears - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall, it appears to be a nice - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall, it appears to be a nice sunny day in San - The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall, it appears to be a nice sunny day in San Francisco. - - + None \ No newline at end of file diff --git a/docs/docs/cloud/how-tos/stream_messages.md b/docs/docs/cloud/how-tos/stream_messages.md index 653da7f71..a359956a3 100644 --- a/docs/docs/cloud/how-tos/stream_messages.md +++ b/docs/docs/cloud/how-tos/stream_messages.md @@ -1,43 +1,9 @@ # How to stream messages from your graph -This guide covers how to stream messages from your graph. In order to use this mode, the state of the graph you are interacting with MUST have a `messages` key that is a list of messages. +!!! info "Prerequisites" + * [Streaming](../../concepts/streaming.md) -E.g., the state should look something like: - -=== "Python" - - ```python - from typing import Annotated - from typing_extensions import TypedDict - from langgraph.graph import add_messages - from langchain_core.messages import AnyMessage - - class State(TypedDict): - messages: Annotated[list[AnyMessage], add_messages] - ``` - -=== "Javascript" - - ```js - import { type BaseMessage } from "@langchain/core/messages"; - import { Annotation, messagesStateReducer } from "@langchain/langgraph"; - - export const StateAnnotation = Annotation.Root({ - messages: Annotation({ - reducer: messagesStateReducer, - default: () => [], - }), - }); - ``` - -Alternatively, you can use an instance or subclass of `from langgraph.graph import MessagesState` (`MessagesState` is equivalent to the implementation above). Or in Javascript: `import { MessagesAnnotation } from "@langchain/langgraph";`. - -With `stream_mode="messages"` two things will be streamed back: - -- It outputs messages produced by any chat model called inside (unless tagged in a special way) -- It outputs messages returned from nodes (to allow for nodes to return `ToolMessages` and the like) - -Read more about how the `messages` streaming mode works [here](https://langchain-ai.github.io/langgraph/cloud/concepts/api/#modemessages) +This guide covers how to stream messages from your graph. With `stream_mode="messages-tuple"`, messages (i.e. individual LLM tokens) from any chat model invocations inside your graph nodes will be streamed back. ## Setup @@ -90,101 +56,9 @@ Output: 'values': None } -Let's also define a helper function for better formatting of the tool calls in messages (for CURL we will define a helper script called `process_stream.sh`) - -=== "Python" - - ```python - def format_tool_calls(tool_calls): - if tool_calls: - formatted_calls = [] - for call in tool_calls: - formatted_calls.append( - f"Tool Call ID: {call['id']}, Function: {call['name']}, Arguments: {call['args']}" - ) - return "\n".join(formatted_calls) - return "No tool calls" - ``` - -=== "Javascript" - - ```js - function formatToolCalls(toolCalls) { - if (toolCalls && toolCalls.length > 0) { - const formattedCalls = toolCalls.map(call => { - return `Tool Call ID: ${call.id}, Function: ${call.name}, Arguments: ${call.args}`; - }); - return formattedCalls.join("\n"); - } - return "No tool calls"; - } - ``` - -=== "CURL" - - ```bash - # process_stream.sh - - format_tool_calls() { - echo "$1" | jq -r 'map("Tool Call ID: \(.id), Function: \(.name), Arguments: \(.args)") | join("\n")' - } - - process_data_item() { - local data_item="$1" - - if echo "$data_item" | jq -e '.role == "user"' > /dev/null; then - echo "Human: $(echo "$data_item" | jq -r '.content')" - else - local tool_calls=$(echo "$data_item" | jq -r '.tool_calls // []') - local invalid_tool_calls=$(echo "$data_item" | jq -r '.invalid_tool_calls // []') - local content=$(echo "$data_item" | jq -r '.content // ""') - local response_metadata=$(echo "$data_item" | jq -r '.response_metadata // {}') - - if [ -n "$content" ] && [ "$content" != "null" ]; then - echo "AI: $content" - fi - - if [ "$tool_calls" != "[]" ]; then - echo "Tool Calls:" - format_tool_calls "$tool_calls" - fi - - if [ "$invalid_tool_calls" != "[]" ]; then - echo "Invalid Tool Calls:" - format_tool_calls "$invalid_tool_calls" - fi - - if [ "$response_metadata" != "{}" ]; then - local finish_reason=$(echo "$response_metadata" | jq -r '.finish_reason // "N/A"') - echo "Response Metadata: Finish Reason - $finish_reason" - fi - fi - } - - while IFS=': ' read -r key value; do - case "$key" in - event) - event="$value" - ;; - data) - if [ "$event" = "metadata" ]; then - run_id=$(echo "$value" | jq -r '.run_id') - echo "Metadata: Run ID - $run_id" - echo "------------------------------------------------" - elif [ "$event" = "messages/partial" ]; then - echo "$value" | jq -c '.[]' | while read -r data_item; do - process_data_item "$data_item" - done - echo "------------------------------------------------" - fi - ;; - esac - done - ``` - ## Stream graph in messages mode -Now we can stream by messages, which will return complete messages (at the end of node execution) as well as tokens for any messages generated inside a node: +Now we can stream LLM tokens for any messages generated inside a node in the form of tuples `(message, metadata)`. Metadata contains additional information that can be useful for filtering the streamed outputs to a specific node or LLM. === "Python" @@ -192,41 +66,16 @@ Now we can stream by messages, which will return complete messages (at the end o input = {"messages": [{"role": "user", "content": "what's the weather in sf"}]} config = {"configurable": {"model_name": "openai"}} - async for event in client.runs.stream( + async for chunk in client.runs.stream( thread["thread_id"], assistant_id=assistant_id, input=input, config=config, - stream_mode="messages", + stream_mode="messages-tuple", ): - if event.event == "metadata": - print(f"Metadata: Run ID - {event.data['run_id']}") - print("-" * 50) - elif event.event == "messages/partial": - for data_item in event.data: - if "role" in data_item and data_item["role"] == "user": - print(f"Human: {data_item['content']}") - else: - tool_calls = data_item.get("tool_calls", []) - invalid_tool_calls = data_item.get("invalid_tool_calls", []) - content = data_item.get("content", "") - response_metadata = data_item.get("response_metadata", {}) - - if content: - print(f"AI: {content}") - - if tool_calls: - print("Tool Calls:") - print(format_tool_calls(tool_calls)) - - if invalid_tool_calls: - print("Invalid Tool Calls:") - print(format_tool_calls(invalid_tool_calls)) - - if response_metadata: - finish_reason = response_metadata.get("finish_reason", "N/A") - print(f"Response Metadata: Finish Reason - {finish_reason}") - print("-" * 50) + print(f"Receiving new event of type: {chunk.event}...") + print(chunk.data) + print("\n\n") ``` === "Javascript" @@ -248,46 +97,13 @@ Now we can stream by messages, which will return complete messages (at the end o { input, config, - streamMode: "messages" + streamMode: "messages-tuple" } ); - - for await (const event of streamResponse) { - if (event.event === "metadata") { - console.log(`Metadata: Run ID - ${event.data.run_id}`); - console.log("-".repeat(50)); - } else if (event.event === "messages/partial") { - event.data.forEach(dataItem => { - if (dataItem.role && dataItem.role === "user") { - console.log(`Human: ${dataItem.content}`); - } else { - const toolCalls = dataItem.tool_calls || []; - const invalidToolCalls = dataItem.invalid_tool_calls || []; - const content = dataItem.content || ""; - const responseMetadata = dataItem.response_metadata || {}; - - if (content) { - console.log(`AI: ${content}`); - } - - if (toolCalls.length > 0) { - console.log("Tool Calls:"); - console.log(formatToolCalls(toolCalls)); - } - - if (invalidToolCalls.length > 0) { - console.log("Invalid Tool Calls:"); - console.log(formatToolCalls(invalidToolCalls)); - } - - if (responseMetadata) { - const finishReason = responseMetadata.finish_reason || "N/A"; - console.log(`Response Metadata: Finish Reason - ${finishReason}`); - } - } - }); - console.log("-".repeat(50)); - } + for await (const chunk of streamResponse) { + console.log(`Receiving new event of type: ${chunk.event}...`); + console.log(chunk.data); + console.log("\n\n"); } ``` @@ -295,203 +111,221 @@ Now we can stream by messages, which will return complete messages (at the end o ```bash curl --request POST \ - --url /threads//runs/stream \ - --header 'Content-Type: application/json' \ - --data "{ - \"assistant_id\": \"agent\", - \"config\":{\"configurable\":{\"model_name\":\"openai\"}}, - \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"What's the weather in sf\"}]}, - \"stream_mode\": [ - \"messages\" - ] - }" | sed 's/\r$//' | ./process_stream.sh + --url /threads//runs/stream \ + --header 'Content-Type: application/json' \ + --data "{ + \"assistant_id\": \"agent\", + \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in la\"}]}, + \"stream_mode\": [ + \"messages-tuple\" + ] + }" | \ + sed 's/\r$//' | \ + awk ' + /^event:/ { + if (data_content != "") { + print data_content "\n" + } + sub(/^event: /, "Receiving event of type: ", $0) + printf "%s...\n", $0 + data_content = "" + } + /^data:/ { + sub(/^data: /, "", $0) + data_content = $0 + } + END { + if (data_content != "") { + print data_content "\n" + } + } + ' ``` Output: - Metadata: Run ID - 1ef2fe5c-6a1d-6575-bc09-d7832711c17e - -------------------------------------------------- - Invalid Tool Calls: - Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: - -------------------------------------------------- - Tool Calls: - Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {} - -------------------------------------------------- - Tool Calls: - Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {} - -------------------------------------------------- - Tool Calls: - Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {'query': ''} - -------------------------------------------------- - Tool Calls: - Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {'query': 'current'} - -------------------------------------------------- - Tool Calls: - Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {'query': 'current weather'} - -------------------------------------------------- - Tool Calls: - Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {'query': 'current weather in'} - -------------------------------------------------- - Tool Calls: - Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {'query': 'current weather in San'} - -------------------------------------------------- - Tool Calls: - Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {'query': 'current weather in San Francisco'} - -------------------------------------------------- - Tool Calls: - Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {'query': 'current weather in San Francisco'} - -------------------------------------------------- - Tool Calls: - Tool Call ID: call_cg14F20jMBqWYrNgEkdWHwB3, Function: tavily_search_results_json, Arguments: {'query': 'current weather in San Francisco'} - Response Metadata: Finish Reason - tool_calls - -------------------------------------------------- - -------------------------------------------------- - AI: The - -------------------------------------------------- - AI: The current - -------------------------------------------------- - AI: The current weather - -------------------------------------------------- - AI: The current weather in - -------------------------------------------------- - AI: The current weather in San - -------------------------------------------------- - AI: The current weather in San Francisco - -------------------------------------------------- - AI: The current weather in San Francisco is - -------------------------------------------------- - AI: The current weather in San Francisco is over - -------------------------------------------------- - AI: The current weather in San Francisco is overcast - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13 - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13. - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9 - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C ( - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57 - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57. - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0 - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-s - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-south - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6 - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6. - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph ( - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11 - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11. - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 k - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81 - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km ( - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles). - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles). The - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles). The UV - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles). The UV index - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles). The UV index is - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles). The UV index is - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles). The UV index is 3 - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles). The UV index is 3. - -------------------------------------------------- - AI: The current weather in San Francisco is overcast with a temperature of 13.9°C (57.0°F). The wind is blowing from the south-southwest at 6.9 mph (11.2 kph). The humidity is at 81%, and the visibility is 16 km (9 miles). The UV index is 3. - Response Metadata: Finish Reason - stop - -------------------------------------------------- + Receiving new event of type: metadata... + {"run_id": "1ef971e0-9a84-6154-9047-247b4ce89c4d", "attempt": 1} + ... + + Receiving new event of type: messages... + [ + { + "type": "AIMessageChunk", + "tool_calls": [ + { + "name": "tavily_search_results_json", + "args": { + "query": "weat" + }, + "id": "toolu_0114XKXdNtHQEa3ozmY1uDdM", + "type": "tool_call" + } + ], + ... + }, + { + "graph_id": "agent", + "langgraph_node": "agent", + ... + } + ] + + + + Receiving new event of type: messages... + [ + { + "type": "AIMessageChunk", + "tool_calls": [ + { + "name": "tavily_search_results_json", + "args": { + "query": "her in san " + }, + "id": "toolu_0114XKXdNtHQEa3ozmY1uDdM", + "type": "tool_call" + } + ], + ... + }, + { + "graph_id": "agent", + "langgraph_node": "agent", + ... + } + ] + + ... + + Receiving new event of type: messages... + [ + { + "type": "AIMessageChunk", + "tool_calls": [ + { + "name": "tavily_search_results_json", + "args": { + "query": "francisco" + }, + "id": "toolu_0114XKXdNtHQEa3ozmY1uDdM", + "type": "tool_call" + } + ], + ... + }, + { + "graph_id": "agent", + "langgraph_node": "agent", + ... + } + ] + + ... + + Receiving new event of type: messages... + [ + { + "content": "[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{'location': {'name': 'San Francisco', 'region': 'California', 'country': 'United States of America', 'lat': 37.775, 'lon': -122.4183, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1730475777, 'localtime': '2024-11-01 08:42'}, 'current': {'last_updated_epoch': 1730475000, 'last_updated': '2024-11-01 08:30', 'temp_c': 11.1, 'temp_f': 52.0, 'is_day': 1, 'condition': {'text': 'Partly cloudy', 'icon': '//cdn.weatherapi.com/weather/64x64/day/116.png', 'code': 1003}, 'wind_mph': 2.2, 'wind_kph': 3.6, 'wind_degree': 192, 'wind_dir': 'SSW', 'pressure_mb': 1018.0, 'pressure_in': 30.07, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 89, 'cloud': 75, 'feelslike_c': 11.5, 'feelslike_f': 52.6, 'windchill_c': 10.0, 'windchill_f': 50.1, 'heatindex_c': 10.4, 'heatindex_f': 50.7, 'dewpoint_c': 9.1, 'dewpoint_f': 48.5, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 3.0, 'gust_mph': 6.7, 'gust_kph': 10.8}}\"}]", + "type": "tool", + "tool_call_id": "toolu_0114XKXdNtHQEa3ozmY1uDdM", + ... + }, + { + "graph_id": "agent", + "langgraph_node": "action", + ... + } + ] + + ... + + Receiving new event of type: messages... + [ + { + "content": [ + { + "text": "\n\nThe search", + "type": "text", + "index": 0 + } + ], + "type": "AIMessageChunk", + ... + }, + { + "graph_id": "agent", + "langgraph_node": "agent", + ... + } + ] + + + + Receiving new event of type: messages... + [ + { + "content": [ + { + "text": " results provide", + "type": "text", + "index": 0 + } + ], + "type": "AIMessageChunk", + ... + }, + { + "graph_id": "agent", + "langgraph_node": "agent", + ... + } + ] + + + + Receiving new event of type: messages... + [ + { + "content": [ + { + "text": " the current weather conditions", + "type": "text", + "index": 0 + } + ], + "type": "AIMessageChunk", + ... + }, + { + "graph_id": "agent", + "langgraph_node": "agent", + ... + } + ] + + + + Receiving new event of type: messages... + [ + { + "content": [ + { + "text": " in San Francisco.", + "type": "text", + "index": 0 + } + ], + "type": "AIMessageChunk", + ... + }, + { + "graph_id": "agent", + "langgraph_node": "agent", + ... + } + ] + + ... \ No newline at end of file diff --git a/docs/docs/cloud/how-tos/stream_multiple.md b/docs/docs/cloud/how-tos/stream_multiple.md index 303880ccc..20a05468c 100644 --- a/docs/docs/cloud/how-tos/stream_multiple.md +++ b/docs/docs/cloud/how-tos/stream_multiple.md @@ -1,5 +1,8 @@ # How to configure multiple streaming modes at the same time +!!! info "Prerequisites" + * [Streaming](../../concepts/streaming.md) + This guide covers how to configure multiple streaming modes at the same time. ## Setup @@ -175,11 +178,6 @@ Output: - Receiving new event of type: messages/complete... - [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}] - - - Receiving new event of type: debug... {'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.117924+00:00', 'step': 0, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bc81-68c8-8000-4e18ae7d67a5', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}]}, 'metadata': {'source': 'loop', 'step': 0, 'writes': None}}} @@ -305,11 +303,6 @@ Output: - Receiving new event of type: messages/complete... - [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}] - - - Receiving new event of type: debug... {'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.124510+00:00', 'step': 1, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bc91-6a34-8001-26353c117c25', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}, 'metadata': {'source': 'loop', 'step': 1, 'writes': {'agent': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}}}} @@ -469,12 +462,7 @@ Output: {'event': 'on_chain_stream', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'name': 'LangGraph', 'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'data': {'chunk': ['values', {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}]}, 'parent_ids': []} - - Receiving new event of type: messages/complete... - [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}] - - - + Receiving new event of type: debug... {'type': 'checkpoint', 'timestamp': '2024-06-24T21:34:06.134190+00:00', 'step': 3, 'payload': {'config': {'tags': [], 'metadata': {'created_by': 'system', 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'callbacks': [None], 'recursion_limit': 25, 'configurable': {'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25', 'user_id': '', 'graph_id': 'agent', 'thread_id': 'bfc68029-1f7b-400f-beab-6f9032a52da4', 'thread_ts': '1ef32717-bca9-6418-8003-8d0d0b06845c', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'}, 'run_id': '1ef32717-bc30-6cf2-8a26-33f63567bc25'}, 'values': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': "What's the weather in SF?", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '7da1bafa-f53c-4df8-ba63-8dd517140b9f', 'example': False}, {'content': 'begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-2424dd6d-5cf5-4244-8d98-357640ce6e12', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': 'tool_call__begin', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': None, 'id': '639ca779-403d-4915-a066-327e1f634c8b', 'tool_call_id': 'tool_call_id'}, {'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}, 'metadata': {'source': 'loop', 'step': 3, 'writes': {'agent': {'some_bytes': 'c29tZV9ieXRlcw==', 'some_byte_array': 'c29tZV9ieXRlX2FycmF5', 'dict_with_bytes': {'more_bytes': 'bW9yZV9ieXRlcw=='}, 'messages': [{'content': 'end', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-0f2ef0a1-0fc7-445c-9df4-55e8bb284575', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}}}} diff --git a/docs/docs/cloud/how-tos/stream_updates.md b/docs/docs/cloud/how-tos/stream_updates.md index 72f16d881..1c08b5e80 100644 --- a/docs/docs/cloud/how-tos/stream_updates.md +++ b/docs/docs/cloud/how-tos/stream_updates.md @@ -1,6 +1,9 @@ # How to stream state updates of your graph -This guide covers how to use `stream_mode="updates"` for your graph, which will stream the updates to the graph state that are made after each node is executed. This differs from using `stream_mode="values"`: instead of streaming the entire value of the state at each superstep, it only streams the updates from each of the nodes that made an update to the state at that superstep. Read [this conceptual guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#stream-and-astream) to learn more. +!!! info "Prerequisites" + * [Streaming](../../concepts/streaming.md) + +This guide covers how to use `stream_mode="updates"` for your graph, which will stream the updates to the graph state that are made after each node is executed. This differs from using `stream_mode="values"`: instead of streaming the entire value of the state at each superstep, it only streams the updates from each of the nodes that made an update to the state at that superstep. ## Setup @@ -146,24 +149,69 @@ Now we can stream by updates, which outputs updates made to the state by each no Output: Receiving new event of type: metadata... - {'run_id': 'cfc96c16-ed9a-44bd-b5bb-c30e3c0725f0'} - - - - Receiving new event of type: data... - {'agent': {'messages': [{'content': [{'id': 'toolu_0148tMmDK51iLQfG1yaNwRHM', 'input': {'query': 'weather in los angeles'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-1a9d32b0-7007-4a36-abde-8df812a0ed94', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in los angeles'}, 'id': 'toolu_0148tMmDK51iLQfG1yaNwRHM'}], 'invalid_tool_calls': []}]}} - - - - Receiving new event of type: data... - {'action': {'messages': [{'content': '[{"url": "https://www.weatherapi.com/", "content": "{\'location\': {\'name\': \'Los Angeles\', \'region\': \'California\', \'country\': \'United States of America\', \'lat\': 34.05, \'lon\': -118.24, \'tz_id\': \'America/Los_Angeles\', \'localtime_epoch\': 1716062239, \'localtime\': \'2024-05-18 12:57\'}, \'current\': {\'last_updated_epoch\': 1716061500, \'last_updated\': \'2024-05-18 12:45\', \'temp_c\': 18.9, \'temp_f\': 66.0, \'is_day\': 1, \'condition\': {\'text\': \'Overcast\', \'icon\': \'//cdn.weatherapi.com/weather/64x64/day/122.png\', \'code\': 1009}, \'wind_mph\': 2.2, \'wind_kph\': 3.6, \'wind_degree\': 10, \'wind_dir\': \'N\', \'pressure_mb\': 1017.0, \'pressure_in\': 30.02, \'precip_mm\': 0.0, \'precip_in\': 0.0, \'humidity\': 65, \'cloud\': 100, \'feelslike_c\': 18.9, \'feelslike_f\': 66.0, \'vis_km\': 16.0, \'vis_miles\': 9.0, \'uv\': 6.0, \'gust_mph\': 7.5, \'gust_kph\': 12.0}}"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': 'a36e8cd1-0e96-4417-9c15-f10a945d2b42', 'tool_call_id': 'toolu_0148tMmDK51iLQfG1yaNwRHM'}]}} - - - - Receiving new event of type: data... - {'agent': {'messages': [{'content': 'The weather in Los Angeles is currently overcast with a temperature of around 66°F (18.9°C). There are light winds from the north at around 2-3 mph. The humidity is 65% and visibility is good at 9 miles. Overall, mild spring weather conditions in LA.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-d5c1c2f0-b12d-41ce-990b-f36570e7483d', 'example': False, 'tool_calls': [], 'invalid_tool_calls': []}]}} - - - + {"run_id": "cfc96c16-ed9a-44bd-b5bb-c30e3c0725f0"} + + + + Receiving new event of type: updates... + { + "agent": { + "messages": [ + { + "type": "ai", + "tool_calls": [ + { + "name": "tavily_search_results_json", + "args": { + "query": "weather in los angeles" + }, + "id": "toolu_0148tMmDK51iLQfG1yaNwRHM" + } + ], + ... + } + ] + } + } + + + + Receiving new event of type: updates... + { + "action": { + "messages": [ + { + "content": [ + { + "url": "https://www.weatherapi.com/", + "content": "{\"location\": {\"name\": \"Los Angeles\", \"region\": \"California\", \"country\": \"United States of America\", \"lat\": 34.05, \"lon\": -118.24, \"tz_id\": \"America/Los_Angeles\", \"localtime_epoch\": 1716062239, \"localtime\": \"2024-05-18 12:57\"}, \"current\": {\"last_updated_epoch\": 1716061500, \"last_updated\": \"2024-05-18 12:45\", \"temp_c\": 18.9, \"temp_f\": 66.0, \"is_day\": 1, \"condition\": {\"text\": \"Overcast\", \"icon\": \"//cdn.weatherapi.com/weather/64x64/day/122.png\", \"code\": 1009}, \"wind_mph\": 2.2, \"wind_kph\": 3.6, \"wind_degree\": 10, \"wind_dir\": \"N\", \"pressure_mb\": 1017.0, \"pressure_in\": 30.02, \"precip_mm\": 0.0, \"precip_in\": 0.0, \"humidity\": 65, \"cloud\": 100, \"feelslike_c\": 18.9, \"feelslike_f\": 66.0, \"vis_km\": 16.0, \"vis_miles\": 9.0, \"uv\": 6.0, \"gust_mph\": 7.5, \"gust_kph\": 12.0}}" + } + ], + "type": "tool", + "name": "tavily_search_results_json", + "tool_call_id": "toolu_0148tMmDK51iLQfG1yaNwRHM", + ... + } + ] + } + } + + + + Receiving new event of type: updates... + { + "agent": { + "messages": [ + { + "content": "The weather in Los Angeles is currently overcast with a temperature of around 66°F (18.9°C). There are light winds from the north at around 2-3 mph. The humidity is 65% and visibility is good at 9 miles. Overall, mild spring weather conditions in LA.", + "type": "ai", + ... + } + ] + } + } + + + Receiving new event of type: end... None \ No newline at end of file diff --git a/docs/docs/cloud/how-tos/stream_values.md b/docs/docs/cloud/how-tos/stream_values.md index 8dab72893..17e186027 100644 --- a/docs/docs/cloud/how-tos/stream_values.md +++ b/docs/docs/cloud/how-tos/stream_values.md @@ -1,6 +1,9 @@ # How to stream full state of your graph -This guide covers how to use `stream_mode="values"`, which streams the value of the state at each superstep. This differs from using `stream_mode="updates"`: instead of streaming just the updates to the state from each node, it streams the entire graph state at that superstep. Read [this conceptual guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#stream-and-astream) to learn more. +!!! info "Prerequisites" + * [Streaming](../../concepts/streaming.md) + +This guide covers how to use `stream_mode="values"`, which streams the value of the state at each superstep. This differs from using `stream_mode="updates"`: instead of streaming just the updates to the state from each node, it streams the entire graph state at that superstep. ## Setup @@ -133,30 +136,93 @@ Now we can stream by values, which streams the full state of the graph after eac Output: Receiving new event of type: metadata... - {'run_id': 'f08791ce-0a3d-44e0-836c-ff62cd2e2786'} - - - + {"run_id": "f08791ce-0a3d-44e0-836c-ff62cd2e2786"} + + + Receiving new event of type: values... - {'messages': [{'role': 'human', 'content': 'what's the weather in la'}]} - - - + { + "messages": [ + { + "role": "human", + "content": "what's the weather in la" + } + ] + } + + + Receiving new event of type: values... - {'messages': [{'content': 'what's the weather in la', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'faa15565-8823-4aa1-87af-e21b40526fae', 'example': False}, {'content': [{'id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g', 'input': {'query': 'weather in los angeles'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-3fe1db7a-6b8d-4d83-ba07-8657190ad811', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in los angeles'}, 'id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g'}], 'invalid_tool_calls': []}]} - - - + { + "messages": [ + { + "content": "what's the weather in la", + "type": "human", + ... + }, + { + "content": "", + "type": "ai", + "tool_calls": [ + { + "name": "tavily_search_results_json", + "args": { + "query": "weather in los angeles" + }, + "id": "toolu_01E5mSaZWm5rWJnCqmt63v4g" + } + ], + ... + } + ] + } + + ... + Receiving new event of type: values... - {'messages': [{'content': 'what's the weather in la', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'faa15565-8823-4aa1-87af-e21b40526fae', 'example': False}, {'content': [{'id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g', 'input': {'query': 'weather in los angeles'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-3fe1db7a-6b8d-4d83-ba07-8657190ad811', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in los angeles'}, 'id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g'}], 'invalid_tool_calls': []}, {'content': '[{"url": "https://www.weatherapi.com/", "content": "{\'location\': {\'name\': \'Los Angeles\', \'region\': \'California\', \'country\': \'United States of America\', \'lat\': 34.05, \'lon\': -118.24, \'tz_id\': \'America/Los_Angeles\', \'localtime_epoch\': 1716310320, \'localtime\': \'2024-05-21 9:52\'}, \'current\': {\'last_updated_epoch\': 1716309900, \'last_updated\': \'2024-05-21 09:45\', \'temp_c\': 16.7, \'temp_f\': 62.1, \'is_day\': 1, \'condition\': {\'text\': \'Overcast\', \'icon\': \'//cdn.weatherapi.com/weather/64x64/day/122.png\', \'code\': 1009}, \'wind_mph\': 8.1, \'wind_kph\': 13.0, \'wind_degree\': 250, \'wind_dir\': \'WSW\', \'pressure_mb\': 1015.0, \'pressure_in\': 29.97, \'precip_mm\': 0.0, \'precip_in\': 0.0, \'humidity\': 65, \'cloud\': 100, \'feelslike_c\': 16.7, \'feelslike_f\': 62.1, \'vis_km\': 16.0, \'vis_miles\': 9.0, \'uv\': 5.0, \'gust_mph\': 12.5, \'gust_kph\': 20.2}}"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': '0d5dab31-5ff8-4ae2-a560-bc4bcba7c9d7', 'tool_call_id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g'}]} - - - - Receiving new event of type: values... - {'messages': [{'content': 'what's the weather in la', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'faa15565-8823-4aa1-87af-e21b40526fae', 'example': False}, {'content': [{'id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g', 'input': {'query': 'weather in los angeles'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-3fe1db7a-6b8d-4d83-ba07-8657190ad811', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'weather in los angeles'}, 'id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g'}], 'invalid_tool_calls': []}, {'content': '[{"url": "https://www.weatherapi.com/", "content": "{\'location\': {\'name\': \'Los Angeles\', \'region\': \'California\', \'country\': \'United States of America\', \'lat\': 34.05, \'lon\': -118.24, \'tz_id\': \'America/Los_Angeles\', \'localtime_epoch\': 1716310320, \'localtime\': \'2024-05-21 9:52\'}, \'current\': {\'last_updated_epoch\': 1716309900, \'last_updated\': \'2024-05-21 09:45\', \'temp_c\': 16.7, \'temp_f\': 62.1, \'is_day\': 1, \'condition\': {\'text\': \'Overcast\', \'icon\': \'//cdn.weatherapi.com/weather/64x64/day/122.png\', \'code\': 1009}, \'wind_mph\': 8.1, \'wind_kph\': 13.0, \'wind_degree\': 250, \'wind_dir\': \'WSW\', \'pressure_mb\': 1015.0, \'pressure_in\': 29.97, \'precip_mm\': 0.0, \'precip_in\': 0.0, \'humidity\': 65, \'cloud\': 100, \'feelslike_c\': 16.7, \'feelslike_f\': 62.1, \'vis_km\': 16.0, \'vis_miles\': 9.0, \'uv\': 5.0, \'gust_mph\': 12.5, \'gust_kph\': 20.2}}"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': '0d5dab31-5ff8-4ae2-a560-bc4bcba7c9d7', 'tool_call_id': 'toolu_01E5mSaZWm5rWJnCqmt63v4g'}, {'content': 'Based on the weather API results, the current weather in Los Angeles is overcast with a temperature of around 62°F (17°C). There are light winds from the west-southwest around 8-13 mph. The humidity is 65% and visibility is good at 9 miles. Overall, mild spring weather conditions in LA.', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-4d6d4c23-5aad-4042-b0d9-19407a9e08e3', 'example': False, 'tool_calls': [], 'invalid_tool_calls': []}]} - - - + { + "messages": [ + { + "content": "what's the weather in la", + "type": "human", + ... + }, + { + "content": "", + "type": "ai", + "tool_calls": [ + { + "name": "tavily_search_results_json", + "args": { + "query": "weather in los angeles" + }, + "id": "toolu_01E5mSaZWm5rWJnCqmt63v4g" + } + ], + ... + } + { + "content": [ + { + "url": "https://www.weatherapi.com/", + "content": "{\"location\": {\"name\": \"Los Angeles\", \"region\": \"California\", \"country\": \"United States of America\", \"lat\": 34.05, \"lon\": -118.24, \"tz_id\": \"America/Los_Angeles\", \"localtime_epoch\": 1716310320, \"localtime\": \"2024-05-21 9:52\"}, \"current\": {\"last_updated_epoch\": 1716309900, \"last_updated\": \"2024-05-21 09:45\", \"temp_c\": 16.7, \"temp_f\": 62.1, \"is_day\": 1, \"condition\": {\"text\": \"Overcast\", \"icon\": \"//cdn.weatherapi.com/weather/64x64/day/122.png\", \"code\": 1009}, \"wind_mph\": 8.1, \"wind_kph\": 13.0, \"wind_degree\": 250, \"wind_dir\": \"WSW\", \"pressure_mb\": 1015.0, \"pressure_in\": 29.97, \"precip_mm\": 0.0, \"precip_in\": 0.0, \"humidity\": 65, \"cloud\": 100, \"feelslike_c\": 16.7, \"feelslike_f\": 62.1, \"vis_km\": 16.0, \"vis_miles\": 9.0, \"uv\": 5.0, \"gust_mph\": 12.5, \"gust_kph\": 20.2}}" + } + ], + "type": "tool", + "name": "tavily_search_results_json", + "tool_call_id": "toolu_01E5mSaZWm5rWJnCqmt63v4g" + ... + }, + { + "content": "Based on the weather API results, the current weather in Los Angeles is overcast with a temperature of around 62°F (17°C). There are light winds from the west-southwest around 8-13 mph. The humidity is 65% and visibility is good at 9 miles. Overall, mild spring weather conditions in LA.", + "type": "ai", + ... + } + ] + } + + + Receiving new event of type: end... None @@ -228,40 +294,42 @@ If we want to just get the final result, we can use this endpoint and just keep Output: - {'messages': [{'content': 'what's the weather in la', - 'additional_kwargs': {}, - 'response_metadata': {}, - 'type': 'human', - 'name': None, - 'id': 'e78c2f94-d810-42fc-a399-11f6bb1b1092', - 'example': False}, - {'content': [{'id': 'toolu_01SBMoAGr4U9x3ibztm2UUom', - 'input': {'query': 'weather in los angeles'}, - 'name': 'tavily_search_results_json', - 'type': 'tool_use'}], - 'additional_kwargs': {}, - 'response_metadata': {}, - 'type': 'ai', - 'name': None, - 'id': 'run-80767ab8-09fc-40ec-9e45-657ddef5e0b1', - 'example': False, - 'tool_calls': [{'name': 'tavily_search_results_json', - 'args': {'query': 'weather in los angeles'}, - 'id': 'toolu_01SBMoAGr4U9x3ibztm2UUom'}], - 'invalid_tool_calls': []}, - {'content': '[{"url": "https://www.weatherapi.com/", "content": "{\'location\': {\'name\': \'Los Angeles\', \'region\': \'California\', \'country\': \'United States of America\', \'lat\': 34.05, \'lon\': -118.24, \'tz_id\': \'America/Los_Angeles\', \'localtime_epoch\': 1716310320, \'localtime\': \'2024-05-21 9:52\'}, \'current\': {\'last_updated_epoch\': 1716309900, \'last_updated\': \'2024-05-21 09:45\', \'temp_c\': 16.7, \'temp_f\': 62.1, \'is_day\': 1, \'condition\': {\'text\': \'Overcast\', \'icon\': \'//cdn.weatherapi.com/weather/64x64/day/122.png\', \'code\': 1009}, \'wind_mph\': 8.1, \'wind_kph\': 13.0, \'wind_degree\': 250, \'wind_dir\': \'WSW\', \'pressure_mb\': 1015.0, \'pressure_in\': 29.97, \'precip_mm\': 0.0, \'precip_in\': 0.0, \'humidity\': 65, \'cloud\': 100, \'feelslike_c\': 16.7, \'feelslike_f\': 62.1, \'vis_km\': 16.0, \'vis_miles\': 9.0, \'uv\': 5.0, \'gust_mph\': 12.5, \'gust_kph\': 20.2}}"}]', - 'additional_kwargs': {}, - 'response_metadata': {}, - 'type': 'tool', - 'name': 'tavily_search_results_json', - 'id': 'af25e94a-c119-48c3-bbd3-096e42f472ac', - 'tool_call_id': 'toolu_01SBMoAGr4U9x3ibztm2UUom'}, - {'content': 'Based on the weather API results, the current weather in Los Angeles is overcast with a temperature of around 62°F (17°C). There are light winds from the west-southwest around 8-13 mph. The humidity is 65% and visibility is good at 9 miles. Overall, mild spring weather conditions in LA.', - 'additional_kwargs': {}, - 'response_metadata': {}, - 'type': 'ai', - 'name': None, - 'id': 'run-b90f0037-e56a-4f3b-ad92-00d10d079a9e', - 'example': False, - 'tool_calls': [], - 'invalid_tool_calls': []}]} \ No newline at end of file + { + "messages": [ + { + "content": "what's the weather in la", + "type": "human", + ... + }, + { + "type": "ai", + "tool_calls": [ + { + "name": "tavily_search_results_json", + "args": { + "query": "weather in los angeles" + }, + "id": "toolu_01E5mSaZWm5rWJnCqmt63v4g" + } + ], + ... + } + { + "content": [ + { + "url": "https://www.weatherapi.com/", + "content": "{\"location\": {\"name\": \"Los Angeles\", \"region\": \"California\", \"country\": \"United States of America\", \"lat\": 34.05, \"lon\": -118.24, \"tz_id\": \"America/Los_Angeles\", \"localtime_epoch\": 1716310320, \"localtime\": \"2024-05-21 9:52\"}, \"current\": {\"last_updated_epoch\": 1716309900, \"last_updated\": \"2024-05-21 09:45\", \"temp_c\": 16.7, \"temp_f\": 62.1, \"is_day\": 1, \"condition\": {\"text\": \"Overcast\", \"icon\": \"//cdn.weatherapi.com/weather/64x64/day/122.png\", \"code\": 1009}, \"wind_mph\": 8.1, \"wind_kph\": 13.0, \"wind_degree\": 250, \"wind_dir\": \"WSW\", \"pressure_mb\": 1015.0, \"pressure_in\": 29.97, \"precip_mm\": 0.0, \"precip_in\": 0.0, \"humidity\": 65, \"cloud\": 100, \"feelslike_c\": 16.7, \"feelslike_f\": 62.1, \"vis_km\": 16.0, \"vis_miles\": 9.0, \"uv\": 5.0, \"gust_mph\": 12.5, \"gust_kph\": 20.2}}" + } + ], + "type": "tool", + "name": "tavily_search_results_json", + "tool_call_id": "toolu_01E5mSaZWm5rWJnCqmt63v4g" + ... + }, + { + "content": "Based on the weather API results, the current weather in Los Angeles is overcast with a temperature of around 62°F (17°C). There are light winds from the west-southwest around 8-13 mph. The humidity is 65% and visibility is good at 9 miles. Overall, mild spring weather conditions in LA.", + "type": "ai", + ... + } + ] + } \ No newline at end of file diff --git a/docs/docs/cloud/how-tos/test_deployment.md b/docs/docs/cloud/how-tos/test_deployment.md index 3a540b229..127e1e25e 100644 --- a/docs/docs/cloud/how-tos/test_deployment.md +++ b/docs/docs/cloud/how-tos/test_deployment.md @@ -4,7 +4,7 @@ The LangGraph Studio UI connects directly to LangGraph Cloud deployments. Starting from the LangSmith UI... -1. In the left-hand navigation panel, select `Deployments`. The `Deployments` view contains a list of existing LangGraph Cloud deployments. +1. In the left-hand navigation panel, select `LangGraph Cloud`. The `LangGraph Cloud` view contains a list of existing LangGraph Cloud deployments. 1. Select an existing deployment to test with LangGraph Studio. 1. In the top-right corner, select `Open LangGraph Studio`. 1. [Invoke an assistant](./invoke_studio.md) or [view an existing thread](./threads_studio.md). diff --git a/docs/docs/cloud/how-tos/webhooks.md b/docs/docs/cloud/how-tos/webhooks.md index 59a3ec049..8e3396923 100644 --- a/docs/docs/cloud/how-tos/webhooks.md +++ b/docs/docs/cloud/how-tos/webhooks.md @@ -76,7 +76,9 @@ Output: ## Use graph with a webhook -Now we can invoke a run with a webhook: +To invoke a run with a webhook, we specify the `webhook` parameter with the desired endpoint when creating a run. Webhook requests are triggered by the end of a run. + +For example, if we can receive requests at `https://my-server.app/my-webhook-endpoint`, we can pass this to `stream`: === "Python" @@ -89,7 +91,7 @@ Now we can invoke a run with a webhook: assistant_id=assistant_id, input=input, stream_mode="events", - webhook="your-webhook" + webhook="https://my-server.app/my-webhook-endpoint" ): # Do something with the stream output pass @@ -107,7 +109,7 @@ Now we can invoke a run with a webhook: assistantID, { input: input, - webhook: "your-webhook" + webhook: "https://my-server.app/my-webhook-endpoint" } ); for await (const chunk of streamResponse) { @@ -124,8 +126,17 @@ Now we can invoke a run with a webhook: --data '{ "assistant_id": , "input" : {"messages":[{"role": "user", "content": "Hello!"}]}, - "webhook": + "webhook": "https://my-server.app/my-webhook-endpoint" }' ``` -And that's it! Now you can trigger your custom webhooks whenever you want in your LangGraph applications! \ No newline at end of file +The schema for the payload sent to `my-webhook-endpoint` is that of a [run](../../concepts/langgraph_server.md/#runs). See [API Reference](https://langchain-ai.github.io/langgraph/cloud/reference/api/api_ref.html#model/run) for more detail. Note that the run input, configuration, etc. are included in the `kwargs` field. + +### Signing webhook requests + +To sign the webhook requests, we can specify a token parameter in the webhook URL, e.g., +``` +https://my-server.app/my-webhook-endpoint?token=... +``` + +The server should then extract the token from the request's parameters and validate it before processing the payload. diff --git a/docs/docs/cloud/img/cloud_deployment.png b/docs/docs/cloud/img/cloud_deployment.png deleted file mode 100644 index 9b65bb4e6..000000000 Binary files a/docs/docs/cloud/img/cloud_deployment.png and /dev/null differ diff --git a/docs/docs/cloud/img/graph_video_poster.png b/docs/docs/cloud/img/graph_video_poster.png deleted file mode 100644 index e757082d6..000000000 Binary files a/docs/docs/cloud/img/graph_video_poster.png and /dev/null differ diff --git a/docs/docs/cloud/index.md b/docs/docs/cloud/index.md deleted file mode 100644 index 73421da49..000000000 --- a/docs/docs/cloud/index.md +++ /dev/null @@ -1,44 +0,0 @@ -# LangGraph Cloud (beta) - -!!! tip - - LangGraph is an MIT-licensed open-source library, which we are committed to maintaining and growing for the community. - - LangGraph Cloud is an optional managed hosting service for LangGraph, which provides additional features geared towards production deployments. - - We are actively contributing improvements back to LangGraph informed by our work on LangGraph Cloud. - - You can always deploy LangGraph applications on your own infrastructure using the open-source LangGraph project. - -!!! warning "Under Construction" - LangGraph Cloud documentation is under construction. Contents may change until general availability. - - - - - -## Overview - -LangGraph Cloud is a managed service for deploying and hosting LangGraph applications. Deploying applications with LangGraph Cloud shortens the time-to-market for developers. With one click, deploy a production-ready API with built-in persistence for your LangGraph application. LangGraph Cloud APIs are horizontally scalable and deployed with durable storage. - -The LangGraph Cloud API exposes functionality of your LangGraph application through [Assistants](./concepts/api.md#assistants). An assistant abstracts the cognitive architecture of your graph. Invoke an assistant by calling the pre-built [API endpoints](./reference/api/api_ref.md). - -LangGraph Cloud is seamlessly integrated with [LangSmith](https://www.langchain.com/langsmith) and is accessible from within the LangSmith UI. - -LangGraph Cloud applications can be tested and debugged using the [LangGraph Studio Desktop](https://github.com/langchain-ai/langgraph-studio). - -## Key Features - -The LangGraph Cloud API supports key LangGraph features in addition to new functionality for enabling complex, agentic workflows. - -- **Assistants and Threads**: Assistants abstract the cognitive architecture of graphs and threads track the state/history of graphs. -- **Streaming**: API support for [LangGraph streaming modes](../concepts/low_level.md#streaming) including setting multiple streaming modes at the same time. -- **Human-in-the-Loop**: API support for [LangGraph human-in-the-loop features](../concepts/agentic_concepts.md#human-in-the-loop). -- **Double Texting**: Configure how assistants respond when new input is received while processing a previous input. Interrupt, rollback, reject, or enqueue. -- **Background Runs/Cron Jobs**: A built-in task queue enables background runs and scheduled cron jobs. -- **Stateless Runs**: For simpler use cases, invoke an assistant without needing to create a thread. - -## Documentation - -- [Tutorials](./quick_start.md): Learn to build and deploy applications for LangGraph Cloud. -- [How-to Guides](./how-tos/index.md): Learn how to set up a LangGraph application for deployment and implement features of the LangGraph Cloud API such as streaming tokens, configuring double texting, and creating cron jobs. Go here if you want to copy and run a specific code snippet. -- [Conceptual Guides](./concepts/api.md): In-depth explanations of the core data models (e.g. assistants), key features of the LangGraph Cloud API (e.g. double texting), and the architecture of a LangGraph Cloud deployment. -- [Reference](./reference/api/api_ref.md): References for the LangGraph Cloud API, the corresponding Python and JS/TS SDKs, the LangGraph CLI, and deployment environment variables. diff --git a/docs/docs/cloud/quick_start.md b/docs/docs/cloud/quick_start.md index 08e42ac9c..158bb1fc8 100644 --- a/docs/docs/cloud/quick_start.md +++ b/docs/docs/cloud/quick_start.md @@ -1,6 +1,8 @@ -# Quick Start +# LangGraph Cloud Quick Start -This quick start guide will cover how to build a simple agent that can look up things on the internet. We will then deploy it to LangGraph Cloud, use the LangGraph Studio to visualize and test it out, and use the LangGraph SDK to interact with it. +In this tutorial you will build and deploy a simple chatbot agent that can look things up on the internet. You will be using [LangGraph Cloud](../concepts/langgraph_cloud.md), [LangGraph Studio](../concepts/langgraph_studio.md) to visualize and test it out, and [LangGraph SDK](./reference/sdk/python_sdk_ref.md) to interact with the deployed agent. + +If you want to learn how to build an agent like this from scratch, take a look at the [LangGraph Quick Start tutorial](../tutorials/introduction.ipynb). ## Set up requirements @@ -10,146 +12,183 @@ This tutorial will use: - Tavily for the search engine - sign up and get an API key [here](https://app.tavily.com/) - LangSmith for hosting - sign up and get an API key [here](https://smith.langchain.com/) -## Set up local files +## Create and configure your app -1. Create a new application with the following directory and files: +First, let's set create all of the necessary files for our LangGraph application. -=== "Python" +1. __Create application directory and files__ - / - |-- agent.py # code for your LangGraph agent - |-- requirements.txt # Python packages required for your graph - |-- langgraph.json # configuration file for LangGraph - |-- .env # environment files with API keys + Create a new application `my-app` with the following file structure: -=== "Javascript" - - / - |-- agent.ts # code for your LangGraph agent - |-- package.json # Javascript packages required for your graph - |-- langgraph.json # configuration file for LangGraph - |-- .env # environment files with API keys - -2. The `agent.py`/`agent.ts` file should contain code for defining your graph. The following code is a simple example, the important thing is that at some point in your file you compile your graph and assign the compiled graph to a variable (in this case the `graph` variable). This example code uses `create_react_agent`, a prebuilt agent. You can read more about it [here](../concepts/agentic_concepts.md#react-implementation). - -=== "Python" - - ```python - from langchain_anthropic import ChatAnthropic - from langchain_community.tools.tavily_search import TavilySearchResults - from langgraph.prebuilt import create_react_agent - - model = ChatAnthropic(model="claude-3-5-sonnet-20240620") - - tools = [TavilySearchResults(max_results=2)] - - graph = create_react_agent(model, tools) + ```shell + mkdir my-app ``` -=== "Javascript" + === "Python" - ```ts - import { ChatAnthropic } from "@langchain/anthropic"; - import { TavilySearchResults } from "@langchain/community/tools/tavily_search"; - import { createReactAgent } from "@langchain/langgraph/prebuilt"; + my-app/ + |-- agent.py # code for your LangGraph agent + |-- requirements.txt # Python packages required for your graph + |-- langgraph.json # configuration file for LangGraph + |-- .env # environment files with API keys - const model = new ChatAnthropic({ - model: "claude-3-5-sonnet-20240620", - }); + === "Javascript" - const tools = [ - new TavilySearchResults({ maxResults: 3, }), - ]; + my-app/ + |-- agent.ts # code for your LangGraph agent + |-- package.json # Javascript packages required for your graph + |-- langgraph.json # configuration file for LangGraph + |-- .env # environment files with API keys - export const graph = createReactAgent({ llm: model, tools }); - ``` -3. The `requirements.txt`/`package.json` file should contain any dependencies for your graph(s). In this case we only require four packages for our graph to run: +1. __Define your graph__ -=== "Python" + === "Python" + The `agent.py` file should contain code with your graph. - ```python - langgraph - langchain_anthropic - tavily-python - langchain_community - ``` + === "Javascript" + The `agent.ts` file should contain code with your graph. -=== "Javascript" + The following code example is a simple chatbot agent (similar to the one in the [previous tutorial](../tutorials/introduction.ipynb)). Specifically, it uses [create_react_agent][langgraph.prebuilt.chat_agent_executor.create_react_agent], a prebuilt [ReAct](../concepts/agentic_concepts.md#react-implementation)-style agent. - ```js - { - "name": "my-app", - "packageManager": "yarn@1.22.22", - "dependencies": { - "@langchain/community": "^0.2.31", - "@langchain/core": "^0.2.31", - "@langchain/langgraph": "0.2.0", - "@langchain/openai": "^0.2.8" + The `agent` file needs to have a variable with a [CompiledGraph][langgraph.graph.graph.CompiledGraph] (in this case the `graph` variable). + + === "Python" + + ```python + # agent.py + from langchain_anthropic import ChatAnthropic + from langchain_community.tools.tavily_search import TavilySearchResults + from langgraph.prebuilt import create_react_agent + + model = ChatAnthropic(model="claude-3-5-sonnet-20240620") + + tools = [TavilySearchResults(max_results=2)] + + # compiled graph + graph = create_react_agent(model, tools) + ``` + + === "Javascript" + + ```ts + // agent.ts + import { ChatAnthropic } from "@langchain/anthropic"; + import { TavilySearchResults } from "@langchain/community/tools/tavily_search"; + import { createReactAgent } from "@langchain/langgraph/prebuilt"; + + const model = new ChatAnthropic({ + model: "claude-3-5-sonnet-20240620", + }); + + const tools = [ + new TavilySearchResults({ maxResults: 3, }), + ]; + + // compiled graph + export const graph = createReactAgent({ llm: model, tools }); + ``` + +1. __Specify dependencies__ + + === "Python" + You should add dependencies for your graph(s) to `requirements.txt`. + + === "Javascript" + You should add dependencies for your graph(s) to `package.json`. + + In this case we only require four packages for our graph to run: + + === "Python" + + ```python + langgraph + langchain_anthropic + tavily-python + langchain_community + ``` + + === "Javascript" + + ```js + { + "name": "my-app", + "packageManager": "yarn@1.22.22", + "dependencies": { + "@langchain/community": "^0.3.11", + "@langchain/core": "^0.3.16", + "@langchain/langgraph": "0.2.18", + "@langchain/anthropic": "^0.3.7" + } + } + ``` + +1. __Create LangGraph configuration file__ + + The [`langgraph.json`][langgraph.json] file is a configuration file that describes what graph(s) you are going to deploy. In this case we only have one graph: the compiled `graph` object from `agent.py` / `agent.ts`. + + === "Python" + + ```json + { + "dependencies": ["."], + "graphs": { + "agent": "./agent.py:graph" + }, + "env": ".env" } - } - ``` + ``` -4. The [`langgraph.json`][langgraph.json] file is a configuration file that describes what graph(s) you are going to host. In this case we only have one graph to host: the compiled `graph` object from `agent.py`/`agent.ts`. + === "Javascript" -=== "Python" + ```json + { + "node_version": "20", + "dockerfile_lines": [], + "dependencies": ["."], + "graphs": { + "agent": "./src/agent.ts:graph" + }, + "env": ".env" + } + ``` - ```json - { - "dependencies": ["."], - "graphs": { - "agent": "./agent.py:graph" - }, - "env": ".env" - } - ``` + Learn more about the LangGraph CLI configuration file [here](./reference/cli.md#configuration-file). -=== "Javascript" +1. __Specify environment variables__ - ```json - { - "node_version": "20", - "dockerfile_lines": [], - "dependencies": ["."], - "graphs": { - "agent": "./src/agent.ts:graph" - }, - "env": ".env" - } - ``` + The `.env` file should have any environment variables needed to run your graph. This will only be used for local testing, so if you are not testing locally you can skip this step. -Learn more about the LangGraph CLI configuration file [here](./reference/cli.md#configuration-file). + !!! warning + The `.env` file should NOT be included with the rest of source code in your Github repository. When creating a deployment using LangGraph Cloud, you will be able to specify the environment variables manually. -5. The `.env` file should have any environment variables needed to run your graph. This will only be used for local testing, so if you are not testing locally you can skip this step. NOTE: if you do add this, you should NOT check this into git. For this graph, we need two environment variables: + For this graph, we need two environment variables: ```shell ANTHROPIC_API_KEY=... TAVILY_API_KEY=... ``` -Now that we have set everything up on our local file system, we are ready to host our graph. +!!! tip + Learn more about different application structure options [here](../how-tos/index.md#application-structure). -## Test the graph build locally +Now that we have set everything up on our local file system, we are ready to test our graph locally. -### Using LangGraph Studio Desktop (recommended) +## Test the app locally -![LangGraph Studio Desktop](./img/graph_video_poster.png) +To test the LangGraph app before deploying it using LangGraph Cloud, you can start the [LangGraph server](../concepts/langgraph_server.md) locally or use [LangGraph Studio](../concepts/langgraph_studio.md). -Testing your graph locally is easy with LangGraph Studio Desktop. LangGraph Studio offers a new way to develop LLM applications by providing a specialized agent IDE that enables visualization, interaction, and debugging of complex agentic applications +## Using local server -With visual graphs and the ability to edit state, you can better understand agent workflows and iterate faster. LangGraph Studio integrates with [LangSmith](https://smith.langchain.com) so you can collaborate with teammates to debug failure modes. +You can test your app by running [LangGraph server](../concepts/langgraph_server.md) locally. This is useful to make sure you have configured our [CLI configuration file][langgraph.json] correctly and can interact with your graph. -### Using the LangGraph CLI - -Before deploying to the cloud, we probably want to test the building of our graph locally. This is useful to make sure we have configured our [CLI configuration file][langgraph.json] correctly and our graph runs. - -In order to do this we can first install the LangGraph CLI +To run the server locally, you need to first install the LangGraph CLI: ```shell pip install langgraph-cli ``` -We can then test our API server locally. This requires access to LangGraph closed beta. In order to run the server locally, you will need to add your `LANGSMITH_API_KEY` to the .env file so we can validate you have access to LangGraph closed beta. +You can then test our API server locally. In order to run the server locally, you will need to add your `LANGSMITH_API_KEY` to the `.env` file. ```shell langgraph up @@ -160,10 +199,21 @@ This will start up the LangGraph API server locally. If this runs successfully, ```shell Ready! - API: http://localhost:8123 -2024-06-26 19:20:41,056:INFO:uvicorn.access 127.0.0.1:44138 - "GET /ok HTTP/1.1" 200 ``` -You can now test this out! **Note: this local server is intended SOLELY for local testing purposes and is not performant enough for production applications, so please do not use it as such.** To test it out, you can go to another terminal window and run: +First, let's verify that the server is running correctly by calling `/ok` endpoint: + +```shell +curl --request GET --url http://localhost:8123/ok +``` + +Output: + +``` +{"ok": "true"} +``` + +Now we're ready to test the app with the real inputs! ```shell curl --request POST \ @@ -175,36 +225,57 @@ curl --request POST \ "messages": [ { "role": "user", - "content": "How are you?" + "content": "What is the weather in NYC?" } ] }, - "metadata": {}, - "config": { - "configurable": {} - }, - "multitask_strategy": "reject", - "stream_mode": [ - "values" - ] + "stream_mode": "updates" }' ``` -If you get back a valid response, then all is functioning properly! +Output: -## Deploy to Cloud +``` +... -### Push your code to GitHub +data: { + "agent": { + "messages": [ + { + "content": "The search results from Tavily provide the current weather conditions in New York City, including temperature, wind speed, precipitation, humidity, and cloud cover. According to the results, as of 3:00pm on October 30th, 2024, it is overcast in NYC with a temperature of around 66°F (19°C), light winds from the southwest around 8 mph (13 km/h), and 66% humidity.\n\nSo in summary, the current weather in NYC is overcast with mild temperatures in the mid 60sF and light winds, based on the search results. Let me know if you need any other details!", + "type": "ai", + ... + } + ] + } +} +``` -Turn the `` directory into a GitHub repo. You can use the GitHub CLI if you like, or just create a repo manually (if unfamiliar, instructions [here](https://docs.github.com/en/migrations/importing-source-code/using-the-command-line-to-import-source-code/adding-locally-hosted-code-to-github)). +You can see that our agent responds with the up-to-date search results! -### Deploy from GitHub with LangGraph Cloud +### Using LangGraph Studio Desktop -Once you have created your github repository with a Python file containing your compiled graph as well as a `langgraph.json` file containing the configuration for hosting your graph, you can head over to LangSmith and click on the 🚀 icon on the left navbar to create a new deployment. Then click the `+ New Deployment` button. +You can also test your app locally with [LangGraph Studio](../concepts/langgraph_studio.md). LangGraph Studio offers a new way to develop LLM applications by providing a specialized agent IDE that enables visualization, interaction, and debugging of complex agentic applications. -![Langsmith Workflow](./img/cloud_deployment.png) +With visual graphs and the ability to edit state, you can better understand agent workflows and iterate faster. LangGraph Studio integrates with LangSmith allowing you to collaborate with teammates to debug failure modes. -**_If you have not deployed to LangGraph Cloud before:_** there will be a button that shows up saying Import from GitHub. You’ll need to follow that flow to connect LangGraph Cloud to GitHub. +LangGraph Studio is available as a [desktop app](https://studio.langchain.com/) for MacOS users. Once you have installed the app, you can select `my-app` directory, which will automatically start the server locally and load the graph in the UI. + +To interact with your chatbot agent in LangGraph Studio, you can add a new message in the `Input` section and press `Submit`. + +![LangGraph Studio Desktop](./deployment/img/quick_start_studio.png) + +## Deploy to LangGraph Cloud + +Once you've tested your graph locally and verified that it works as expected, you can deploy it to the LangGraph Cloud. + +First, you'll need to turn the `my-app` directory into a GitHub repo and [push it to GitHub](https://docs.github.com/en/migrations/importing-source-code/using-the-command-line-to-import-source-code/adding-locally-hosted-code-to-github). + +Once you have created your GitHub repository with a Python file containing your compiled graph as well as a `langgraph.json` with the configuration, you can head over to [LangSmith](https://smith.langchain.com/) and click on the graph icon (`LangGraph Cloud`) on the bottom of the left navbar. This will open the LangGraph deployments page. On this page, click the `+ New Deployment` button in the top right corner. + +![Langsmith Workflow](./deployment/img/cloud_deployment.png) + +**_If you have not deployed to LangGraph Cloud before:_** there will be a button that shows up saying `Import from GitHub`. You’ll need to follow that flow to connect LangGraph Cloud to GitHub. **_Once you have set up your GitHub connection:_** the new deployment page will look as follows: @@ -213,53 +284,43 @@ Once you have created your github repository with a Python file containing your To deploy your application, you should do the following: 1. Select your GitHub username or organization from the selector -2. Search for your repo to deploy in the search bar and select it -3. Choose any name -4. In the `LangGraph API config file` field, enter the path to your `langgraph.json` file (which in this case is just `langgraph.json`) -5. For Git Reference, you can select either the git branch for the code you want to deploy, or the exact commit SHA. -6. If your chain relies on environment variables, add those in. They will be propagated to the underlying server so your code can access them. In this case, we need `ANTHROPIC_API_KEY` and `TAVILY_API_KEY`. - -Putting this all together, you should have something as follows for your deployment details: - -![Deployment filled out](./deployment/img/deploy_filled_out.png) +1. Search for your repo to deploy in the search bar and select it +1. Choose a name for your deployment +1. In the `Git Branch` field, you can specify either the branch for the code you want to deploy, or the exact commit SHA. +1. In the `LangGraph API config file` field, enter the path to your `langgraph.json` file (which in this case is just `langgraph.json`) +1. If your application needs environment variables, add those in the `Environment Variables` section. They will be propagated to the underlying server so your code can access them. In this case, we will need `ANTHROPIC_API_KEY` and `TAVILY_API_KEY`. Hit `Submit` and your application will start deploying! -## Inspect Traces + Monitor Service - -### Deployments View - After your deployment is complete, your deployments page should look as follows: ![Deployed page](./deployment/img/deployed_page.png) -You can see that by default, you get access to the `Trace Count` monitoring chart and `Recent Traces` run view. These are powered by LangSmith. +## Interact with your deployment -You can click on `All Charts` to view all monitoring info for your server, or click on `See tracing project` to get more information on an individual trace. +### Using LangGraph Studio (Cloud) -### Access the Docs - -You can access the docs by clicking on the API docs link, which should send you to a page that looks like this: - -![API Docs page](./deployment/img/api_page.png) - -You won’t actually be able to test any of the API endpoints without authorizing first. To do so, grab your Langsmith API key and add it at the top where it says `API KEY (X-API-KEY)`. You should now be able to select any of the API endpoints, click `Test Request`, enter the parameters you would like to pass, and then click `Send` to view the results of the API call. - -## Interact with your deployment via LangGraph Studio - -If you click on your deployment you should see a blue button in the top right that says `LangGraph Studio`. Clicking on this button will take you to a page that looks like this: - -![Studio UI before being run](./deployment/img/graph_visualization.png) - -On this page you can test out your graph by passing in starting states and clicking `Start Run` (this should behave identically to calling `.invoke`). You will then be able to look into the execution thread for each run and explore the steps your graph is taking to produce its output. +On the deployment page for your application,, you should see a button in the top right corner that says `LangGraph Studio`. Clicking on this button will take you to the web version of LangGraph Studio. This is the same UI that you interacted with when [testing the app locally](#using-langgraph-studio-recommended), but instead of using a local LangGraph server, it uses the one from your LangGraph Cloud deployment. ![Studio UI once being run](./deployment/img/graph_run.png) -## Use with the SDK +### Using LangGraph SDK -Once you have tested that your hosted graph works as expected using LangGraph Studio, you can start using your hosted graph all over your organization by using the LangGraph SDK. Let's see how we can access our hosted graph and execute our run from a python file. +You can also interact with your deployed LangGraph application programmatically, using [LangGraph SDK](./reference/sdk/python_sdk_ref.md). -First, make sure you have the SDK installed by calling `pip install langgraph_sdk`. +First, make sure you have the SDK installed: + +=== "Python" + + ```shell + pip install langgraph_sdk + ``` + +=== "Javascript" + + ```shell + yarn add @langchain/langgraph-sdk + ``` Before using, you need to get the URL of your LangGraph deployment. You can find this in the `Deployment` view. Click the URL to copy it to the clipboard. @@ -278,8 +339,8 @@ The first thing to do when using the SDK is to setup our client, access our assi client = get_client(url=) # get default assistant - assistants = await client.assistants.search() - assistant = [a for a in assistants if not a["config"]][0] + assistants = await client.assistants.search(metadata={"created_by": "system"}) + assistant = assistants[0] # create thread thread = await client.threads.create() print(thread) @@ -292,8 +353,8 @@ The first thing to do when using the SDK is to setup our client, access our assi const client = new Client({ apiUrl: }); // get default assistant - const assistants = await client.assistants.search(); - const assistant = assistants.find(a => !a.config); + const assistants = await client.assistants.search({ metadata: {"created_by": "system"} }) + const assistant = assistants[0]; // create thread const thread = await client.threads.create(); console.log(thread) @@ -307,8 +368,9 @@ The first thing to do when using the SDK is to setup our client, access our assi --header 'Content-Type: application/json' \ --data '{ "limit": 10, - "offset": 0 - }' | jq -c 'map(select(.config == null or .config == {})) | .[0]' && \ + "offset": 0, + "metadata": {"created_by": "system"} + }' && curl --request POST \ --url /threads \ --header 'Content-Type: application/json' \ @@ -320,32 +382,35 @@ We can then execute a run on the thread: === "Python" ```python - input = {"messages":[{"role": "user", "content": "Hello! My name is Bagatur and I am 26 years old."}]} + input = { + "messages": [{"role": "user", "content": "What is the weather in NYC?"}] + } async for chunk in client.runs.stream( - thread['thread_id'], - assistant["assistant_id"], - input=input, - stream_mode="updates", - ): - if chunk.data and chunk.event != "metadata": + thread["thread_id"], + assistant["assistant_id"], + input=input, + stream_mode="updates", + ): + if chunk.data: print(chunk.data) ``` === "Javascript" ```js - const input = { "messages":[{ "role": "user", "content": "Hello! My name is Bagatur and I am 26 years old." }] }; + const input = { "messages": [{ "role": "user", "content": "What is the weather in NYC?" }] }; const streamResponse = client.runs.stream( thread["thread_id"], assistant["assistant_id"], { input, + streamMode: "updates" } ); for await (const chunk of streamResponse) { - if (chunk.data && chunk.event !== "metadata" ) { + if (chunk.data) { console.log(chunk.data); } } @@ -357,43 +422,41 @@ We can then execute a run on the thread: curl --request POST \ --url /threads//runs/stream \ --header 'Content-Type: application/json' \ - --data "{ - \"assistant_id\": , - \"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"Hello! My name is Bagatur and I am 26 years old.\"}]}, - }" | sed 's/\r$//' | awk ' - /^event:/ { event = $2 } - /^data:/ { - json_data = substr($0, index($0, $2)) - - if (event != "metadata") { - print json_data - } + --data '{ + "assistant_id": , + "input": { + "messages": [ + { + "role": "user", + "content": "What is the weather in NYC?" + } + ] + }, + "stream_mode": "updates" }' ``` - Output: - {'agent': {'messages': [{'content': "Hi Bagatur! It's nice to meet you. How can I assist you today?", 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_9cb5d38cf7'}, 'type': 'ai', 'name': None, 'id': 'run-c89118b7-1b1e-42b9-a85d-c43fe99881cd', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}} +``` +... -## What's Next +data: { + "agent": { + "messages": [ + { + "content": "The search results from Tavily provide the current weather conditions in New York City, including temperature, wind speed, precipitation, humidity, and cloud cover. According to the results, as of 3:00pm on October 30th, 2024, it is overcast in NYC with a temperature of around 66°F (19°C), light winds from the southwest around 8 mph (13 km/h), and 66% humidity.\n\nSo in summary, the current weather in NYC is overcast with mild temperatures in the mid 60sF and light winds, based on the search results. Let me know if you need any other details!", + "type": "ai", + ... + } + ] + } +} +``` + +## Next steps Congratulations! If you've worked your way through this tutorial you are well on your way to becoming a LangGraph Cloud expert. Here are some other resources to check out to help you out on the path to expertise: -### LangGraph Cloud How-tos - -If you want to learn more about streaming from hosted graphs, check out the Streaming [how-to guides](how-tos/index.md#streaming). - -To learn more about double-texting and all the ways you can handle it in your application, read up on these [how-to guides](how-tos/index.md#double-texting). - -To learn about how to include different human-in-the-loop behavior in your graph, take a look at [these how-tos](how-tos/index.md#human-in-the-loop). - -### LangGraph Tutorials - -Before hosting, you have to write a graph to host. Here are some tutorials to get you more comfortable with writing LangGraph graphs and give you inspiration for the types of graphs you want to host. - -[This tutorial](../tutorials/customer-support/customer-support.ipynb) walks you through how to write a customer support bot using LangGraph. - -If you are interested in writing a SQL agent, check out [this tutorial](../tutorials/sql-agent.ipynb). - -Check out the [LangGraph tutorials](../tutorials/index.md) page to read about more exciting use cases. +* [LangGraph How-to guides](../how-tos/index.md) +* [LangGraph Tutorials](../tutorials/index.md) \ No newline at end of file diff --git a/docs/docs/cloud/reference/api/openapi.json b/docs/docs/cloud/reference/api/openapi.json index aab93c6cd..c16f2488d 100644 --- a/docs/docs/cloud/reference/api/openapi.json +++ b/docs/docs/cloud/reference/api/openapi.json @@ -1,15 +1,43 @@ { "openapi": "3.1.0", "info": { - "title": "LangGraph", + "title": "LangGraph Platform", "version": "0.1.0" }, + "tags": [ + { + "name": "Assistants", + "description": "An assistant is a configured instance of a graph." + }, + { + "name": "Threads", + "description": "A thread contains the accumulated outputs of a group of runs." + }, + { + "name": "Thread Runs", + "description": "A run is an invocation of a graph / assistant on a thread. It updates the state of the thread." + }, + { + "name": "Stateless Runs", + "description": "A run is an invocation of a graph / assistant, with no state or memory persistence." + }, + { + "name": "Crons (Enterprise-only)", + "description": "A cron is a periodic run that recurs on a given schedule. The repeats can be isolated, or share state in a thread" + }, + { + "name": "Store", + "description": "Store is an API for managing persistent key-value store (long-term memory) that is available from any thread." + } + ], "paths": { "/assistants": { "post": { - "tags": ["assistants/create"], + "tags": [ + "Assistants" + ], "summary": "Create Assistant", - "description": "Create an assistant.", + "description": "Create an assistant.\n\nAn initial version of the assistant will be created and the assistant is set to that version. To change versions, use the `POST /assistants/{assistant_id}/latest` endpoint.", "operationId": "create_assistant_assistants_post", "requestBody": { "content": { @@ -23,7 +51,7 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -32,12 +60,32 @@ } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "Conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -47,9 +95,11 @@ }, "/assistants/search": { "post": { - "tags": ["assistants/manage"], + "tags": [ + "Assistants" + ], "summary": "Search Assistants", - "description": "List assistants.", + "description": "Search for assistants.\n\nThis endpoint also functions as the endpoint to list all assistants.", "operationId": "search_assistants_assistants_search_post", "requestBody": { "content": { @@ -63,7 +113,7 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -76,12 +126,22 @@ } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -91,7 +151,9 @@ }, "/assistants/{assistant_id}": { "get": { - "tags": ["assistants/manage"], + "tags": [ + "Assistants" + ], "summary": "Get Assistant", "description": "Get an assistant by ID.", "operationId": "get_assistant_assistants__assistant_id__get", @@ -102,7 +164,7 @@ "schema": { "type": "string", "format": "uuid", - "title": "Assistant Id", + "title": "Assistant ID", "description": "The ID of the assistant." }, "name": "assistant_id", @@ -111,7 +173,7 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -120,12 +182,12 @@ } } }, - "422": { - "description": "Validation Error", + "404": { + "description": "Not Found", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -133,9 +195,11 @@ } }, "delete": { - "tags": ["assistants/manage"], + "tags": [ + "Assistants" + ], "summary": "Delete Assistant", - "description": "Delete an assistant by ID.", + "description": "Delete an assistant by ID.\n\nAll versions of the assistant will be deleted as well.", "operationId": "delete_assistant_assistants__assistant_id__delete", "parameters": [ { @@ -144,7 +208,7 @@ "schema": { "type": "string", "format": "uuid", - "title": "Assistant Id", + "title": "Assistant ID", "description": "The ID of the assistant." }, "name": "assistant_id", @@ -153,19 +217,29 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": {} } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -173,7 +247,9 @@ } }, "patch": { - "tags": ["assistants/manage"], + "tags": [ + "Assistants" + ], "summary": "Patch Assistant", "description": "Update an assistant.", "operationId": "patch_assistant_assistants__assistant_id__patch", @@ -184,7 +260,7 @@ "schema": { "type": "string", "format": "uuid", - "title": "Assistant Id", + "title": "Assistant ID", "description": "The ID of the assistant." }, "name": "assistant_id", @@ -203,7 +279,7 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -212,12 +288,22 @@ } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -227,7 +313,9 @@ }, "/assistants/{assistant_id}/graph": { "get": { - "tags": ["assistants/manage"], + "tags": [ + "Assistants" + ], "summary": "Get Assistant Graph", "description": "Get an assistant by ID.", "operationId": "get_assistant_graph_assistants__assistant_id__graph_get", @@ -236,10 +324,19 @@ "description": "The ID of the assistant.", "required": true, "schema": { - "type": "string", - "format": "uuid", - "title": "Assistant Id", - "description": "The ID of the assistant." + "anyOf": [ + { + "type": "string", + "format": "uuid", + "title": "Assistant ID", + "description": "The ID of the assistant." + }, + { + "type": "string", + "title": "Graph ID", + "description": "The ID of the graph." + } + ] }, "name": "assistant_id", "in": "path" @@ -248,7 +345,14 @@ "description": "Include graph representation of subgraphs. If an integer value is provided, only subgraphs with a depth less than or equal to the value will be included.", "required": false, "schema": { - "oneOf": [{ "type": "boolean" }, { "type": "integer" }], + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "integer" + } + ], "title": "Xray", "default": false, "description": "Include graph representation of subgraphs. If an integer value is provided, only subgraphs with a depth less than or equal to the value will be included." @@ -259,7 +363,7 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -275,12 +379,22 @@ } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -290,7 +404,9 @@ }, "/assistants/{assistant_id}/subgraphs": { "get": { - "tags": ["assistants/manage"], + "tags": [ + "Assistants" + ], "summary": "Get Assistant Subgraphs", "description": "Get an assistant's subgraphs.", "operationId": "get_assistant_subgraphs_assistants__assistant_id__subgraphs_get", @@ -320,7 +436,7 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -329,12 +445,22 @@ } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -344,7 +470,9 @@ }, "/assistants/{assistant_id}/subgraphs/{namespace}": { "get": { - "tags": ["assistants/manage"], + "tags": [ + "Assistants" + ], "summary": "Get Assistant Subgraphs by Namespace", "description": "Get an assistant's subgraphs filtered by namespace.", "operationId": "get_assistant_subgraphs_assistants__assistant_id__subgraphs__namespace__get", @@ -384,7 +512,7 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -398,7 +526,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -408,7 +536,9 @@ }, "/assistants/{assistant_id}/schemas": { "get": { - "tags": ["assistants/manage"], + "tags": [ + "Assistants" + ], "summary": "Get Assistant Schemas", "description": "Get an assistant by ID.", "operationId": "get_assistant_schemas_assistants__assistant_id__schemas_get", @@ -428,7 +558,7 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -437,12 +567,22 @@ } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -452,7 +592,9 @@ }, "/assistants/{assistant_id}/versions": { "post": { - "tags": ["assistants/manage"], + "tags": [ + "Assistants" + ], "summary": "Get Assistant Versions", "description": "Get all versions of an assistant.", "operationId": "get_assistant_versions_assistants__assistant_id__versions_get", @@ -472,7 +614,7 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -490,7 +632,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -498,12 +640,14 @@ } } }, - "/assistants/{assistant_id}/change_version": { + "/assistants/{assistant_id}/latest": { "post": { - "tags": ["assistants/manage"], - "summary": "Change Assistant Version", - "description": "Change the version of an assistant.", - "operationId": "change_assistant_version__assistant_id__change_version__version_post", + "tags": [ + "Assistants" + ], + "summary": "Set Latest Assistant Version", + "description": "Set the latest version for an assistant.", + "operationId": "set_latest_assistant_version_assistants__assistant_id__versions_post", "parameters": [ { "description": "The ID of the assistant.", @@ -531,7 +675,7 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -540,12 +684,22 @@ } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -555,7 +709,9 @@ }, "/threads": { "post": { - "tags": ["threads/create"], + "tags": [ + "Threads" + ], "summary": "Create Thread", "description": "Create a thread.", "operationId": "create_thread_threads_post", @@ -571,7 +727,7 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -580,12 +736,22 @@ } } }, + "409": { + "description": "Conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -595,9 +761,11 @@ }, "/threads/search": { "post": { - "tags": ["threads/manage"], + "tags": [ + "Threads" + ], "summary": "Search Threads", - "description": "List threads.", + "description": "Search for threads.\n\nThis endpoint also functions as the endpoint to list all threads.", "operationId": "search_threads_threads_search_post", "requestBody": { "content": { @@ -611,7 +779,7 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -629,7 +797,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -639,9 +807,11 @@ }, "/threads/{thread_id}/state": { "get": { - "tags": ["threads/state"], - "summary": "Get Latest Thread State", - "description": "Get state for a thread.", + "tags": [ + "Threads" + ], + "summary": "Get Thread State", + "description": "Get state for a thread.\n\nThe latest state of the thread (i.e. latest checkpoint) is returned.", "operationId": "get_latest_thread_state_threads__thread_id__state_get", "parameters": [ { @@ -659,7 +829,7 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -673,7 +843,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -681,7 +851,9 @@ } }, "post": { - "tags": ["threads/state"], + "tags": [ + "Threads" + ], "summary": "Update Thread State", "description": "Add state to a thread.", "operationId": "update_thread_state_threads__thread_id__state_post", @@ -711,7 +883,7 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -725,7 +897,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -735,7 +907,9 @@ }, "/threads/{thread_id}/state/checkpoint": { "post": { - "tags": ["threads/state"], + "tags": [ + "Threads" + ], "summary": "Get Thread State At Checkpoint", "description": "Get state for a thread at a specific checkpoint.", "operationId": "post_thread_state_at_checkpoint_threads__thread_id__state__checkpoint_id__get", @@ -751,7 +925,7 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -765,7 +939,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -775,7 +949,9 @@ }, "/threads/{thread_id}/history": { "get": { - "tags": ["threads/state"], + "tags": [ + "Threads" + ], "summary": "Get Thread History", "description": "Get all past states for a thread.", "operationId": "get_thread_history_threads__thread_id__history_get", @@ -814,7 +990,7 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -832,7 +1008,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -840,7 +1016,9 @@ } }, "post": { - "tags": ["threads/state"], + "tags": [ + "Threads" + ], "summary": "Get Thread History Post", "description": "Get all past states for a thread.", "operationId": "get_thread_history_post_threads__thread_id__history_post", @@ -870,7 +1048,7 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -888,7 +1066,63 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/threads/{thread_id}/copy": { + "post": { + "tags": [ + "Threads" + ], + "summary": "Copy Thread", + "description": "Create a new thread with a copy of the state and checkpoints from an existing thread.", + "operationId": "copy_thread_post_threads__thread_id__copy_post", + "parameters": [ + { + "description": "The ID of the thread.", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Thread Id", + "description": "The ID of the thread." + }, + "name": "thread_id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Thread" + } + } + } + }, + "409": { + "description": "Conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -898,7 +1132,9 @@ }, "/threads/{thread_id}": { "get": { - "tags": ["threads/manage"], + "tags": [ + "Threads" + ], "summary": "Get Thread", "description": "Get a thread by ID.", "operationId": "get_thread_threads__thread_id__get", @@ -918,7 +1154,7 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -927,12 +1163,22 @@ } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -940,7 +1186,9 @@ } }, "delete": { - "tags": ["threads/manage"], + "tags": [ + "Threads" + ], "summary": "Delete Thread", "description": "Delete a thread by ID.", "operationId": "delete_thread_threads__thread_id__delete", @@ -960,19 +1208,29 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": {} } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -980,7 +1238,9 @@ } }, "patch": { - "tags": ["threads/manage"], + "tags": [ + "Threads" + ], "summary": "Patch Thread", "description": "Update a thread.", "operationId": "patch_thread_threads__thread_id__patch", @@ -1010,7 +1270,7 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -1019,12 +1279,22 @@ } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -1034,7 +1304,9 @@ }, "/threads/{thread_id}/runs": { "get": { - "tags": ["runs/manage"], + "tags": [ + "Thread Runs" + ], "summary": "List Runs", "description": "List runs for a thread.", "operationId": "list_runs_http_threads__thread_id__runs_get", @@ -1074,7 +1346,7 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -1086,12 +1358,22 @@ } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -1099,9 +1381,11 @@ } }, "post": { - "tags": ["runs/create"], + "tags": [ + "Thread Runs" + ], "summary": "Create Background Run", - "description": "Create a run, return immediately.", + "description": "Create a run in existing thread, return the run ID immediately. Don't wait for the final run output.", "operationId": "create_run_threads__thread_id__runs_post", "parameters": [ { @@ -1129,7 +1413,7 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -1138,12 +1422,32 @@ } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "Conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -1153,7 +1457,9 @@ }, "/threads/{thread_id}/runs/crons": { "post": { - "tags": ["runs/create"], + "tags": [ + "Crons (Enterprise-only)" + ], "summary": "Create Thread Cron", "description": "Create a cron to schedule runs on a thread.", "operationId": "create_thread_cron_threads__thread_id__runs_crons_post", @@ -1183,7 +1489,7 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -1192,12 +1498,22 @@ } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -1207,9 +1523,11 @@ }, "/threads/{thread_id}/runs/stream": { "post": { - "tags": ["runs/create"], - "summary": "Create Streaming Run", - "description": "Create a run, stream the output.", + "tags": [ + "Thread Runs" + ], + "summary": "Create Run, Stream Output", + "description": "Create a run in existing thread. Stream the output.", "operationId": "stream_run_threads__thread_id__runs_stream_post", "parameters": [ { @@ -1237,19 +1555,39 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": {} } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "Conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -1259,9 +1597,11 @@ }, "/threads/{thread_id}/runs/wait": { "post": { - "tags": ["runs/create"], - "summary": "Create Run and Get Output", - "description": "Create a run, return the final output.", + "tags": [ + "Thread Runs" + ], + "summary": "Create Run, Wait for Output", + "description": "Create a run in existing thread. Wait for the final output and then return it.", "operationId": "wait_run_threads__thread_id__runs_wait_post", "parameters": [ { @@ -1289,19 +1629,39 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": {} } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "Conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -1311,7 +1671,9 @@ }, "/threads/{thread_id}/runs/{run_id}": { "get": { - "tags": ["runs/manage"], + "tags": [ + "Thread Runs" + ], "summary": "Get Run", "description": "Get a run by ID.", "operationId": "get_run_http_threads__thread_id__runs__run_id__get", @@ -1343,7 +1705,7 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -1352,12 +1714,22 @@ } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -1365,7 +1737,9 @@ } }, "delete": { - "tags": ["runs/manage"], + "tags": [ + "Thread Runs" + ], "summary": "Delete Run", "description": "Delete a run by ID.", "operationId": "delete_run_threads__thread_id__runs__run_id__delete", @@ -1397,19 +1771,29 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": {} } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -1419,7 +1803,9 @@ }, "/threads/{thread_id}/runs/{run_id}/join": { "get": { - "tags": ["runs/manage"], + "tags": [ + "Thread Runs" + ], "summary": "Join Run", "description": "Wait for a run to finish.", "operationId": "join_run_http_threads__thread_id__runs__run_id__join_get", @@ -1451,19 +1837,29 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": {} } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -1473,7 +1869,9 @@ }, "/threads/{thread_id}/runs/{run_id}/stream": { "get": { - "tags": ["runs/manage"], + "tags": [ + "Thread Runs" + ], "summary": "Join Run Stream", "description": "Join a run stream. This endpoint streams output in real-time from a run similar to the /threads/__THREAD_ID__/runs/stream endpoint. Only output produced after this endpoint is called will be streamed.", "operationId": "stream_run_http_threads__thread_id__runs__run_id__join_get", @@ -1505,19 +1903,29 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": {} } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -1527,7 +1935,9 @@ }, "/threads/{thread_id}/runs/{run_id}/cancel": { "post": { - "tags": ["runs/manage"], + "tags": [ + "Thread Runs" + ], "summary": "Cancel Run", "operationId": "cancel_run_http_threads__thread_id__runs__run_id__cancel_post", "parameters": [ @@ -1564,23 +1974,48 @@ }, "name": "wait", "in": "query" + }, + { + "description": "Action to take when cancelling the run. Possible values are `interrupt` or `rollback`. `interrupt` will simply cancel the run. `rollback` will cancel the run and delete the run and associated checkpoints afterwards.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "interrupt", + "rollback" + ], + "title": "Action", + "default": "interrupt" + }, + "name": "action", + "in": "query" } ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": {} } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -1590,7 +2025,9 @@ }, "/runs/crons": { "post": { - "tags": ["runs/create"], + "tags": [ + "Crons (Enterprise-only)" + ], "summary": "Create Cron", "description": "Create a cron to schedule runs on new threads.", "operationId": "create_cron_runs_crons_post", @@ -1606,7 +2043,7 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -1615,12 +2052,22 @@ } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -1630,7 +2077,9 @@ }, "/runs/crons/search": { "post": { - "tags": ["crons/search"], + "tags": [ + "Crons (Enterprise-only)" + ], "summary": "Search Crons", "description": "Search all active crons", "operationId": "search_crons_runs_crons_post", @@ -1646,7 +2095,7 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -1664,7 +2113,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -1674,8 +2123,10 @@ }, "/runs/stream": { "post": { - "tags": ["runs/create"], - "summary": "Stream Run in new Thread", + "tags": [ + "Stateless Runs" + ], + "summary": "Create Run, Stream Output", "description": "Create a run in a new thread, stream the output.", "operationId": "stream_run_stateless_runs_stream_post", "requestBody": { @@ -1690,19 +2141,39 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": {} } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "Conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -1712,9 +2183,11 @@ }, "/runs/wait": { "post": { - "tags": ["runs/create"], - "summary": "Create Run in new Thread and Get Output", - "description": "Create a run in a new thread, return the final output.", + "tags": [ + "Stateless Runs" + ], + "summary": "Create Run, Wait for Output", + "description": "Create a run in a new thread. Wait for the final output and then return it.", "operationId": "wait_run_stateless_runs_wait_post", "requestBody": { "content": { @@ -1728,19 +2201,39 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": {} } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "Conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -1750,9 +2243,11 @@ }, "/runs": { "post": { - "tags": ["runs/create"], - "summary": "Create Background Run in new Thread", - "description": "Create a run in a new thread, return immediately.", + "tags": [ + "Stateless Runs" + ], + "summary": "Create Background Run", + "description": "Create a run in a new thread, return the run ID immediately. Don't wait for the final run output.", "operationId": "run_stateless_runs_post", "requestBody": { "content": { @@ -1766,19 +2261,39 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": {} } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "Conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -1788,7 +2303,9 @@ }, "/runs/batch": { "post": { - "tags": ["runs/create"], + "tags": [ + "Stateless Runs" + ], "summary": "Create Run Batch", "description": "Create a batch of runs in new threads, return immediately.", "operationId": "run_batch_stateless_runs_post", @@ -1804,19 +2321,39 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": {} } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "409": { + "description": "Conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -1826,7 +2363,9 @@ }, "/runs/crons/{cron_id}": { "delete": { - "tags": ["runs/manage"], + "tags": [ + "Crons (Enterprise-only)" + ], "summary": "Delete Cron", "description": "Delete a cron by ID.", "operationId": "delete_cron_runs_crons__cron_id__delete", @@ -1844,19 +2383,29 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": {} } } }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/ErrorResponse" } } } @@ -1864,59 +2413,75 @@ } } }, - "/store/items": { "put": { + "tags": [ + "Store" + ], "summary": "Store or update an item.", "operationId": "put_item", "requestBody": { "required": true, "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/StorePutRequest" } + "schema": { + "$ref": "#/components/schemas/StorePutRequest" + } } } }, "responses": { "204": { - "description": "Successful Response" + "description": "Success" }, "422": { "description": "Validation Error", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } } } } } }, "delete": { + "tags": [ + "Store" + ], "summary": "Delete an item.", "operationId": "delete_item", "requestBody": { "required": true, "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/StoreDeleteRequest" } + "schema": { + "$ref": "#/components/schemas/StoreDeleteRequest" + } } } }, "responses": { "204": { - "description": "Successful Response" + "description": "Success" }, "422": { "description": "Validation Error", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } } } } } }, "get": { + "tags": [ + "Store" + ], "summary": "Retrieve a single item.", "operationId": "get_item", "parameters": [ @@ -1942,10 +2507,22 @@ ], "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/Item" } + "schema": { + "$ref": "#/components/schemas/Item" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } } } }, @@ -1953,7 +2530,9 @@ "description": "Validation Error", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } } } } @@ -1962,22 +2541,29 @@ }, "/store/items/search": { "post": { + "tags": [ + "Store" + ], "summary": "Search for items within a namespace prefix.", "operationId": "search_items", "requestBody": { "required": true, "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/StoreSearchRequest" } + "schema": { + "$ref": "#/components/schemas/StoreSearchRequest" + } } } }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/SearchItemsResponse" } + "schema": { + "$ref": "#/components/schemas/SearchItemsResponse" + } } } }, @@ -1985,7 +2571,9 @@ "description": "Validation Error", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } } } } @@ -1994,6 +2582,9 @@ }, "/store/namespaces": { "post": { + "tags": [ + "Store" + ], "summary": "List namespaces with optional match conditions.", "operationId": "list_namespaces", "requestBody": { @@ -2008,7 +2599,7 @@ }, "responses": { "200": { - "description": "Successful Response", + "description": "Success", "content": { "application/json": { "schema": { @@ -2021,7 +2612,9 @@ "description": "Validation Error", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } } } } @@ -2036,11 +2629,13 @@ "assistant_id": { "type": "string", "format": "uuid", - "title": "Assistant Id" + "title": "Assistant Id", + "description": "The ID of the assistant." }, "graph_id": { "type": "string", - "title": "Graph Id" + "title": "Graph Id", + "description": "The ID of the graph." }, "config": { "properties": { @@ -2061,29 +2656,35 @@ } }, "type": "object", - "title": "Config" + "title": "Config", + "description": "The assistant config." }, "created_at": { "type": "string", "format": "date-time", - "title": "Created At" + "title": "Created At", + "description": "The time the assistant was created." }, "updated_at": { "type": "string", "format": "date-time", - "title": "Updated At" + "title": "Updated At", + "description": "The last time the assistant was updated." }, "metadata": { "type": "object", - "title": "Metadata" + "title": "Metadata", + "description": "The assistant metadata." }, "version": { "type": "integer", - "title": "Version" + "title": "Version", + "description": "The version of the assistant" }, "name": { "type": "string", - "title": "Assistant Name" + "title": "Assistant Name", + "description": "The name of the assistant" } }, "type": "object", @@ -2103,26 +2704,43 @@ "type": "string", "format": "uuid", "title": "Assistant Id", - "description": "The ID of the assistant. If not provided, an ID is generated." + "description": "The ID of the assistant. If not provided, a random UUID will be generated." }, "graph_id": { "type": "string", "title": "Graph Id", - "description": "The graph to use." + "description": "The ID of the graph the assistant should use. The graph ID is normally set in your langgraph.json configuration." }, "config": { "type": "object", "title": "Config", - "description": "The assistant config." + "description": "Configuration to use for the graph. Useful when graph is configurable and you want to create different assistants based on different configurations." }, "metadata": { "type": "object", "title": "Metadata", - "description": "Metadata for the assistant." + "description": "Metadata to add to assistant." + }, + "if_exists": { + "type": "string", + "enum": [ + "raise", + "do_nothing" + ], + "title": "If Exists", + "description": "How to handle duplicate creation. Must be either 'raise' (raise error if duplicate), or 'do_nothing' (return existing assistant).", + "default": "raise" + }, + "name": { + "type": "string", + "title": "Name", + "description": "The name of the assistant. Defaults to 'Untitled'." } }, "type": "object", - "required": ["graph_id"], + "required": [ + "graph_id" + ], "title": "AssistantCreate", "description": "Payload for creating an assistant." }, @@ -2131,12 +2749,12 @@ "graph_id": { "type": "string", "title": "Graph Id", - "description": "The graph to use." + "description": "The ID of the graph the assistant should use. The graph ID is normally set in your langgraph.json configuration. If not provided, assistant will keep pointing to same graph." }, "config": { "type": "object", "title": "Config", - "description": "The assistant config." + "description": "Configuration to use for the graph. Useful when graph is configurable and you want to update the assistant's configuration." }, "metadata": { "type": "object", @@ -2145,8 +2763,8 @@ }, "name": { "type": "string", - "title": "Assistant Name", - "description": "The assistant name." + "title": "Name", + "description": "The new name for the assistant. If not provided, assistant will keep its current name." } }, "type": "object", @@ -2191,35 +2809,42 @@ "cron_id": { "type": "string", "format": "uuid", - "title": "Cron Id" + "title": "Cron Id", + "description": "The ID of the cron." }, "thread_id": { "type": "string", "format": "uuid", - "title": "Thread Id" + "title": "Thread Id", + "description": "The ID of the thread." }, "end_time": { "type": "string", "format": "date-time", - "title": "End Time" + "title": "End Time", + "description": "The end date to stop running the cron." }, "schedule": { "type": "string", - "title": "Schedule" + "title": "Schedule", + "description": "The schedule to run, cron format." }, "created_at": { "type": "string", "format": "date-time", - "title": "Created At" + "title": "Created At", + "description": "The time the cron was created." }, "updated_at": { "type": "string", "format": "date-time", - "title": "Updated At" + "title": "Updated At", + "description": "The last time the cron was updated." }, "payload": { "type": "object", - "title": "Payload" + "title": "Payload", + "description": "The run payload to use for creating new run." } }, "type": "object", @@ -2232,18 +2857,29 @@ "updated_at", "payload" ], - "title": "Cron" + "title": "Cron", + "description": "Represents a scheduled task." }, "CronCreate": { "properties": { - "assistant_id": { + "schedule": { "type": "string", - "format": "uuid", - "title": "Assistant Id" + "title": "Schedule", + "description": "The cron schedule to execute this job on." }, - "checkpoint_id": { - "type": "string", - "title": "Checkpoint Id" + "assistant_id": { + "anyOf": [ + { + "type": "string", + "format": "uuid", + "title": "Assistant Id" + }, + { + "type": "string", + "title": "Graph Id" + } + ], + "description": "The assistant ID or graph name to run. If using graph name, will default to the assistant automatically created from that graph by the server." }, "input": { "anyOf": [ @@ -2257,12 +2893,13 @@ "type": "object" } ], - "title": "Input" + "title": "Input", + "description": "The input to the graph." }, "metadata": { "type": "object", "title": "Metadata", - "description": "Metadata for the run." + "description": "Metadata to assign to the cron job runs." }, "config": { "properties": { @@ -2283,20 +2920,24 @@ } }, "type": "object", - "title": "Config" + "title": "Config", + "description": "The configuration for the assistant." }, "webhook": { "type": "string", "maxLength": 65536, "minLength": 1, "format": "uri", - "title": "Webhook" + "title": "Webhook", + "description": "Webhook to call after LangGraph API call is done." }, "interrupt_before": { "anyOf": [ { "type": "string", - "enum": ["*"] + "enum": [ + "*" + ] }, { "items": { @@ -2305,13 +2946,16 @@ "type": "array" } ], - "title": "Interrupt Before" + "title": "Interrupt Before", + "description": "Nodes to interrupt immediately before they get executed." }, "interrupt_after": { "anyOf": [ { "type": "string", - "enum": ["*"] + "enum": [ + "*" + ] }, { "items": { @@ -2320,36 +2964,48 @@ "type": "array" } ], - "title": "Interrupt After" + "title": "Interrupt After", + "description": "Nodes to interrupt immediately after they get executed." }, "multitask_strategy": { "type": "string", - "enum": ["reject", "rollback", "interrupt", "enqueue"], + "enum": [ + "reject", + "rollback", + "interrupt", + "enqueue" + ], "title": "Multitask Strategy", + "description": "Multitask strategy to use. Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'.", "default": "reject" } }, "type": "object", - "required": ["assistant_id", "schedule"], + "required": [ + "assistant_id", + "schedule" + ], "title": "CronCreate", - "description": "Payload for creating a cron." + "description": "Payload for creating a cron job." }, "CronSearch": { "properties": { "assistant_id": { "type": "string", "format": "uuid", - "title": "Assistant Id" + "title": "Assistant Id", + "description": "The assistant ID or graph name to search for." }, "thread_id": { "type": "string", "format": "uuid", - "title": "Thread Id" + "title": "Thread Id", + "description": "The thread ID to search for." }, "limit": { "type": "integer", "title": "Limit", - "description": "Maximum number to return.", + "description": "The maximum number of results to return.", "default": 10, "minimum": 1, "maximum": 1000 @@ -2357,7 +3013,7 @@ "offset": { "type": "integer", "title": "Offset", - "description": "Offset to start from.", + "description": "The number of results to skip.", "default": 0, "minimum": 0 } @@ -2371,51 +3027,71 @@ "properties": { "graph_id": { "type": "string", - "title": "Graph Id" + "title": "Graph Id", + "description": "The ID of the graph." }, "input_schema": { "type": "object", - "title": "Input Schema" + "title": "Input Schema", + "description": "The schema for the graph input. Missing if unable to generate JSON schema from graph." }, "output_schema": { "type": "object", - "title": "Input Schema" + "title": "Output Schema", + "description": "The schema for the graph output. Missing if unable to generate JSON schema from graph." }, "state_schema": { "type": "object", - "title": "State Schema" + "title": "State Schema", + "description": "The schema for the graph state. Missing if unable to generate JSON schema from graph." }, "config_schema": { "type": "object", - "title": "Config Schema" + "title": "Config Schema", + "description": "The schema for the graph config. Missing if unable to generate JSON schema from graph." } }, "type": "object", - "required": ["graph_id", "state_schema", "config_schema"], - "title": "GraphSchema" + "required": [ + "graph_id", + "state_schema", + "config_schema" + ], + "title": "GraphSchema", + "description": "Defines the structure and properties of a graph." }, "GraphSchemaNoId": { "properties": { "input_schema": { "type": "object", - "title": "Input Schema" + "title": "Input Schema", + "description": "The schema for the graph input. Missing if unable to generate JSON schema from graph." }, "output_schema": { "type": "object", - "title": "Input Schema" + "title": "Output Schema", + "description": "The schema for the graph output. Missing if unable to generate JSON schema from graph." }, "state_schema": { "type": "object", - "title": "State Schema" + "title": "State Schema", + "description": "The schema for the graph state. Missing if unable to generate JSON schema from graph." }, "config_schema": { "type": "object", - "title": "Config Schema" + "title": "Config Schema", + "description": "The schema for the graph config. Missing if unable to generate JSON schema from graph." } }, "type": "object", - "required": ["input_schema", "output_schema", "state_schema", "config_schema"], - "title": "GraphSchemaNoId" + "required": [ + "input_schema", + "output_schema", + "state_schema", + "config_schema" + ], + "title": "GraphSchemaNoId", + "description": "Defines the structure and properties of a graph without an ID." }, "Subgraphs": { "type": "object", @@ -2425,54 +3101,54 @@ "title": "Subgraphs", "description": "Map of graph name to graph schema metadata (`input_schema`, `output_schema`, `state_schema`, `config_schema`)." }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - "type": "array", - "title": "Detail" - } - }, - "type": "object", - "title": "HTTPValidationError" - }, "Run": { "properties": { "run_id": { "type": "string", "format": "uuid", - "title": "Run Id" + "title": "Run Id", + "description": "The ID of the run." }, "thread_id": { "type": "string", "format": "uuid", - "title": "Thread Id" + "title": "Thread Id", + "description": "The ID of the thread." }, "assistant_id": { "type": "string", "format": "uuid", - "title": "Assistant Id" + "title": "Assistant Id", + "description": "The assistant that was used for this run." }, "created_at": { "type": "string", "format": "date-time", - "title": "Created At" + "title": "Created At", + "description": "The time the run was created." }, "updated_at": { "type": "string", "format": "date-time", - "title": "Updated At" + "title": "Updated At", + "description": "The last time the run was updated." }, "status": { "type": "string", - "enum": ["pending", "error", "success", "timeout", "interrupted"], - "title": "Status" + "enum": [ + "pending", + "error", + "success", + "timeout", + "interrupted" + ], + "title": "Status", + "description": "The status of the run. One of 'pending', 'error', 'success', 'timeout', 'interrupted'." }, "metadata": { "type": "object", - "title": "Metadata" + "title": "Metadata", + "description": "The run metadata." }, "kwargs": { "type": "object", @@ -2480,8 +3156,14 @@ }, "multitask_strategy": { "type": "string", - "enum": ["reject", "rollback", "interrupt", "enqueue"], - "title": "Multitask Strategy" + "enum": [ + "reject", + "rollback", + "interrupt", + "enqueue" + ], + "title": "Multitask Strategy", + "description": "Strategy to handle concurrent runs on the same thread." } }, "type": "object", @@ -2498,6 +3180,66 @@ ], "title": "Run" }, + "Send": { + "type": "object", + "title": "Send", + "description": "A message to send to a node.", + "properties": { + "node": { + "type": "string", + "title": "Node", + "description": "The node to send the message to." + }, + "input": { + "type": "object", + "title": "Message", + "description": "The message to send." + } + }, + "required": [ + "node", + "input" + ] + }, + "Command": { + "type": "object", + "title": "Command", + "description": "The command to run.", + "properties": { + "update": { + "type": "object", + "title": "Update", + "description": "An update to the state." + }, + "resume": { + "type": [ + "object", + "array", + "number", + "string", + "null" + ], + "title": "Resume", + "description": "A value to pass to an interrupted node." + }, + "send": { + "anyOf": [ + { + "$ref": "#/components/schemas/Send" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/Send" + } + }, + { + "type": "null" + } + ] + } + } + }, "RunCreateStateful": { "properties": { "assistant_id": { @@ -2511,20 +3253,17 @@ "type": "string", "title": "Graph Id" } - ] + ], + "description": "The assistant ID or graph name to run. If using graph name, will default to first assistant created from that graph." }, - "checkpoint_id": { - "type": "string", - "title": "Checkpoint Id" + "checkpoint": { + "type": "object", + "title": "Checkpoint", + "description": "The checkpoint to resume from.", + "$ref": "#/components/schemas/CheckpointConfig" }, "input": { "anyOf": [ - { - "items": { - "type": "object" - }, - "type": "array" - }, { "type": "object" }, @@ -2532,12 +3271,25 @@ "type": "null" } ], - "title": "Input" + "title": "Input", + "description": "The input to the graph." + }, + "command": { + "anyOf": [ + { + "$ref": "#/components/schemas/Command" + }, + { + "type": "null" + } + ], + "title": "Input", + "description": "The input to the graph." }, "metadata": { "type": "object", "title": "Metadata", - "description": "Metadata for the run." + "description": "Metadata to assign to the run." }, "config": { "properties": { @@ -2558,20 +3310,24 @@ } }, "type": "object", - "title": "Config" + "title": "Config", + "description": "The configuration for the assistant." }, "webhook": { "type": "string", "maxLength": 65536, "minLength": 1, "format": "uri", - "title": "Webhook" + "title": "Webhook", + "description": "Webhook to call after LangGraph API call is done." }, "interrupt_before": { "anyOf": [ { "type": "string", - "enum": ["*"] + "enum": [ + "*" + ] }, { "items": { @@ -2580,13 +3336,16 @@ "type": "array" } ], - "title": "Interrupt Before" + "title": "Interrupt Before", + "description": "Nodes to interrupt immediately before they get executed." }, "interrupt_after": { "anyOf": [ { "type": "string", - "enum": ["*"] + "enum": [ + "*" + ] }, { "items": { @@ -2595,7 +3354,8 @@ "type": "array" } ], - "title": "Interrupt After" + "title": "Interrupt After", + "description": "Nodes to interrupt immediately after they get executed." }, "stream_mode": { "anyOf": [ @@ -2605,6 +3365,7 @@ "enum": [ "values", "messages", + "messages-tuple", "updates", "events", "debug", @@ -2618,6 +3379,7 @@ "enum": [ "values", "messages", + "messages-tuple", "updates", "events", "debug", @@ -2626,12 +3388,25 @@ } ], "title": "Stream Mode", - "default": ["values"] + "description": "The stream mode(s) to use.", + "default": [ + "values" + ] + }, + "stream_subgraphs": { + "type": "boolean", + "title": "Stream Subgraphs", + "description": "Whether to stream output from subgraphs.", + "default": false }, "on_disconnect": { "type": "string", - "enum": ["cancel", "continue"], + "enum": [ + "cancel", + "continue" + ], "title": "On Disconnect", + "description": "The disconnect mode to use. Must be one of 'cancel' or 'continue'.", "default": "cancel" }, "feedback_keys": { @@ -2639,22 +3414,41 @@ "type": "string" }, "type": "array", - "title": "Feedback Keys" + "title": "Feedback Keys", + "description": "Feedback keys to assign to run." }, "multitask_strategy": { "type": "string", - "enum": ["reject", "rollback", "interrupt", "enqueue"], + "enum": [ + "reject", + "rollback", + "interrupt", + "enqueue" + ], "title": "Multitask Strategy", + "description": "Multitask strategy to use. Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'.", + "default": "reject" + }, + "if_not_exists": { + "type": "string", + "enum": [ + "create", + "reject" + ], + "title": "If Not Exists", + "description": "How to handle missing thread. Must be either 'reject' (raise error if missing), or 'create' (create new thread).", "default": "reject" }, "after_seconds": { "type": "integer", "title": "After Seconds", - "description": "Number of seconds to wait before starting the run." + "description": "The number of seconds to wait before starting the run. Use to schedule future runs." } }, "type": "object", - "required": ["assistant_id"], + "required": [ + "assistant_id" + ], "title": "RunCreateStateful", "description": "Payload for creating a run." }, @@ -2680,16 +3474,11 @@ "type": "string", "title": "Graph Id" } - ] + ], + "description": "The assistant ID or graph name to run. If using graph name, will default to first assistant created from that graph." }, "input": { "anyOf": [ - { - "items": { - "type": "object" - }, - "type": "array" - }, { "type": "object" }, @@ -2697,12 +3486,25 @@ "type": "null" } ], - "title": "Input" + "title": "Input", + "description": "The input to the graph." + }, + "command": { + "anyOf": [ + { + "$ref": "#/components/schemas/Command" + }, + { + "type": "null" + } + ], + "title": "Input", + "description": "The input to the graph." }, "metadata": { "type": "object", "title": "Metadata", - "description": "Metadata for the run." + "description": "Metadata to assign to the run." }, "config": { "properties": { @@ -2723,20 +3525,24 @@ } }, "type": "object", - "title": "Config" + "title": "Config", + "description": "The configuration for the assistant." }, "webhook": { "type": "string", "maxLength": 65536, "minLength": 1, "format": "uri", - "title": "Webhook" + "title": "Webhook", + "description": "Webhook to call after LangGraph API call is done." }, "interrupt_before": { "anyOf": [ { "type": "string", - "enum": ["*"] + "enum": [ + "*" + ] }, { "items": { @@ -2745,13 +3551,16 @@ "type": "array" } ], - "title": "Interrupt Before" + "title": "Interrupt Before", + "description": "Nodes to interrupt immediately before they get executed." }, "interrupt_after": { "anyOf": [ { "type": "string", - "enum": ["*"] + "enum": [ + "*" + ] }, { "items": { @@ -2760,7 +3569,8 @@ "type": "array" } ], - "title": "Interrupt After" + "title": "Interrupt After", + "description": "Nodes to interrupt immediately after they get executed." }, "stream_mode": { "anyOf": [ @@ -2770,6 +3580,7 @@ "enum": [ "values", "messages", + "messages-tuple", "updates", "events", "debug", @@ -2783,6 +3594,7 @@ "enum": [ "values", "messages", + "messages-tuple", "updates", "events", "debug", @@ -2791,81 +3603,74 @@ } ], "title": "Stream Mode", - "default": ["values"] + "description": "The stream mode(s) to use.", + "default": [ + "values" + ] }, "feedback_keys": { "items": { "type": "string" }, "type": "array", - "title": "Feedback Keys" + "title": "Feedback Keys", + "description": "Feedback keys to assign to run." + }, + "stream_subgraphs": { + "type": "boolean", + "title": "Stream Subgraphs", + "description": "Whether to stream output from subgraphs.", + "default": false }, "on_completion": { "type": "string", - "enum": ["delete", "keep"], + "enum": [ + "delete", + "keep" + ], "title": "On Completion", + "description": "Whether to delete or keep the thread created for a stateless run. Must be one of 'delete' or 'keep'.", "default": "delete" }, "on_disconnect": { "type": "string", - "enum": ["cancel", "continue"], + "enum": [ + "cancel", + "continue" + ], "title": "On Disconnect", + "description": "The disconnect mode to use. Must be one of 'cancel' or 'continue'.", "default": "cancel" }, "after_seconds": { "type": "integer", "title": "After Seconds", - "description": "Number of seconds to wait before starting the run." + "description": "The number of seconds to wait before starting the run. Use to schedule future runs." } }, "type": "object", - "required": ["assistant_id"], + "required": [ + "assistant_id" + ], "title": "RunCreateStateless", - "description": "Payload for creating a streaming run." - }, - "SearchRequest": { - "properties": { - "metadata": { - "type": "object", - "title": "Metadata", - "description": "Metadata to search for." - }, - "limit": { - "type": "integer", - "title": "Limit", - "description": "Maximum number to return.", - "default": 10, - "minimum": 1, - "maximum": 1000 - }, - "offset": { - "type": "integer", - "title": "Offset", - "description": "Offset to start from.", - "default": 0, - "minimum": 0 - } - }, - "type": "object", - "title": "SearchRequest", - "description": "Payload for listing runs." + "description": "Payload for creating a run." }, "AssistantSearchRequest": { "properties": { "metadata": { "type": "object", "title": "Metadata", - "description": "Metadata to search for." + "description": "Metadata to filter by. Exact match filter for each KV pair." }, "graph_id": { "type": "string", "title": "Graph Id", - "description": "Filter by graph ID." + "description": "The ID of the graph to filter by. The graph ID is normally set in your langgraph.json configuration." }, "limit": { "type": "integer", "title": "Limit", - "description": "Maximum number to return.", + "description": "The maximum number of results to return.", "default": 10, "minimum": 1, "maximum": 1000 @@ -2873,13 +3678,13 @@ "offset": { "type": "integer", "title": "Offset", - "description": "Offset to start from.", + "description": "The number of results to skip.", "default": 0, "minimum": 0 } }, "type": "object", - "title": "SearchRequest", + "title": "AssistantSearchRequest", "description": "Payload for listing assistants." }, "AssistantVersionsSearchRequest": { @@ -2887,12 +3692,12 @@ "metadata": { "type": "object", "title": "Metadata", - "description": "Metadata to search for." + "description": "Metadata to filter versions by. Exact match filter for each KV pair." }, "limit": { "type": "integer", "title": "Limit", - "description": "Maximum number to return.", + "description": "The maximum number of versions to return.", "default": 10, "minimum": 1, "maximum": 1000 @@ -2900,7 +3705,7 @@ "offset": { "type": "integer", "title": "Offset", - "description": "Offset to start from.", + "description": "The number of versions to skip.", "default": 0, "minimum": 0 } @@ -2914,7 +3719,7 @@ "metadata": { "type": "object", "title": "Metadata", - "description": "Metadata to filter on." + "description": "Thread metadata to filter on." }, "values": { "type": "object", @@ -2923,9 +3728,14 @@ }, "status": { "type": "string", - "enum": ["idle", "busy", "interrupted", "error"], + "enum": [ + "idle", + "busy", + "interrupted", + "error" + ], "title": "Status", - "description": "Filter by thread status." + "description": "Thread status to filter on." }, "limit": { "type": "integer", @@ -2944,7 +3754,7 @@ } }, "type": "object", - "title": "SearchRequest", + "title": "ThreadSearchRequest", "description": "Payload for listing threads." }, "Thread": { @@ -2952,30 +3762,41 @@ "thread_id": { "type": "string", "format": "uuid", - "title": "Thread Id" + "title": "Thread Id", + "description": "The ID of the thread." }, "created_at": { "type": "string", "format": "date-time", - "title": "Created At" + "title": "Created At", + "description": "The time the thread was created." }, "updated_at": { "type": "string", "format": "date-time", - "title": "Updated At" + "title": "Updated At", + "description": "The last time the thread was updated." }, "metadata": { "type": "object", - "title": "Metadata" + "title": "Metadata", + "description": "The thread metadata." }, "status": { "type": "string", - "enum": ["idle", "busy", "interrupted", "error"], - "title": "Status" + "enum": [ + "idle", + "busy", + "interrupted", + "error" + ], + "title": "Status", + "description": "The status of the thread." }, "values": { "type": "object", - "title": "Values" + "title": "Values", + "description": "The current state of the thread." } }, "type": "object", @@ -2994,12 +3815,22 @@ "type": "string", "format": "uuid", "title": "Thread Id", - "description": "The ID of the thread. If not provided, an ID is generated." + "description": "The ID of the thread. If not provided, a random UUID will be generated." }, "metadata": { "type": "object", "title": "Metadata", - "description": "Metadata for the thread." + "description": "Metadata to add to thread." + }, + "if_exists": { + "type": "string", + "enum": [ + "raise", + "do_nothing" + ], + "title": "If Exists", + "description": "How to handle duplicate creation. Must be either 'raise' (raise error if duplicate), or 'do_nothing' (return existing thread).", + "default": "raise" } }, "type": "object", @@ -3021,7 +3852,7 @@ "ThreadStateCheckpointRequest": { "properties": { "checkpoint": { - "type": "object", + "$ref": "#/components/schemas/CheckpointConfig", "title": "Checkpoint", "description": "The checkpoint to get the state for." }, @@ -3031,7 +3862,9 @@ "description": "Include subgraph states." } }, - "required": ["checkpoint"], + "required": [ + "checkpoint" + ], "type": "object", "title": "ThreadStateCheckpointRequest", "description": "Payload for getting the state of a thread at a checkpoint." @@ -3080,20 +3913,23 @@ "items": {} }, "checkpoint": { - "type": "object", + "$ref": "#/components/schemas/CheckpointConfig", "title": "Checkpoint" }, "state": { "$ref": "#/components/schemas/ThreadState" } }, - "required": ["id", "name"] + "required": [ + "id", + "name" + ] }, "type": "array", "title": "Tasks" }, "checkpoint": { - "type": "object", + "$ref": "#/components/schemas/CheckpointConfig", "title": "Checkpoint" }, "metadata": { @@ -3110,7 +3946,13 @@ } }, "type": "object", - "required": ["values", "next", "checkpoint", "metadata", "created_at"], + "required": [ + "values", + "next", + "checkpoint", + "metadata", + "created_at" + ], "title": "ThreadState" }, "ThreadStateSearch": { @@ -3125,15 +3967,8 @@ }, "before": { "title": "Before", - "description": "Return states before this checkpoint ID.", - "anyOf": [ - { - "type": "string" - }, - { - "type": "object" - } - ] + "description": "Return states before this checkpoint.", + "$ref": "#/components/schemas/CheckpointConfig" }, "metadata": { "type": "object", @@ -3141,7 +3976,7 @@ "description": "Filter states by metadata key-value pairs." }, "checkpoint": { - "type": "object", + "$ref": "#/components/schemas/CheckpointConfig", "title": "Checkpoint", "description": "Return states for this subgraph." } @@ -3166,20 +4001,23 @@ "type": "null" } ], - "title": "Values" + "title": "Values", + "description": "The values to update the state with." }, "checkpoint": { - "type": "object", - "title": "Checkpoint" + "$ref": "#/components/schemas/CheckpointConfig", + "title": "Checkpoint", + "description": "The checkpoint to update the state of." }, "as_node": { "type": "string", - "title": "As Node" + "title": "As Node", + "description": "Update the state as if this node had just executed." } }, "type": "object", "title": "ThreadStateUpdate", - "description": "Payload for adding state to a thread." + "description": "Payload for updating the state of a thread." }, "ThreadStateUpdateResponse": { "properties": { @@ -3192,87 +4030,208 @@ "title": "ThreadStateUpdateResponse", "description": "Response for adding state to a thread." }, + "CheckpointConfig": { + "type": "object", + "title": "CheckpointConfig", + "description": "Checkpoint config.", + "properties": { + "thread_id": { + "type": "string", + "description": "Unique identifier for the thread associated with this checkpoint." + }, + "checkpoint_ns": { + "type": "string", + "description": "Namespace for the checkpoint, used for organization and retrieval." + }, + "checkpoint_id": { + "type": "string", + "description": "Optional unique identifier for the checkpoint itself." + }, + "checkpoint_map": { + "type": "object", + "description": "Optional dictionary containing checkpoint-specific data." + } + } + }, "StorePutRequest": { "type": "object", - "required": ["namespace", "key", "value"], + "required": [ + "namespace", + "key", + "value" + ], "properties": { "namespace": { "type": "array", - "items": { "type": "string" } + "items": { + "type": "string" + }, + "title": "Namespace", + "description": "A list of strings representing the namespace path." }, - "key": { "type": "string" }, - "value": { "type": "object" } - } + "key": { + "type": "string", + "title": "Key", + "description": "The unique identifier for the item within the namespace." + }, + "value": { + "type": "object", + "title": "Value", + "description": "A dictionary containing the item's data." + } + }, + "title": "StorePutRequest", + "description": "Request to store or update an item." }, "StoreDeleteRequest": { "type": "object", - "required": ["key"], + "required": [ + "key" + ], "properties": { "namespace": { "type": "array", - "items": { "type": "string" } + "items": { + "type": "string" + }, + "title": "Namespace", + "description": "A list of strings representing the namespace path." }, - "key": { "type": "string" } - } + "key": { + "type": "string", + "title": "Key", + "description": "The unique identifier for the item." + } + }, + "title": "StoreDeleteRequest", + "description": "Request to delete an item." }, "StoreSearchRequest": { "type": "object", "properties": { "namespace_prefix": { - "type": ["array", "null"], - "items": { "type": "string" } + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + }, + "title": "Namespace Prefix", + "description": "List of strings representing the namespace prefix." }, "filter": { - "type": ["object", "null"], - "additionalProperties": true + "type": [ + "object", + "null" + ], + "additionalProperties": true, + "title": "Filter", + "description": "Optional dictionary of key-value pairs to filter results." }, - "limit": { "type": "integer", "default": 10 }, - "offset": { "type": "integer", "default": 0 } - } + "limit": { + "type": "integer", + "default": 10, + "title": "Limit", + "description": "Maximum number of items to return (default is 10)." + }, + "offset": { + "type": "integer", + "default": 0, + "title": "Offset", + "description": "Number of items to skip before returning results (default is 0)." + } + }, + "title": "StoreSearchRequest", + "description": "Request to search for items within a namespace prefix." }, "StoreListNamespacesRequest": { "type": "object", "properties": { "prefix": { "type": "array", - "items": { "type": "string" } + "items": { + "type": "string" + }, + "title": "Prefix", + "description": "Optional list of strings representing the prefix to filter namespaces." }, "suffix": { "type": "array", - "items": { "type": "string" } + "items": { + "type": "string" + }, + "title": "Suffix", + "description": "Optional list of strings representing the suffix to filter namespaces." }, - "max_depth": { "type": "integer" }, - "limit": { "type": "integer", "default": 100 }, - "offset": { "type": "integer", "default": 0 } + "max_depth": { + "type": "integer", + "title": "Max Depth", + "description": "Optional integer specifying the maximum depth of namespaces to return." + }, + "limit": { + "type": "integer", + "default": 100, + "title": "Limit", + "description": "Maximum number of namespaces to return (default is 100)." + }, + "offset": { + "type": "integer", + "default": 0, + "title": "Offset", + "description": "Number of namespaces to skip before returning results (default is 0)." + } } }, "Item": { "type": "object", - "required": ["namespace", "key", "value", "created_at", "updated_at"], + "required": [ + "namespace", + "key", + "value", + "created_at", + "updated_at" + ], "properties": { "namespace": { "type": "array", - "items": { "type": "string" } + "items": { + "type": "string" + }, + "description": "The namespace of the item. A namespace is analogous to a document's directory." + }, + "key": { + "type": "string", + "description": "The unique identifier of the item within its namespace. In general, keys needn't be globally unique." + }, + "value": { + "type": "object", + "description": "The value stored in the item. This is the document itself." }, - "key": { "type": "string" }, - "value": { "type": "object" }, "created_at": { "type": "string", - "format": "date-time" + "format": "date-time", + "description": "The timestamp when the item was created." }, "updated_at": { "type": "string", - "format": "date-time" + "format": "date-time", + "description": "The timestamp when the item was last updated." } - } + }, + "description": "Represents a single document or data entry in the graph's Store. Items are used to store cross-thread memories." }, "SearchItemsResponse": { "type": "object", - "required": ["items"], + "required": [ + "items" + ], "properties": { "items": { "type": "array", - "items": { "$ref": "#/components/schemas/Item" } + "items": { + "$ref": "#/components/schemas/Item" + } } } }, @@ -3280,15 +4239,15 @@ "type": "array", "items": { "type": "array", - "items": { "type": "string" } + "items": { + "type": "string" + } } }, "ErrorResponse": { - "type": "object", - "properties": { - "error": { "type": "string" }, - "message": { "type": "string" } - } + "type": "string", + "title": "ErrorResponse", + "description": "Error message returned from the server" } }, "responses": { @@ -3296,7 +4255,9 @@ "description": "Successful retrieval of an item.", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/Item" } + "schema": { + "$ref": "#/components/schemas/Item" + } } } }, @@ -3312,7 +4273,9 @@ "description": "Successful search operation.", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/SearchItemsResponse" } + "schema": { + "$ref": "#/components/schemas/SearchItemsResponse" + } } } }, @@ -3320,7 +4283,9 @@ "description": "Successful retrieval of namespaces.", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/ListNamespaceResponse" } + "schema": { + "$ref": "#/components/schemas/ListNamespaceResponse" + } } } }, @@ -3328,38 +4293,11 @@ "description": "An error occurred.", "content": { "application/json": { - "schema": { "$ref": "#/components/schemas/ErrorResponse" } + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } } } - }, - "ValidationError": { - "properties": { - "loc": { - "items": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "integer" - } - ] - }, - "type": "array", - "title": "Location" - }, - "msg": { - "type": "string", - "title": "Message" - }, - "type": { - "type": "string", - "title": "Error Type" - } - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError" } } } diff --git a/docs/docs/cloud/reference/cli.md b/docs/docs/cloud/reference/cli.md index ee481ea20..0db84cb43 100644 --- a/docs/docs/cloud/reference/cli.md +++ b/docs/docs/cloud/reference/cli.md @@ -1,23 +1,38 @@ # LangGraph CLI -The LangGraph CLI includes commands to build and run a LangGraph Cloud API server locally in [Docker](https://www.docker.com/). For development and testing, use the CLI to deploy a local API server. + +The LangGraph command line interface includes commands to build and run a LangGraph Cloud API server locally in [Docker](https://www.docker.com/). For development and testing, you can use the CLI to deploy a local API server as an alternative to the [Studio desktop app](../../concepts/langgraph_studio.md). ## Installation + 1. Ensure that Docker is installed (e.g. `docker --version`). -2. Install the `langgraph-cli` Python package (e.g. `pip install langgraph-cli`). +2. Install the `langgraph-cli` package: + + === "pip" + ```bash + pip install langgraph-cli + ``` + + === "Homebrew (MacOS only)" + ```bash + brew install langgraph-cli + ``` + 3. Run the command `langgraph --help` to confirm that the CLI is installed. [](){#langgraph.json} + ## Configuration File + The LangGraph CLI requires a JSON configuration file with the following keys: -| Key | Description | -| --- | ----------- | -| `dependencies` | **Required**. Array of dependencies for LangGraph Cloud API server. Dependencies can be one of the following: (1) `"."`, which will look for local Python packages, (2) `pyproject.toml`, `setup.py` or `requirements.txt` in the app directory `"./local_package"`, or (3) a package name. | -| `graphs` | **Required**. Mapping from graph ID to path where the compiled graph or a function that makes a graph is defined. Example:
  • `./your_package/your_file.py:variable`, where `variable` is an instance of `langgraph.graph.state.CompiledStateGraph`
  • `./your_package/your_file.py:make_graph`, where `make_graph` is a function that takes a config dictionary (`langchain_core.runnables.RunnableConfig`) and creates an instance of `langgraph.graph.state.StateGraph` / `langgraph.graph.state.CompiledStateGraph`.
| -| `env` | Path to `.env` file or a mapping from environment variable to its value. | -| `python_version` | `3.11` or `3.12`. Defaults to `3.11`. | -| `pip_config_file`| Path to `pip` config file. | -| `dockerfile_lines` | Array of additional lines to add to Dockerfile following the import from parent image. | +| Key | Description | +|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `dependencies` | **Required**. Array of dependencies for LangGraph Cloud API server. Dependencies can be one of the following: (1) `"."`, which will look for local Python packages, (2) `pyproject.toml`, `setup.py` or `requirements.txt` in the app directory `"./local_package"`, or (3) a package name. | +| `graphs` | **Required**. Mapping from graph ID to path where the compiled graph or a function that makes a graph is defined. Example:
  • `./your_package/your_file.py:variable`, where `variable` is an instance of `langgraph.graph.state.CompiledStateGraph`
  • `./your_package/your_file.py:make_graph`, where `make_graph` is a function that takes a config dictionary (`langchain_core.runnables.RunnableConfig`) and creates an instance of `langgraph.graph.state.StateGraph` / `langgraph.graph.state.CompiledStateGraph`.
| +| `env` | Path to `.env` file or a mapping from environment variable to its value. | +| `python_version` | `3.11` or `3.12`. Defaults to `3.11`. | +| `pip_config_file` | Path to `pip` config file. | +| `dockerfile_lines` | Array of additional lines to add to Dockerfile following the import from parent image. |

Note

@@ -27,101 +42,162 @@ The LangGraph CLI requires a JSON configuration file with the following keys:
Example: + ```json { - "dependencies": [ - "langchain_openai", - "./your_package" - ], - "graphs": { - "my_graph_id": "./your_package/your_file.py:variable" - }, - "env": "./.env" + "dependencies": ["langchain_openai", "./your_package"], + "graphs": { + "my_graph_id": "./your_package/your_file.py:variable" + }, + "env": "./.env" } ``` -Example: +Example with environment variables: + ```json { - "python_version": "3.11", - "dependencies": [ - "langchain_openai", - "." - ], - "graphs": { - "my_graph_id": "./your_package/your_file.py:make_graph" - }, - "env": { - "OPENAI_API_KEY": "secret-key" - } + "python_version": "3.11", + "dependencies": ["langchain_openai", "."], + "graphs": { + "my_graph_id": "./your_package/your_file.py:make_graph" + }, + "env": { + "OPENAI_API_KEY": "secret-key" + } } ``` ## Commands + The base command for the LangGraph CLI is `langgraph`. **Usage** + ``` langgraph [OPTIONS] COMMAND [ARGS] ``` +### `dev` + +Run LangGraph API server in development mode with hot reloading and debugging capabilities. This lightweight server requires no Docker installation and is suitable for development and testing. State is persisted to a local directory. + +**Installation** + +This command requires the "inmem" extra to be installed: + +```bash +pip install -U "langgraph-cli[inmem]" +``` + +**Usage** + +``` +langgraph dev [OPTIONS] +``` + +**Options** + +| Option | Default | Description | +|----------------------------|------------------|--------------------------------------------------------------------------------------------| +| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables | +| `--host TEXT` | `127.0.0.1` | Host to bind the server to | +| `--port INTEGER` | `2024` | Port to bind the server to | +| `--no-reload` | | Disable auto-reload | +| `--n-jobs-per-worker INTEGER` | | Number of jobs per worker. Default is 10 | +| `--no-browser` | | Disable automatic browser opening | +| `--debug-port INTEGER` | | Port for debugger to listen on | +| `--help` | | Display command documentation | + ### `build` + Build LangGraph Cloud API server Docker image. **Usage** + ``` langgraph build [OPTIONS] ``` **Options** -| Option | Default | Description | -| ------ | ------- | ----------- | -| `--platform TEXT` | | Target platform(s) to build the Docker image for. Example: `langgraph build --platform linux/amd64,linux/arm64` | -| `-t, --tag TEXT` | | **Required**. Tag for the Docker image. Example: `langgraph build -t my-image` | -| `--pull / --no-pull` | `--pull` | Build with latest remote Docker image. Use `--no-pull` for running the LangGraph Cloud API server with locally built images. | -| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. | -| `--help` | | Display command documentation. | +| Option | Default | Description | +|----------------------|------------------|------------------------------------------------------------------------------------------------------------------------------| +| `--platform TEXT` | | Target platform(s) to build the Docker image for. Example: `langgraph build --platform linux/amd64,linux/arm64` | +| `-t, --tag TEXT` | | **Required**. Tag for the Docker image. Example: `langgraph build -t my-image` | +| `--pull / --no-pull` | `--pull` | Build with latest remote Docker image. Use `--no-pull` for running the LangGraph Cloud API server with locally built images. | +| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. | +| `--help` | | Display command documentation. | ### `up` -Start langgraph API server. For local testing, requires a LangSmith API key with access to LangGraph Cloud closed beta. Requires a license key for production use. + +Start LangGraph API server. For local testing, requires a LangSmith API key with access to LangGraph Cloud closed beta. Requires a license key for production use. **Usage** + ``` langgraph up [OPTIONS] ``` **Options** -| Option | Default | Description | -| ------ | ------- | ----------- | -| `--wait` | | Wait for services to start before returning. Implies --detach | -| `--postgres-uri TEXT` | Local database | Postgres URI to use for the database. | -| `--watch` | | Restart on file changes | -| `--debugger-base-url TEXT` | `http://127.0.0.1:[PORT]` | URL used by the debugger to access LangGraph API. | -| `--debugger-port INTEGER` | | Pull the debugger image locally and serve the UI on specified port | -| `--verbose` | | Show more output from the server logs. | -| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. | -| `-d, --docker-compose FILE` | | Path to docker-compose.yml file with additional services to launch. | -| `-p, --port INTEGER` | `8123` | Port to expose. Example: `langgraph test --port 8000` | -| `--pull / --no-pull` | `pull` | Pull latest images. Use --no-pull for running the server with locally-built images. Example: `langgraph up --no-pull` | -| `--recreate / --no-recreate` | `no-recreate` | Recreate containers even if their configuration and image haven't changed | -| `--help` | | Display command documentation. | +| Option | Default | Description | +|------------------------------|---------------------------|-----------------------------------------------------------------------------------------------------------------------| +| `--wait` | | Wait for services to start before returning. Implies --detach | +| `--postgres-uri TEXT` | Local database | Postgres URI to use for the database. | +| `--watch` | | Restart on file changes | +| `--debugger-base-url TEXT` | `http://127.0.0.1:[PORT]` | URL used by the debugger to access LangGraph API. | +| `--debugger-port INTEGER` | | Pull the debugger image locally and serve the UI on specified port | +| `--verbose` | | Show more output from the server logs. | +| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. | +| `-d, --docker-compose FILE` | | Path to docker-compose.yml file with additional services to launch. | +| `-p, --port INTEGER` | `8123` | Port to expose. Example: `langgraph up --port 8000` | +| `--pull / --no-pull` | `pull` | Pull latest images. Use `--no-pull` for running the server with locally-built images. Example: `langgraph up --no-pull` | +| `--recreate / --no-recreate` | `no-recreate` | Recreate containers even if their configuration and image haven't changed | +| `--help` | | Display command documentation. | -### `test` -Test your LangGraph in the cloud. The only function you can call from the SDK after testing your graph is `client.runs.stream(thread_id=None, ...)` +### `dockerfile` + +Generate a Dockerfile for building a LangGraph Cloud API server Docker image. **Usage** + ``` -langgraph test [OPTIONS] +langgraph dockerfile [OPTIONS] SAVE_PATH ``` **Options** -| Option | Default | Description | -| ------ | ------- | ----------- | -| `--verbose` | | Show more output from the server logs. | -| `-c, --config FILE` | `langgraph.json` | Path to configuration file declaring dependencies, graphs and environment variables. | -| `-p, --port INTEGER` | `8123` | Port to expose. Example: `langgraph test --port 8000` | -| `--pull / --no-pull` | `pull` | Pull latest images. Use --no-pull for running the server with locally-built images. Example: `langgraph up --no-pull` | -| `--help` | | Display command documentation. | \ No newline at end of file +| Option | Default | Description | +|---------------------|------------------|-----------------------------------------------------------------------------------------------------------------| +| `-c, --config FILE` | `langgraph.json` | Path to the [configuration file](#configuration-file) declaring dependencies, graphs and environment variables. | +| `--help` | | Show this message and exit. | + +Example: + +```bash +langgraph dockerfile -c langgraph.json Dockerfile +``` + +This generates a Dockerfile that looks similar to: + +```dockerfile +FROM langchain/langgraph-api:3.11 + +ADD ./pipconf.txt /pipconfig.txt + +RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt langchain_community langchain_anthropic langchain_openai wikipedia scikit-learn + +ADD ./graphs /deps/__outer_graphs/src +RUN set -ex && \ + for line in '[project]' \ + 'name = "graphs"' \ + 'version = "0.1"' \ + '[tool.setuptools.package-data]' \ + '"*" = ["**/*"]'; do \ + echo "$line" >> /deps/__outer_graphs/pyproject.toml; \ + done + +RUN PIP_CONFIG_FILE=/pipconfig.txt PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/* + +ENV LANGSERVE_GRAPHS='{"agent": "/deps/__outer_graphs/src/agent.py:graph", "storm": "/deps/__outer_graphs/src/storm.py:graph"}' diff --git a/docs/docs/concepts/agentic_concepts.md b/docs/docs/concepts/agentic_concepts.md index 94b52249b..46015673a 100644 --- a/docs/docs/concepts/agentic_concepts.md +++ b/docs/docs/concepts/agentic_concepts.md @@ -103,15 +103,15 @@ Parallel processing is vital for efficient multi-agent systems and complex tasks For practical implementation, see our [map-reduce tutorial](../how-tos/map-reduce.ipynb). -### Sub-graphs +### Subgraphs -Sub-graphs are essential for managing complex agent architectures, particularly in multi-agent systems. They allow: +[Subgraphs](./low_level.md#subgraphs) are essential for managing complex agent architectures, particularly in [multi-agent systems](./multi_agent.md). They allow: - Isolated state management for individual agents - Hierarchical organization of agent teams - Controlled communication between agents and the main system -Sub-graphs communicate with the parent graph through overlapping keys in the state schema. This enables flexible, modular agent design. For implementation details, refer to our [sub-graph tutorial](../how-tos/subgraph.ipynb). +Subgraphs communicate with the parent graph through overlapping keys in the state schema. This enables flexible, modular agent design. For implementation details, refer to our [subgraph how-to guide](../how-tos/subgraph.ipynb). ### Reflection diff --git a/docs/docs/concepts/application_structure.md b/docs/docs/concepts/application_structure.md new file mode 100644 index 000000000..d27ed60b9 --- /dev/null +++ b/docs/docs/concepts/application_structure.md @@ -0,0 +1,167 @@ +# Application Structure + +!!! info "Prerequisites" + + - [LangGraph Server](./langgraph_server.md) + - [LangGraph Glossary](./low_level.md) + +## Overview + +A LangGraph application consists of one or more graphs, a LangGraph API Configuration file (`langgraph.json`), a file that specifies dependencies, and an optional .env file that specifies environment variables. + +This guide shows a typical structure for a LangGraph application and shows how the required information to deploy a LangGraph application using the LangGraph Platform is specified. + +## Key Concepts + +To deploy using the LangGraph Platform, the following information should be provided: + +1. A [LangGraph API Configuration file](#configuration-file) (`langgraph.json`) that specifies the dependencies, graphs, environment variables to use for the application. +2. The [graphs](#graphs) that implement the logic of the application. +3. A file that specifies [dependencies](#dependencies) required to run the application. +4. [Environment variable](#environment-variables) that are required for the application to run. + +## File Structure + +Below are examples of directory structures for Python and JavaScript applications: + +=== "Python (requirements.txt)" + + ```plaintext + my-app/ + ├── my_agent # all project code lies within here + │ ├── utils # utilities for your graph + │ │ ├── __init__.py + │ │ ├── tools.py # tools for your graph + │ │ ├── nodes.py # node functions for you graph + │ │ └── state.py # state definition of your graph + │ ├── requirements.txt # package dependencies + │ ├── __init__.py + │ └── agent.py # code for constructing your graph + ├── .env # environment variables + └── langgraph.json # configuration file for LangGraph + ``` +=== "Python (pyproject.toml)" + + ```plaintext + my-app/ + ├── my_agent # all project code lies within here + │ ├── utils # utilities for your graph + │ │ ├── __init__.py + │ │ ├── tools.py # tools for your graph + │ │ ├── nodes.py # node functions for you graph + │ │ └── state.py # state definition of your graph + │ ├── __init__.py + │ └── agent.py # code for constructing your graph + ├── .env # environment variables + ├── langgraph.json # configuration file for LangGraph + └── pyproject.toml # dependencies for your project + ``` + +=== "JS (package.json)" + + ```plaintext + my-app/ + ├── src # all project code lies within here + │ ├── utils # optional utilities for your graph + │ │ ├── tools.ts # tools for your graph + │ │ ├── nodes.ts # node functions for you graph + │ │ └── state.ts # state definition of your graph + │ └── agent.ts # code for constructing your graph + ├── package.json # package dependencies + ├── .env # environment variables + └── langgraph.json # configuration file for LangGraph + ``` + +!!! note + + The directory structure of a LangGraph application can vary depending on the programming language and the package manager used. + + +## Configuration File + +The `langgraph.json` file is a JSON file that specifies the dependencies, graphs, environment variables, and other settings required to deploy a LangGraph application. + +The file supports specification of the following information: + + +| Key | Description | +|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `dependencies` | **Required**. Array of dependencies for LangGraph API server. Dependencies can be one of the following: (1) `"."`, which will look for local Python packages, (2) `pyproject.toml`, `setup.py` or `requirements.txt` in the app directory `"./local_package"`, or (3) a package name. | +| `graphs` | **Required**. Mapping from graph ID to path where the compiled graph or a function that makes a graph is defined. Example:
  • `./your_package/your_file.py:variable`, where `variable` is an instance of `langgraph.graph.state.CompiledStateGraph`
  • `./your_package/your_file.py:make_graph`, where `make_graph` is a function that takes a config dictionary (`langchain_core.runnables.RunnableConfig`) and creates an instance of `langgraph.graph.state.StateGraph` / `langgraph.graph.state.CompiledStateGraph`.
| +| `env` | Path to `.env` file or a mapping from environment variable to its value. | +| `python_version` | `3.11` or `3.12`. Defaults to `3.11`. | +| `pip_config_file` | Path to `pip` config file. | +| `dockerfile_lines` | Array of additional lines to add to Dockerfile following the import from parent image. | +!!! tip + + The LangGraph CLI defaults to using the configuration file **langgraph.json** in the current directory. + + +### Examples + +=== "Python" + + * The dependencies involve a custom local package and the `langchain_openai` package. + * A single graph will be loaded from the file `./your_package/your_file.py` with the variable `variable`. + * The environment variables are loaded from the `.env` file. + + ```json + { + "dependencies": [ + "langchain_openai", + "./your_package" + ], + "graphs": { + "my_agent": "./your_package/your_file.py:agent" + }, + "env": "./.env" + } + ``` + +=== "JavaScript" + + * The dependencies will be loaded from a dependency file in the local directory (e.g., `package.json`). + * A single graph will be loaded from the file `./your_package/your_file.js` with the function `agent`. + * The environment variable `OPENAI_API_KEY` is set inline. + + ```json + { + "dependencies": [ + "." + ], + "graphs": { + "my_agent": "./your_package/your_file.js:agent" + }, + "env": { + "OPENAI_API_KEY": "secret-key" + } + } + ``` + +## Dependencies + +A LangGraph application may depend on other Python packages or JavaScript libraries (depending on the programming language in which the application is written). + +You will generally need to specify the following information for dependencies to be set up correctly: + +1. A file in the directory that specifies the dependencies (e.g., `requirements.txt`, `pyproject.toml`, or `package.json`). +2. A `dependencies` key in the [LangGraph configuration file](#configuration-file) that specifies the dependencies required to run the LangGraph application. +3. Any additional binaries or system libraries can be specified using `dockerfile_lines` key in the [LangGraph configuration file](#configuration-file). + +## Graphs + +Use the `graphs` key in the [LangGraph configuration file](#configuration-file) to specify which graphs will be available in the deployed LangGraph application. + +You can specify one or more graphs in the configuration file. Each graph is identified by a name (which should be unique) and a path for either: (1) the compiled graph or (2) a function that makes a graph is defined. + +## Environment Variables + +If you're working with a deployed LangGraph application locally, you can configure environment variables in the `env` key of the [LangGraph configuration file](#configuration-file). + +For a production deployment, you will typically want to configure the environment variables in the deployment environment. + +## Related + +Please see the following resources for more information: + +- How-to guides for [Application Structure](../how-tos/index.md#application-structure). diff --git a/docs/docs/concepts/assistants.md b/docs/docs/concepts/assistants.md new file mode 100644 index 000000000..4e33fc694 --- /dev/null +++ b/docs/docs/concepts/assistants.md @@ -0,0 +1,37 @@ +# Assistants + +!!! info "Prerequisites" + + - [LangGraph Server](./langgraph_server.md) + +When building agents, it is fairly common to make rapid changes that *do not* alter the graph logic. For example, simply changing prompts or the LLM selection can have significant impacts on the behavior of the agents. Assistants offer an easy way to make and save these types of changes to agent configuration. This can have at least two use-cases: + +* Assistants give developers a quick and easy way to modify and version agents for experimentation. +* Assistants can be modified via LangGraph Studio, offering a no-code way to configure agents (e.g., for business users). + +Assistants build off the concept of ["configuration"](low_level.md#configuration). +While ["configuration"](low_level.md#configuration) is available in the open source LangGraph library as well, assistants are only present in [LangGraph Platform](langgraph_platform.md). +This is because Assistants are tightly coupled to your deployed graph, and so we can only make them available when we are also deploying the graphs. + +## Configuring Assistants + +In practice, an assistant is just an *instance* of a graph with a specific configuration. Because of this, multiple assistants can reference the same graph but can contain different configurations, such as prompts, models, and other graph configuration options. The LangGraph Cloud API provides several endpoints for creating and managing assistants. See the [API reference](../cloud/reference/api/api_ref.html) and [this how-to](../cloud/how-tos/configuration_cloud.md) for more details on how to create assistants. + +## Versioning Assistants + +Once you've created an assistant, you can save and version it to track changes to the configuration over time. You can think about this at three levels: + +1) The graph lays out the general agent application logic +2) The agent configuration options represent parameters that can be changed +3) Assistant versions save and track specific settings of the agent configuration options + +For example, let's imagine you have a general writing agent. You have created a general graph architecture that works well for writing. However, there are different types of writing, e.g. blogs vs tweets. In order to get the best performance on each use case, you need to make some minor changes to the models and prompts used. In this setup, you could create an assistant for each use case - one for blog writing and one for tweeting. These would share the same graph structure, but they may use different models and different prompts. Read [this how-to](../cloud/how-tos/assistant_versioning.md) to learn how you can use assistant versioning through both the [Studio](../concepts/langgraph_studio.md) and the SDK. + +![assistant versions](img/assistants.png) + + +## Resources + +For more information on assistants, see the following resources: + +- [Assistants how-to guides](../how-tos/index.md#assistants) \ No newline at end of file diff --git a/docs/docs/concepts/bring_your_own_cloud.md b/docs/docs/concepts/bring_your_own_cloud.md new file mode 100644 index 000000000..6d0def74a --- /dev/null +++ b/docs/docs/concepts/bring_your_own_cloud.md @@ -0,0 +1,54 @@ +# Bring Your Own Cloud (BYOC) + +!!! note Prerequisites + + - [LangGraph Platform](./langgraph_platform.md) + - [Deployment Options](./deployment_options.md) + +## Architecture + +Split control plane (hosted by us) and data plane (hosted by you, managed by us). + +| | Control Plane | Data Plane | +|-----------------------------|---------------------------------|-----------------------------------------------| +| What it does | Manages deployments, revisions. | Runs your LangGraph graphs, stores your data. | +| Where it is hosted | LangChain Cloud account | Your cloud account | +| Who provisions and monitors | LangChain | LangChain | + +LangChain has no direct access to the resources created in your cloud account, and can only interact with them via AWS APIs. Your data never leaves your cloud account / VPC at rest or in transit. + +![Architecture](img/byoc_architecture.png) + +## Requirements + +- You’re using AWS already. +- You use `langgraph-cli` and/or [LangGraph Studio](./langgraph_studio.md) app to test graph locally. +- You use `langgraph build` command to build image and then push it to your AWS ECR repository (`docker push`). + +## How it works + +- We provide you a [Terraform module](https://github.com/langchain-ai/terraform/tree/main/modules/langgraph_cloud_setup) which you run to set up our requirements + 1. Creates an AWS role (which our control plane will later assume to provision and monitor resources) + - https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonVPCReadOnlyAccess.html + - Read VPCS to find subnets + - https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonECS_FullAccess.html + - Used to create/delete ECS resources for your LangGraph Cloud instances + - https://docs.aws.amazon.com/aws-managed-policy/latest/reference/SecretsManagerReadWrite.html + - Create secrets for your ECS resources + - https://docs.aws.amazon.com/aws-managed-policy/latest/reference/CloudWatchReadOnlyAccess.html + - Read CloudWatch metrics/logs to monitor your instances/push deployment logs + - https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonRDSFullAccess.html + - Provision `RDS` instances for your LangGraph Cloud instances + 2. Either + - Tags an existing vpc / subnets as `langgraph-cloud-enabled` + - Creates a new vpc and subnets and tags them as `langgraph-cloud-enabled` +- You create a LangGraph Cloud Project in `smith.langchain.com` providing + - the ID of the AWS role created in the step above + - the AWS ECR repo to pull the service image from +- We provision the resources in your cloud account using the role above +- We monitor those resources to ensure uptime and recovery from errors + +Notes for customers using [self-hosted LangSmith](https://docs.smith.langchain.com/self_hosting): + +- Creation of new LangGraph Cloud projects and revisions currently needs to be done on smith.langchain.com. +- You can however set up the project to trace to your self-hosted LangSmith instance if desired diff --git a/docs/docs/concepts/deployment_options.md b/docs/docs/concepts/deployment_options.md new file mode 100644 index 000000000..c59cbeecd --- /dev/null +++ b/docs/docs/concepts/deployment_options.md @@ -0,0 +1,96 @@ +# Deployment Options + +!!! info "Prerequisites" + + - [LangGraph Platform](./langgraph_platform.md) + - [LangGraph Server](./langgraph_server.md) + - [LangGraph Platform Plans](./plans.md) + +## Overview + +There are 4 main options for deploying with the LangGraph Platform: + +1. **[Self-Hosted Lite](#self-hosted-lite)**: Available for all plans. + +2. **[Self-Hosted Enterprise](#self-hosted-enterprise)**: Available for the **Enterprise** plan. + +3. **[Cloud SaaS](#cloud-saas)**: Available for **Plus** and **Enterprise** plans. + +4. **[Bring Your Own Cloud](#bring-your-own-cloud)**: Available only for **Enterprise** plans and **only on AWS**. + +Please see the [LangGraph Platform Plans](./plans.md) for more information on the different plans. + +The guide below will explain the differences between the deployment options. + +## Self-Hosted Enterprise + +!!! important + + The Self-Hosted Enterprise version is only available for the **Enterprise** plan. + +With a Self-Hosted Enterprise deployment, you are responsible for managing the infrastructure, including setting up and maintaining required databases and Redis instances. + +You’ll build a Docker image using the [LangGraph CLI](./langgraph_cli.md), which can then be deployed on your own infrastructure. + +For more information, please see: + +* [Self-Hosted conceptual guide](./self_hosted.md) +* [Self-Hosted Deployment how-to guide](../how-tos/deploy-self-hosted.md) + +## Self-Hosted Lite + +!!! important + + The Self-Hosted Lite version is available for all plans. + +The Self-Hosted Lite deployment option is a free (up to 1 million nodes executed), limited version of LangGraph Platform that you can run locally or in a self-hosted manner. + +With a Self-Hosted Lite deployment, you are responsible for managing the infrastructure, including setting up and maintaining required databases and Redis instances. + +You’ll build a Docker image using the [LangGraph CLI](./langgraph_cli.md), which can then be deployed on your own infrastructure. + + +For more information, please see: + +* [Self-Hosted conceptual guide](./self_hosted.md) +* [Self-Hosted deployment how-to guide](../how-tos/deploy-self-hosted.md) + +## Cloud SaaS + +!!! important + + The Cloud SaaS version of LangGraph Platform is only available for **Plus** and **Enterprise** plans. + + +The [Cloud SaaS](./langgraph_cloud.md) version of LangGraph Platform is hosted as part of [LangSmith](https://smith.langchain.com/). + +The Cloud SaaS version of LangGraph Platform provides a simple way to deploy and manage your LangGraph applications. + +This deployment option provides an integration with GitHub, allowing you to deploy code from any of your repositories on GitHub. + +For more information, please see: + +* [Cloud SaaS Conceptual Guide](./langgraph_cloud.md) +* [How to deploy to Cloud SaaS](../cloud/deployment/cloud.md) + + +## Bring Your Own Cloud + +!!! important + + The Bring Your Own Cloud version of LangGraph Platform is only available for **Enterprise** plans. + + +This combines the best of both worlds for Cloud and Self-Hosted. We manage the infrastructure, so you don't have to, but the infrastructure all runs within your cloud. This is currently only available on AWS. + +For more information please see: + +* [Bring Your Own Cloud Conceptual Guide](./bring_your_own_cloud.md) + +## Related + +For more information, please see: + +* [LangGraph Platform plans](./plans.md) +* [LangGraph Platform pricing](https://www.langchain.com/langgraph-platform-pricing) +* [Deployment how-to guides](../how-tos/index.md#deployment) diff --git a/docs/docs/concepts/double_texting.md b/docs/docs/concepts/double_texting.md new file mode 100644 index 000000000..6906a9e4d --- /dev/null +++ b/docs/docs/concepts/double_texting.md @@ -0,0 +1,42 @@ +# Double Texting + +!!! info "Prerequisites" + - [LangGraph Server](./langgraph_server.md) + +Many times users might interact with your graph in unintended ways. +For instance, a user may send one message and before the graph has finished running send a second message. +More generally, users may invoke the graph a second time before the first run has finished. +We call this "double texting". + +Currently, LangGraph only addresses this as part of [LangGraph Platform](langgraph_platform.md), not in the open source. +The reason for this is that in order to handle this we need to know how the graph is deployed, and since LangGraph Platform deals with deployment the logic needs to live there. +If you do not want to use LangGraph Platform, we describe the options we have implemented in detail below. + +![](img/double_texting.png) + +## Reject + +This is the simplest option, this just rejects any follow up runs and does not allow double texting. +See the [how-to guide](../cloud/how-tos/reject_concurrent.md) for configuring the reject double text option. + +## Enqueue + +This is a relatively simple option which continues the first run until it completes the whole run, then sends the new input as a separate run. +See the [how-to guide](../cloud/how-tos/enqueue_concurrent.md) for configuring the enqueue double text option. + +## Interrupt + +This option interrupts the current execution but saves all the work done up until that point. +It then inserts the user input and continues from there. + +If you enable this option, your graph should be able to handle weird edge cases that may arise. +For example, you could have called a tool but not yet gotten back a result from running that tool. +You may need to remove that tool call in order to not have a dangling tool call. + +See the [how-to guide](../cloud/how-tos/interrupt_concurrent.md) for configuring the interrupt double text option. + +## Rollback + +This option interrupts the current execution AND rolls back all work done up until that point, including the original run input. It then sends the new user input in, basically as if it was the original input. + +See the [how-to guide](../cloud/how-tos/rollback_concurrent.md) for configuring the rollback double text option. diff --git a/docs/docs/concepts/faq.md b/docs/docs/concepts/faq.md index 4ce51b98e..33457bdf8 100644 --- a/docs/docs/concepts/faq.md +++ b/docs/docs/concepts/faq.md @@ -2,9 +2,58 @@ Common questions and their answers! -## Do I need to use LangChain in order to use LangGraph? +## Do I need to use LangChain to use LangGraph? What’s the difference? -No! LangGraph is a general-purpose framework - the nodes and edges are nothing more than Python functions. You can use LangChain, raw HTTP requests, or even other frameworks inside these nodes and edges. +No. LangGraph is an orchestration framework for complex agentic systems and is more low-level and controllable than LangChain agents. LangChain provides a standard interface to interact with models and other components, useful for straight-forward chains and retrieval flows. + +## How is LangGraph different from other agent frameworks? + +Other agentic frameworks can work for simple, generic tasks but fall short for complex tasks bespoke to a company’s needs. LangGraph provides a more expressive framework to handle companies’ unique tasks without restricting users to a single black-box cognitive architecture. + +## Does LangGraph impact the performance of my app? + +LangGraph will not add any overhead to your code and is specifically designed with streaming workflows in mind. + +## Is LangGraph open source? Is it free? + +Yes. LangGraph is an MIT-licensed open-source library and is free to use. + +## How are LangGraph and LangGraph Platform different? + +LangGraph is a stateful, orchestration framework that brings added control to agent workflows. LangGraph Platform is a service for deploying and scaling LangGraph applications, with an opinionated API for building agent UXs, plus an integrated developer studio. + +| Features | LangGraph (open source) | LangGraph Platform | +|----------|------------------------|-------------------| +| Description | Stateful orchestration framework for agentic applications | Scalable infrastructure for deploying LangGraph applications | +| SDKs | Python and JavaScript | Python and JavaScript | +| HTTP APIs | None | Yes - useful for retrieving & updating state or long-term memory, or creating a configurable assistant | +| Streaming | Basic | Dedicated mode for token-by-token messages | +| Checkpointer | Community contributed | Supported out-of-the-box | +| Persistence Layer | Self-managed | Managed Postgres with efficient storage | +| Deployment | Self-managed | • Cloud SaaS
• Free self-hosted
• Enterprise (BYOC or paid self-hosted) | +| Scalability | Self-managed | Auto-scaling of task queues and servers | +| Fault-tolerance | Self-managed | Automated retries | +| Concurrency Control | Simple threading | Supports double-texting | +| Scheduling | None | Cron scheduling | +| Monitoring | None | Integrated with LangSmith for observability | +| IDE integration | LangGraph Studio for Desktop | LangGraph Studio for Desktop & Cloud | + +## What are my deployment options for LangGraph Platform? + +We currently have the following deployment options for LangGraph applications: + +- [‍Self-Hosted Lite](./deployment_options.md#self-hosted-lite): A free (up to 1M nodes executed), limited version of LangGraph Platform that you can run locally or in a self-hosted manner. This version requires a LangSmith API key and logs all usage to LangSmith. Fewer features are available than in paid plans. +- [Cloud SaaS](./deployment_options.md#cloud-saas): Fully managed and hosted as part of LangSmith, with automatic updates and zero maintenance. +- [‍Bring Your Own Cloud (BYOC)](./deployment_options.md#bring-your-own-cloud): Deploy LangGraph Platform within your VPC, provisioned and run as a service. Keep data in your environment while outsourcing the management of the service. +- [Self-Hosted Enterprise](./deployment_options.md#self-hosted-enterprise): Deploy LangGraph entirely on your own infrastructure. + +## Is LangGraph Platform open source? + +No. LangGraph Platform is proprietary software. + +There is a free, self-hosted version of LangGraph Platform with access to basic features. The Cloud SaaS deployment option is free while in beta, but will eventually be a paid service. We will always give ample notice before charging for a service and reward our early adopters with preferential pricing. The Bring Your Own Cloud (BYOC) and Self-Hosted Enterprise options are also paid services. [Contact our sales team](https://www.langchain.com/contact-sales) to learn more. + +For more information, see our [LangGraph Platform pricing page](https://www.langchain.com/pricing-langgraph-platform). ## Does LangGraph work with LLMs that don't support tool calling? diff --git a/docs/docs/concepts/high_level.md b/docs/docs/concepts/high_level.md index 7146fed9c..54f23d9ae 100644 --- a/docs/docs/concepts/high_level.md +++ b/docs/docs/concepts/high_level.md @@ -2,13 +2,13 @@ LLMs are extremely powerful, particularly when connected to other systems such as a retriever or APIs. This is why many LLM applications use a control flow of steps before and / or after LLM calls. As an example [RAG](https://github.com/langchain-ai/rag-from-scratch) performs retrieval of relevant documents to a question, and passes those documents to an LLM in order to ground the response. Often a control flow of steps before and / or after an LLM is called a "chain." Chains are a popular paradigm for programming with LLMs and offer a high degree of reliability; the same set of steps runs with each chain invocation. -However, we often want LLM systems that can pick their own control flow! This is one definition of an [agent](https://blog.langchain.dev/what-is-an-agent/): an agent is a system that uses an LLM to decide the control flow of an application. Unlike a chain, an agent given an LLM some degree of control over the sequence of steps in the application. Examples of using an LLM to decide the control of an application: +However, we often want LLM systems that can pick their own control flow! This is one definition of an [agent](https://blog.langchain.dev/what-is-an-agent/): an agent is a system that uses an LLM to decide the control flow of an application. Unlike a chain, an agent gives an LLM some degree of control over the sequence of steps in the application. Examples of using an LLM to decide the control of an application: - Using an LLM to route between two potential paths - Using an LLM to decide which of many tools to call - Using an LLM to decide whether the generated answer is sufficient or more work is need -There are many different types of [agent architectures](https://blog.langchain.dev/what-is-a-cognitive-architecture/) to consider, which given an LLM varying levels of control. On one extreme, a router allows an LLM to select a single step from a specified set of options and, on the other extreme, a fully autonomous long-running agent may have complete freedom to select any sequence of steps that it wants for a given problem. +There are many different types of [agent architectures](https://blog.langchain.dev/what-is-a-cognitive-architecture/) to consider, which give an LLM varying levels of control. On one extreme, a router allows an LLM to select a single step from a specified set of options and, on the other extreme, a fully autonomous long-running agent may have complete freedom to select any sequence of steps that it wants for a given problem. ![Agent Types](img/agent_types.png) @@ -55,4 +55,4 @@ Once you've built a graph, you often want to test and debug it. [LangGraph Studi ## Deployment -Once you have confidence in your LangGraph application, many developers want an easy path to deployment. [LangGraph Cloud](../cloud/index.md) is an opinionated, simple way to deploy LangGraph objects from the LangChain team. Of course, you can also use services like [FastAPI](https://fastapi.tiangolo.com/) and call your graph from inside the FastAPI server as you see fit. \ No newline at end of file +Once you have confidence in your LangGraph application, many developers want an easy path to deployment. [LangGraph Platform](../concepts/index.md#langgraph-platform) offers a range of options for deploying LangGraph graphs. \ No newline at end of file diff --git a/docs/docs/concepts/img/assistants.png b/docs/docs/concepts/img/assistants.png new file mode 100644 index 000000000..0da78a031 Binary files /dev/null and b/docs/docs/concepts/img/assistants.png differ diff --git a/docs/docs/concepts/img/byoc_architecture.png b/docs/docs/concepts/img/byoc_architecture.png new file mode 100644 index 000000000..97bb2db1d Binary files /dev/null and b/docs/docs/concepts/img/byoc_architecture.png differ diff --git a/docs/docs/concepts/img/double_texting.png b/docs/docs/concepts/img/double_texting.png new file mode 100644 index 000000000..a16a58ce2 Binary files /dev/null and b/docs/docs/concepts/img/double_texting.png differ diff --git a/docs/docs/cloud/concepts/langgraph_cloud_architecture.png b/docs/docs/concepts/img/langgraph_cloud_architecture.png similarity index 100% rename from docs/docs/cloud/concepts/langgraph_cloud_architecture.png rename to docs/docs/concepts/img/langgraph_cloud_architecture.png diff --git a/docs/docs/concepts/img/lg_platform.png b/docs/docs/concepts/img/lg_platform.png new file mode 100644 index 000000000..de54cedc5 Binary files /dev/null and b/docs/docs/concepts/img/lg_platform.png differ diff --git a/docs/docs/concepts/img/multi_agent/architectures.png b/docs/docs/concepts/img/multi_agent/architectures.png new file mode 100644 index 000000000..9a45e8389 Binary files /dev/null and b/docs/docs/concepts/img/multi_agent/architectures.png differ diff --git a/docs/docs/concepts/img/multi_agent/collaboration.png b/docs/docs/concepts/img/multi_agent/collaboration.png deleted file mode 100644 index 3da47ba9b..000000000 Binary files a/docs/docs/concepts/img/multi_agent/collaboration.png and /dev/null differ diff --git a/docs/docs/concepts/img/multi_agent/hierarchical.png b/docs/docs/concepts/img/multi_agent/hierarchical.png deleted file mode 100644 index 3cee3b663..000000000 Binary files a/docs/docs/concepts/img/multi_agent/hierarchical.png and /dev/null differ diff --git a/docs/docs/concepts/img/multi_agent/request.png b/docs/docs/concepts/img/multi_agent/request.png new file mode 100644 index 000000000..8a5f5a16f Binary files /dev/null and b/docs/docs/concepts/img/multi_agent/request.png differ diff --git a/docs/docs/concepts/img/multi_agent/response.png b/docs/docs/concepts/img/multi_agent/response.png new file mode 100644 index 000000000..5f2c08440 Binary files /dev/null and b/docs/docs/concepts/img/multi_agent/response.png differ diff --git a/docs/docs/concepts/img/multi_agent/subgraph.png b/docs/docs/concepts/img/multi_agent/subgraph.png deleted file mode 100644 index 29401933c..000000000 Binary files a/docs/docs/concepts/img/multi_agent/subgraph.png and /dev/null differ diff --git a/docs/docs/concepts/img/multi_agent/supervisor.png b/docs/docs/concepts/img/multi_agent/supervisor.png deleted file mode 100644 index 898f753b0..000000000 Binary files a/docs/docs/concepts/img/multi_agent/supervisor.png and /dev/null differ diff --git a/docs/docs/concepts/index.md b/docs/docs/concepts/index.md new file mode 100644 index 000000000..10b4f0009 --- /dev/null +++ b/docs/docs/concepts/index.md @@ -0,0 +1,76 @@ +--- +hide: + - navigation +title: Concepts +description: Conceptual Guide for LangGraph +--- + +# Conceptual Guide + +This guide provides explanations of the key concepts behind the LangGraph framework and AI applications more broadly. + +We recommend that you go through at least the [Quick Start](../tutorials/introduction.ipynb) before diving into the conceptual guide. This will provide practical context that will make it easier to understand the concepts discussed here. + +The conceptual guide does not cover step-by-step instructions or specific implementation examples — those are found in the [Tutorials](../tutorials/index.md) and [How-to guides](../how-tos/index.md). For detailed reference material, please see the [API reference](../reference/index.md). + +## LangGraph + +**High Level** + +- [Why LangGraph?](high_level.md): A high-level overview of LangGraph and its goals. + +**Concepts** + +- [LangGraph Glossary](low_level.md): LangGraph workflows are designed as graphs, with nodes representing different components and edges representing the flow of information between them. This guide provides an overview of the key concepts associated with LangGraph graph primitives. +- [Common Agentic Patterns](agentic_concepts.md): An agent uses an LLM to pick its own control flow to solve more complex problems! Agents are a key building block in many LLM applications. This guide explains the different types of agent architectures and how they can be used to control the flow of an application. +- [Multi-Agent Systems](multi_agent.md): Complex LLM applications can often be broken down into multiple agents, each responsible for a different part of the application. This guide explains common patterns for building multi-agent systems. +- [Human-in-the-Loop](human_in_the_loop.md): Explains different ways of integrating human feedback into a LangGraph application. +- [Persistence](persistence.md): LangGraph has a built-in persistence layer, implemented through checkpointers. This persistence layer helps to support powerful capabilities like human-in-the-loop, memory, time travel, and fault-tolerance. +- [Memory](memory.md): Memory in AI applications refers to the ability to process, store, and effectively recall information from past interactions. With memory, your agents can learn from feedback and adapt to users' preferences. +- [Streaming](streaming.md): Streaming is crucial for enhancing the responsiveness of applications built on LLMs. By displaying output progressively, even before a complete response is ready, streaming significantly improves user experience (UX), particularly when dealing with the latency of LLMs. +- [FAQ](faq.md): Frequently asked questions about LangGraph. + +## LangGraph Platform + +LangGraph Platform is a commercial solution for deploying agentic applications in production, built on the open-source LangGraph framework. + +The LangGraph Platform offers a few different deployment options described in the [deployment options guide](./deployment_options.md). + + +!!! tip + + * LangGraph is an MIT-licensed open-source library, which we are committed to maintaining and growing for the community. + * You can always deploy LangGraph applications on your own infrastructure using the open-source LangGraph project without using LangGraph Platform. + +### High Level + +- [Why LangGraph Platform?](./langgraph_platform.md): The LangGraph platform is an opinionated way to deploy and manage LangGraph applications. This guide provides an overview of the key features and concepts behind LangGraph Platform. +- [Deployment Options](./deployment_options.md): LangGraph Platform offers four deployment options: [Self-Hosted Lite](./self_hosted.md#self-hosted-lite), [Self-Hosted Enterprise](./self_hosted.md#self-hosted-enterprise), [bring your own cloud (BYOC)](./bring_your_own_cloud.md), and [Cloud SaaS](./langgraph_cloud.md). This guide explains the differences between these options, and which Plans they are available on. +- [Plans](./plans.md): LangGraph Platforms offer three different plans: Developer, Plus, Enterprise. This guide explains the differences between these options, what deployment options are available for each, and how to sign up for each one. +- [Template Applications](./template_applications.md): Reference applications designed to help you get started quickly when building with LangGraph. + +### Components + +The LangGraph Platform comprises several components that work together to support the deployment and management of LangGraph applications: + +- [LangGraph Server](./langgraph_server.md): The LangGraph Server is designed to support a wide range of agentic application use cases, from background processing to real-time interactions. +- [LangGraph Studio](./langgraph_studio.md): LangGraph Studio is a specialized IDE that can connect to a LangGraph Server to enable visualization, interaction, and debugging of the application locally. +- [LangGraph CLI](./langgraph_cli.md): LangGraph CLI is a command-line interface that helps to interact with a local LangGraph +- [Python/JS SDK](./sdk.md): The Python/JS SDK provides a programmatic way to interact with deployed LangGraph Applications. +- [Remote Graph](../how-tos/use-remote-graph.md): A RemoteGraph allows you to interact with any deployed LangGraph application as though it were running locally. + +### LangGraph Server + +- [Application Structure](./application_structure.md): A LangGraph application consists of one or more graphs, a LangGraph API Configuration file (`langgraph.json`), a file that specifies dependencies, and environment variables. +- [Assistants](./assistants.md): Assistants are a way to save and manage different configurations of your LangGraph applications. +- [Web-hooks](./langgraph_server.md#webhooks): Webhooks allow your running LangGraph application to send data to external services on specific events. +- [Cron Jobs](./langgraph_server.md#cron-jobs): Cron jobs are a way to schedule tasks to run at specific times in your LangGraph application. +- [Double Texting](./double_texting.md): Double texting is a common issue in LLM applications where users may send multiple messages before the graph has finished running. This guide explains how to handle double texting with LangGraph Deploy. + +### Deployment Options + + +- [Self-Hosted Lite](./self_hosted.md): A free (up to 1 million nodes executed), limited version of LangGraph Platform that you can run locally or in a self-hosted manner +- [Cloud SaaS](./langgraph_cloud.md): Hosted as part of LangSmith. +- [Bring Your Own Cloud](./bring_your_own_cloud.md): We manage the infrastructure, so you don't have to, but the infrastructure all runs within your cloud. +- [Self-Hosted Enterprise](./self_hosted.md): Completely managed by you. \ No newline at end of file diff --git a/docs/docs/concepts/langgraph_cli.md b/docs/docs/concepts/langgraph_cli.md new file mode 100644 index 000000000..0b930b45b --- /dev/null +++ b/docs/docs/concepts/langgraph_cli.md @@ -0,0 +1,62 @@ +# LangGraph CLI + +!!! info "Prerequisites" + - [LangGraph Platform](./langgraph_platform.md) + - [LangGraph Server](./langgraph_server.md) + +The LangGraph CLI is a multi-platform command-line tool for building and running the [LangGraph API server](./langgraph_server.md) locally. This offers an alternative to the [LangGraph Studio desktop app](./langgraph_studio.md) for developing and testing agents across all major operating systems (Linux, Windows, MacOS). The resulting server includes all API endpoints for your graph's runs, threads, assistants, etc. as well as the other services required to run your agent, including a managed database for checkpointing and storage. + +## Installation + +The LangGraph CLI can be installed via Homebrew (on macOS) or pip: + +=== "Homebrew" + ```bash + brew install langgraph-cli + ``` + +=== "pip" + ```bash + pip install langgraph-cli + ``` + +## Commands + +The CLI provides the following core functionality: + +### `build` + +The `langgraph build` command builds a Docker image for the [LangGraph API server](./langgraph_server.md) that can be directly deployed. + +### `dev` + +!!! note "New in version 0.1.55" + The `langgraph dev` command was introduced in langgraph-cli version 0.1.55. + +The `langgraph dev` command starts a lightweight development server that requires no Docker installation. This server is ideal for rapid development and testing, with features like: + +- Hot reloading: Changes to your code are automatically detected and reloaded +- Debugger support: Attach your IDE's debugger for line-by-line debugging +- In-memory state with local persistence: Server state is stored in memory for speed but persisted locally between restarts + +To use this command, you need to install the CLI with the "inmem" extra: + +```bash +pip install -U "langgraph-cli[inmem]" +``` + +**Note**: This command is intended for local development and testing only. It is not recommended for production use. Since it does not use Docker, we recommend using virtual environments to manage your project's dependencies. + +### `up` + +The `langgraph up` command starts an instance of the [LangGraph API server](./langgraph_server.md) locally in a docker container. This requires thedocker server to be running locally. It also requires a LangSmith API key for local development or a license key for production use. + +The server includes all API endpoints for your graph's runs, threads, assistants, etc. as well as the other services required to run your agent, including a managed database for checkpointing and storage. + +### `dockerfile` + +The `langgraph dockerfile` command generates a [Dockerfile](https://docs.docker.com/reference/dockerfile/) that can be used to build images for and deploy instances of the [LangGraph API server](./langgraph_server.md). This is useful if you want to further customize the dockerfile or deploy in a more custom way. + +## Related + +- [LangGraph CLI API Reference](../cloud/reference/cli.md) diff --git a/docs/docs/concepts/langgraph_cloud.md b/docs/docs/concepts/langgraph_cloud.md new file mode 100644 index 000000000..371e8bb47 --- /dev/null +++ b/docs/docs/concepts/langgraph_cloud.md @@ -0,0 +1,38 @@ +# Cloud SaaS + +!!! info "Prerequisites" + - [LangGraph Platform](./langgraph_platform.md) + - [LangGraph Server](./langgraph_server.md) + +## Overview + +LangGraph's Cloud SaaS is a managed service for deploying LangGraph APIs, regardless of its definition or dependencies. The service offers managed implementations of checkpointers and stores, allowing you to focus on building the right cognitive architecture for your use case. By handling scalable & secure infrastructure, LangGraph Cloud offers the fastest path to getting your LangGraph API deployed to production. + +## Deployment + +A **deployment** is an instance of a LangGraph API. A single deployment can have many [revisions](#revision). When a deployment is created, all the necessary infrastructure (e.g. database, containers, secrets store) are automatically provisioned. See the [architecture diagram](#architecture) below for more details. + +See the [how-to guide](../cloud/deployment/cloud.md#create-new-deployment) for creating a new deployment. + +## Revision + +A revision is an iteration of a [deployment](#deployment). When a new deployment is created, an initial revision is automatically created. To deploy new code changes or update environment variable configurations for a deployment, a new revision must be created. When a revision is created, a new container image is built automatically. + +See the [how-to guide](../cloud/deployment/cloud.md#create-new-revision) for creating a new revision. + +## Asynchronous Deployment + +Infrastructure for [deployments](#deployment) and [revisions](#revision) are provisioned and deployed asynchronously. They are not deployed immediately after submission. Currently, deployment can take up to several minutes. + +## Architecture + +!!! warning "Subject to Change" +The Cloud SaaS deployment architecture may change in the future. + +A high-level diagram of a Cloud SaaS deployment. + +![diagram](img/langgraph_cloud_architecture.png) + +## Related + +- [Deployment Options](./deployment_options.md) diff --git a/docs/docs/concepts/langgraph_platform.md b/docs/docs/concepts/langgraph_platform.md new file mode 100644 index 000000000..8b2e6b61c --- /dev/null +++ b/docs/docs/concepts/langgraph_platform.md @@ -0,0 +1,62 @@ +# LangGraph Platform + +## Overview + +LangGraph Platform is a commercial solution for deploying agentic applications to production, built on the open-source [LangGraph framework](./high_level.md). + +The LangGraph Platform consists of several components that work together to support the development, deployment, debugging, and monitoring of LangGraph applications: + +- [LangGraph Server](./langgraph_server.md): The server defines an opinionated API and architecture that incorporates best practices for deploying agentic applications, allowing you to focus on building your agent logic rather than developing server infrastructure. +- [LangGraph Studio](./langgraph_studio.md): LangGraph Studio is a specialized IDE that can connect to a LangGraph Server to enable visualization, interaction, and debugging of the application locally. +- [LangGraph CLI](./langgraph_cli.md): LangGraph CLI is a command-line interface that helps to interact with a local LangGraph +- [Python/JS SDK](./sdk.md): The Python/JS SDK provides a programmatic way to interact with deployed LangGraph Applications. +- [Remote Graph](../how-tos/use-remote-graph.md): A RemoteGraph allows you to interact with any deployed LangGraph application as though it were running locally. + +![](img/lg_platform.png) + +The LangGraph Platform offers a few different deployment options described in the [deployment options guide](./deployment_options.md). + +## Why Use LangGraph Platform? + +LangGraph Platform is designed to make deploying agentic applications seamless and production-ready. + +For simpler applications, deploying a LangGraph agent can be as straightforward as using your own server logic—for example, setting up a FastAPI endpoint and invoking LangGraph directly. + +### Option 1: Deploying with Custom Server Logic + +For basic LangGraph applications, you may choose to handle deployment using your custom server infrastructure. Setting up endpoints with frameworks like [FastAPI](https://fastapi.tiangolo.com/) allows you to quickly deploy and run LangGraph as you would any other Python application: + +```python +from fastapi import FastAPI +from your_agent_package import graph + +app = FastAPI() + +@app.get("/foo") +async def foo(...): + return await graph.ainvoke({...}) +``` + +This approach works well for simple applications with straightforward needs and provides you with full control over the deployment setup. For example, you might use this for a single-assistant application that doesn’t require long-running sessions or persistent memory. + +### Option 2: Leveraging LangGraph Platform for Complex Deployments + +As your applications scale or add complex features, the deployment requirements often evolve. Running an application with more nodes, longer processing times, or a need for persistent memory can introduce challenges that quickly become time-consuming and difficult to manage manually. [LangGraph Platform](./langgraph_platform.md) is built to handle these challenges seamlessly, allowing you to focus on agent logic rather than server infrastructure. + +Here are some common issues that arise in complex deployments, which LangGraph Platform addresses: + +- **[Streaming Support](streaming.md)**: As agents grow more sophisticated, they often benefit from streaming both token outputs and intermediate states back to the user. Without this, users are left waiting for potentially long operations with no feedback. LangGraph Server provides [multiple streaming modes](streaming.md) optimized for various application needs. + +- **Background Runs**: For agents that take longer to process (e.g., hours), maintaining an open connection can be impractical. The LangGraph Server supports launching agent runs in the background and provides both polling endpoints and webhooks to monitor run status effectively. + +- **Support for long runs**: Vanilla server setups often encounter timeouts or disruptions when handling requests that take a long time to complete. LangGraph Server’s API provides robust support for these tasks by sending regular heartbeat signals, preventing unexpected connection closures during prolonged processes. + +- **Handling Burstiness**: Certain applications, especially those with real-time user interaction, may experience "bursty" request loads where numerous requests hit the server simultaneously. LangGraph Server includes a task queue, ensuring requests are handled consistently without loss, even under heavy loads. + +- **[Double Texting](double_texting.md)**: In user-driven applications, it’s common for users to send multiple messages rapidly. This “double texting” can disrupt agent flows if not handled properly. LangGraph Server offers built-in strategies to address and manage such interactions. + +- **[Checkpointers and Memory Management](persistence.md#checkpoints)**: For agents needing persistence (e.g., conversation memory), deploying a robust storage solution can be complex. LangGraph Platform includes optimized [checkpointers](persistence.md#checkpoints) and a [memory store](persistence.md#memory-store), managing state across sessions without the need for custom solutions. + +- **[Human-in-the-loop Support](human_in_the_loop.md)**: In many applications, users require a way to intervene in agent processes. LangGraph Server provides specialized endpoints for human-in-the-loop scenarios, simplifying the integration of manual oversight into agent workflows. + +By using LangGraph Platform, you gain access to a robust, scalable deployment solution that mitigates these challenges, saving you the effort of implementing and maintaining them manually. This allows you to focus more on building effective agent behavior and less on solving deployment infrastructure issues. diff --git a/docs/docs/concepts/langgraph_server.md b/docs/docs/concepts/langgraph_server.md new file mode 100644 index 000000000..cb2d3948c --- /dev/null +++ b/docs/docs/concepts/langgraph_server.md @@ -0,0 +1,130 @@ +# LangGraph Server + +!!! info "Prerequisites" + - [LangGraph Platform](./langgraph_platform.md) + - [LangGraph Glossary](low_level.md) + +## Overview + +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. + +## Key Features + +The LangGraph Platform incorporates best practices for agent deployment, so you can focus on building your agent logic. + +* **Streaming endpoints**: Endpoints that expose [multiple different streaming modes](streaming.md). We've made these work even for long-running agents that may go minutes between consecutive stream events. +* **Background runs**: The LangGraph Server supports launching assistants in the background with endpoints for polling the status of the assistant's run and webhooks to monitor run status effectively. +- **Support for long runs**: Our blocking endpoints for running assistants send regular heartbeat signals, preventing unexpected connection closures when handling requests that take a long time to complete. +* **Task queue**: We've added a task queue to make sure we don't drop any requests if they arrive in a bursty nature. +* **Horizontally scalable infrastructure**: LangGraph Server is designed to be horizontally scalable, allowing you to scale up and down your usage as needed. +* **Double texting support**: Many times users might interact with your graph in unintended ways. For instance, a user may send one message and before the graph has finished running send a second message. We call this ["double texting"](double_texting.md) and have added four different ways to handle this. +* **Optimized checkpointer**: LangGraph Platform comes with a built-in [checkpointer](./persistence.md#checkpoints) optimized for LangGraph applications. +* **Human-in-the-loop endpoints**: We've exposed all endpoints needed to support [human-in-the-loop](human_in_the_loop.md) features. +* **Memory**: In addition to thread-level persistence (covered above by [checkpointers]l(./persistence.md#checkpoints)), LangGraph Platform also comes with a built-in [memory store](persistence.md#memory-store). +* **Cron jobs**: Built-in support for scheduling tasks, enabling you to automate regular actions like data clean-up or batch processing within your applications. +* **Webhooks**: Allows your application to send real-time notifications and data updates to external systems, making it easy to integrate with third-party services and trigger actions based on specific events. +* **Monitoring**: LangGraph Server integrates seamlessly with the [LangSmith](https://docs.smith.langchain.com/) monitoring platform, providing real-time insights into your application's performance and health. + +## What are you deploying? + +When you deploy a LangGraph Server, you are deploying one or more [graphs](#graphs), a database for [persistence](persistence.md), and a task queue. + +### Graphs + +When you deploy a graph with LangGraph Server, you are deploying a "blueprint" for an [Assistant](assistants.md). + +An [Assistant](assistants.md) is a graph paired with specific configuration settings. You can create multiple assistants per graph, each with unique settings to accommodate different use cases +that can be served by the same graph. + +Upon deployment, LangGraph Server will automatically create a default assistant for each graph using the graph's default configuration settings. + +You can interact with assistants through the [LangGraph Server API](#langgraph-server-api). + +!!! note + + We often think of a graph as implementing an [agent](agentic_concepts.md), but a graph does not necessarily need to implement an agent. For example, a graph could implement a simple + chatbot that only supports back-and-forth conversation, without the ability to influence any application control flow. In reality, as applications get more complex, a graph will often implement a more complex flow that may use [multiple agents](./multi_agent.md) working in tandem. + +### Persistence and Task Queue + +The LangGraph Server leverages a database for [persistence](persistence.md) and a task queue. + +Currently, only [Postgres](https://www.postgresql.org/) is supported as a database for LangGraph Server and [Redis](https://redis.io/) as the task queue. + +If you're deploying using [LangGraph Cloud](./langgraph_cloud.md), these components are managed for you. If you're deploying LangGraph Server on your own infrastructure, you'll need to set up and manage these components yourself. + +Please review the [deployment options](./deployment_options.md) guide for more information on how these components are set up and managed. + +## Application Structure + +To deploy a LangGraph Server application, you need to specify the graph(s) you want to deploy, as well as any relevant configuration settings, such as dependencies and environment variables. + +Read the [application structure](./application_structure.md) guide to learn how to structure your LangGraph application for deployment. + +## LangGraph Server API + +The LangGraph Server API allows you to create and manage [assistants](assistants.md), [threads](#threads), [runs](#runs), [cron jobs](#cron-jobs), and more. + +The [LangGraph Cloud API Reference](../cloud/reference/api/api_ref.html) provides detailed information on the API endpoints and data models. + +### Assistants + +An [Assistant](assistants.md) refers to a [graph](#graphs) plus specific [configuration](low_level.md#configuration) settings for that graph. + +You can think of an assistant as a saved configuration of an [agent](agentic_concepts.md). + +When building agents, it is fairly common to make rapid changes that *do not* alter the graph logic. For example, simply changing prompts or the LLM selection can have significant impacts on the behavior of the agents. Assistants offer an easy way to make and save these types of changes to agent configuration. + +### Threads + +A thread contains the accumulated state of a sequence of [runs](#runs). If a run is executed on a thread, then the [state](low_level.md#state) of the underlying graph of the assistant will be persisted to the thread. + +A thread's current and historical state can be retrieved. To persist state, a thread must be created prior to executing a run. + +The state of a thread at a particular point in time is called a [checkpoint](persistence.md#checkpoints). Checkpoints can be used to restore the state of a thread at a later time. + +For more on threads and checkpoints, see this section of the [LangGraph conceptual guide](low_level.md#persistence). + +The LangGraph Cloud API provides several endpoints for creating and managing threads and thread state. See the [API reference](../cloud/reference/api/api_ref.html#tag/threads) for more details. + +### Runs + +A run is an invocation of an [assistant](#assistants). Each run may have its own input, configuration, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a [thread](#threads). + +The LangGraph Cloud API provides several endpoints for creating and managing runs. See the [API reference](../cloud/reference/api/api_ref.html#tag/thread-runs/) for more details. + +### Store + +Store is an API for managing persistent [key-value store](./persistence.md#memory-store) that is available from any [thread](#threads). + +Stores are useful for implementing [memory](./memory.md) in your LangGraph application. + +### Cron Jobs + +There are many situations in which it is useful to run an assistant on a schedule. + +For example, say that you're building an assistant that runs daily and sends an email summary +of the day's news. You could use a cron job to run the assistant every day at 8:00 PM. + +LangGraph Cloud supports cron jobs, which run on a user-defined schedule. The user specifies a schedule, an assistant, and some input. After that, on the specified schedule, the server will: + +- Create a new thread with the specified assistant +- Send the specified input to that thread + +Note that this sends the same input to the thread every time. See the [how-to guide](../cloud/how-tos/cron_jobs.md) for creating cron jobs. + +The LangGraph Cloud API provides several endpoints for creating and managing cron jobs. See the [API reference](../cloud/reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/crons) for more details. + +### Webhooks + +Webhooks enable event-driven communication from your LangGraph Cloud application to external services. For example, you may want to issue an update to a separate service once an API call to LangGraph Cloud has finished running. + +Many LangGraph Cloud endpoints accept a `webhook` parameter. If this parameter is specified by a an endpoint that can accept POST requests, LangGraph Cloud will send a request at the completion of a run. + +See the corresponding [how-to guide](../cloud/how-tos/webhooks.md) for more detail. + +## Related + +* LangGraph [Application Structure](./application_structure.md) guide explains how to structure your LangGraph application for deployment. +* [How-to guides for the LangGraph Platform](../how-tos/index.md). +* The [LangGraph Cloud API Reference](../cloud/reference/api/api_ref.html) provides detailed information on the API endpoints and data models. diff --git a/docs/docs/cloud/faq/studio.md b/docs/docs/concepts/langgraph_studio.md similarity index 58% rename from docs/docs/cloud/faq/studio.md rename to docs/docs/concepts/langgraph_studio.md index 232699315..f4849e6db 100644 --- a/docs/docs/cloud/faq/studio.md +++ b/docs/docs/concepts/langgraph_studio.md @@ -1,39 +1,86 @@ -# Studio FAQs +# LangGraph Studio -## Why is my project failing to start? +!!! info "Prerequisites" + + - [LangGraph Platform](./langgraph_platform.md) + - [LangGraph Server](./langgraph_server.md) + +LangGraph Studio offers a new way to develop LLM applications by providing a specialized agent IDE that enables visualization, interaction, and debugging of complex agentic applications. + +With visual graphs and the ability to edit state, you can better understand agent workflows and iterate faster. LangGraph Studio integrates with LangSmith allowing you to collaborate with teammates to debug failure modes. + +![](img/lg_studio.png) + +## Features + +The key features of LangGraph Studio are: + +- Visualizes your graph +- Test your graph by running it from the UI +- Debug your agent by [modifying its state and rerunning](human_in_the_loop.md) +- Create and manage [assistants](assistants.md) +- View and manage [threads](persistence.md#threads) +- View and manage [long term memory](memory.md) +- Add node input/outputs to [LangSmith](https://smith.langchain.com/) datasets for testing + +## Types + +### Desktop app + +LangGraph Studio is available as a [desktop app](https://studio.langchain.com/) for MacOS users. + +While in Beta, LangGraph Studio is available for free to all [LangSmith](https://smith.langchain.com/) users on any plan tier. + +### Cloud studio + +If you have deployed your LangGraph application on LangGraph Platform (Cloud), you can access the studio as part of that + +### Development server + +LangGraph CLI also contains a command for running an in-memory development server that can be used to connect a local LangGraph app with the studio. +See [instructions here](../cloud/reference/cli.md#dev) for more information. + +The way this works is that it runs inside your local environment. +It will spin up an in-memory, development server to deploy the graph. +You can then connect to the studio via the Cloud hosted version of LangGraph Platform. +To be clear, the web studio will connect to your locally running server - your agent is still running locally and never leaves your device. + +## Studio FAQs + +### Why is my project failing to start? There are a few reasons that your project might fail to start, here are some of the most common ones. -### Docker issues +#### Docker issues (desktop only) -LangGraph Studio requires Docker Desktop version 4.24 or higher. Please make sure you have a version of Docker installed that satisfies that requirement and also make sure you have the Docker Desktop app up and running before trying to use LangGraph Studio. In addition, make sure you have docker-compose updated to version 2.22.0 or higher. +LangGraph Studio (desktop) requires Docker Desktop version 4.24 or higher. Please make sure you have a version of Docker installed that satisfies that requirement and also make sure you have the Docker Desktop app up and running before trying to use LangGraph Studio. In addition, make sure you have docker-compose updated to version 2.22.0 or higher. -### Configuration or environment issues +#### Configuration or environment issues Another reason your project might fail to start is because your configuration file is defined incorrectly, or you are missing required environment variables. -## How does interrupt work? +### How does interrupt work? When you select the `Interrupts` dropdown and select a node to interrupt the graph will pause execution before and after (unless the node goes straight to `END`) that node has run. This means that you will be able to both edit the state before the node is ran and the state after the node has ran. This is intended to allow developers more fine-grained control over the behavior of a node and make it easier to observe how the node is behaving. You will not be able to edit the state after the node has ran if the node is the final node in the graph. -## How do I reload the app? +### How do I reload the app? (desktop only) If you would like to reload the app, don't use Command+R as you might normally do. Instead, close and reopen the app for a full refresh. -## How does automatic rebuilding work? +### How does automatic rebuilding work? (desktop only) One of the key features of LangGraph Studio is that it automatically rebuilds your image when you change the source code. This allows for a super fast development and testing cycle which makes it easy to iterate on your graph. There are two different ways that LangGraph rebuilds your image: either by editing the image or completely rebuilding it. -### Rebuilds from source code changes +#### Rebuilds from source code changes If you modified the source code only (no configuration or dependency changes!) then the image does not require a full rebuild, and LangGraph Studio will only update the relevant parts. The UI status in the bottom left will switch from `Online` to `Stopping` temporarily while the image gets edited. The logs will be shown as this process is happening, and after the image has been edited the status will change back to `Online` and you will be able to run your graph with the modified code! -### Rebuilds from configuration or dependency changes +#### Rebuilds from configuration or dependency changes If you edit your graph configuration file (`langgraph.json`) or the dependencies (either `pyproject.toml` or `requirements.txt`) then the entire image will be rebuilt. This will cause the UI to switch away from the graph view and start showing the logs of the new image building process. This can take a minute or two, and once it is done your updated image will be ready to use! -## Why is my graph taking so long to startup? +### Why is my graph taking so long to startup? (desktop only) The LangGraph Studio interacts with a local LangGraph API server. To stay aligned with ongoing updates, the LangGraph API requires regular rebuilding. As a result, you may occasionally experience slight delays when starting up your project. @@ -71,3 +118,9 @@ def routing_function(state: GraphState) -> Literal["node_b","node_c"]: return "node_c" ``` + +## Related + +For more information please see the following: + +* [LangGraph Studio how-to guides](../how-tos/index.md#langgraph-studio) \ No newline at end of file diff --git a/docs/docs/concepts/low_level.md b/docs/docs/concepts/low_level.md index 30776ac77..05ceacdea 100644 --- a/docs/docs/concepts/low_level.md +++ b/docs/docs/concepts/low_level.md @@ -20,7 +20,7 @@ A super-step can be considered a single iteration over the graph nodes. Nodes th ### StateGraph -The `StateGraph` class is the main graph class to uses. This is parameterized by a user defined `State` object. +The `StateGraph` class is the main graph class to use. This is parameterized by a user defined `State` object. ### MessageGraph @@ -52,12 +52,12 @@ By default, the graph will have the same input and output schemas. If you want t Typically, all graph nodes communicate with a single schema. This means that they will read and write to the same state channels. But, there are cases where we want more control over this: -* Internal nodes can pass information that is not required in the graph's input / output. -* We may also want to use different input / output schemas for the graph. The output might, for example, only contain a single relevant output key. +- Internal nodes can pass information that is not required in the graph's input / output. +- We may also want to use different input / output schemas for the graph. The output might, for example, only contain a single relevant output key. -It is possible to have nodes write to private state channels inside the graph for internal node communication. We can simply define a private schema, `PrivateState`. See [this notebook](../how-tos/pass_private_state.ipynb) for more detail. +It is possible to have nodes write to private state channels inside the graph for internal node communication. We can simply define a private schema, `PrivateState`. See [this notebook](../how-tos/pass_private_state.ipynb) for more detail. -It is also possible to define explicit input and output schemas for a graph. In these cases, we define an "internal" schema that contains *all* keys relevant to graph operations. But, we also define `input` and `output` schemas that are sub-sets of the "internal" schema to constrain the input and output of the graph. See [this notebook](../how-tos/input_output_schema.ipynb) for more detail. +It is also possible to define explicit input and output schemas for a graph. In these cases, we define an "internal" schema that contains _all_ keys relevant to graph operations. But, we also define `input` and `output` schemas that are sub-sets of the "internal" schema to constrain the input and output of the graph. See [this notebook](../how-tos/input_output_schema.ipynb) for more detail. Let's look at an example: @@ -101,11 +101,12 @@ graph = builder.compile() graph.invoke({"user_input":"My"}) {'graph_output': 'My name is Lance'} ``` + There are two subtle and important points to note here: -1. We pass `state: InputState` as the input schema to `node_1`. But, we write out to `foo`, a channel in `OverallState`. How can we write out to a state channel that is not included in the input schema? This is because a node *can write to any state channel in the graph state.* The graph state is the union of of the state channels defined at initialization, which includes `OverallState` and the filters `InputState` and `OutputState`. +1. We pass `state: InputState` as the input schema to `node_1`. But, we write out to `foo`, a channel in `OverallState`. How can we write out to a state channel that is not included in the input schema? This is because a node _can write to any state channel in the graph state._ The graph state is the union of of the state channels defined at initialization, which includes `OverallState` and the filters `InputState` and `OutputState`. -2. We initialize the graph with `StateGraph(OverallState,input=InputState,output=OutputState)`. So, how can we write to `PrivateState` in `node_2`? How does the graph gain access to this schema if it was not passed in the `StateGraph` initialization? We can do this because *nodes can also declare additional state channels* as long as the state schema definition exists. In this case, the `PrivateState` schema is defined, so we can add `bar` as a new state channel in the graph and write to it. +2. We initialize the graph with `StateGraph(OverallState,input=InputState,output=OutputState)`. So, how can we write to `PrivateState` in `node_2`? How does the graph gain access to this schema if it was not passed in the `StateGraph` initialization? We can do this because _nodes can also declare additional state channels_ as long as the state schema definition exists. In this case, the `PrivateState` schema is defined, so we can add `bar` as a new state channel in the graph and write to it. ### Reducers @@ -323,7 +324,7 @@ graph.add_conditional_edges("node_a", continue_to_jokes) ## Persistence -LangGraph provides built-in persistence for your agent's state using [checkpointers][langgraph.checkpoint.base.BaseCheckpointSaver]. Checkpointers save snapshots of the graph state at every superstep, allowing resumption at any time. This enables features like human-in-the-loop interactions, memory management, and fault-tolerance. You can even directly manipulate a graph's state after its execution using the +LangGraph provides built-in persistence for your agent's state using [checkpointers][langgraph.checkpoint.base.BaseCheckpointSaver]. Checkpointers save snapshots of the graph state at every superstep, allowing resumption at any time. This enables features like human-in-the-loop interactions, memory management, and fault-tolerance. You can even directly manipulate a graph's state after its execution using the appropriate `get` and `update` methods. For more details, see the [persistence conceptual guide](./persistence.md). ## Threads @@ -390,7 +391,7 @@ Read [this how-to](https://langchain-ai.github.io/langgraph/how-tos/recursion-li It can often be useful to set breakpoints before or after certain nodes execute. This can be used to wait for human approval before continuing. These can be set when you ["compile" a graph](#compiling-your-graph). You can set breakpoints either _before_ a node executes (using `interrupt_before`) or after a node executes (using `interrupt_after`.) -You **MUST** use a [checkpoiner](./persistence.md) when using breakpoints. This is because your graph needs to be able to resume execution. +You **MUST** use a [checkpointer](./persistence.md) when using breakpoints. This is because your graph needs to be able to resume execution. In order to resume execution, you can just invoke your graph with `None` as the input. @@ -416,10 +417,112 @@ def my_node(state: State) -> State: return state ``` +## Subgraphs + +A subgraph is a [graph](#graphs) that is used as a [node](#nodes) in another graph. This is nothing more than the age-old concept of encapsulation, applied to LangGraph. Some reasons for using subgraphs are: + +- building [multi-agent systems](./multi_agent.md) + +- when you want to reuse a set of nodes in multiple graphs, which maybe share some state, you can define them once in a subgraph and then use them in multiple parent graphs + +- when you want different teams to work on different parts of the graph independently, you can define each part as a subgraph, and as long as the subgraph interface (the input and output schemas) is respected, the parent graph can be built without knowing any details of the subgraph + +There are two ways to add subgraphs to a parent graph: + +- add a node with the compiled subgraph: this is useful when the parent graph and the subgraph share state keys and you don't need to transform state on the way in or out + +```python +builder.add_node("subgraph", subgraph_builder.compile()) +``` + +- add a node with a function that invokes the subgraph: this is useful when the parent graph and the subgraph have different state schemas and you need to transform state before or after calling the subgraph + +```python +subgraph = subgraph_builder.compile() + +def call_subgraph(state: State): + return subgraph.invoke({"subgraph_key": state["parent_key"]}) + +builder.add_node("subgraph", call_subgraph) +``` + +Let's take a look at examples for each. + +### As a compiled graph + +The simplest way to create subgraph nodes is by using a [compiled subgraph](#compiling-your-graph) directly. When doing so, it is **important** that the parent graph and the subgraph [state schemas](#state) share at least one key which they can use to communicate. If your graph and subgraph do not share any keys, you should use write a function [invoking the subgraph](#as-a-function) instead. + +!!! Note + If you pass extra keys to the subgraph node (i.e., in addition to the shared keys), they will be ignored by the subgraph node. Similarly, if you return extra keys from the subgraph, they will be ignored by the parent graph. + +```python +from langgraph.graph import START, StateGraph +from typing import TypedDict + +class State(TypedDict): + foo: str + +class SubgraphState(TypedDict): + foo: str # note that this key is shared with the parent graph state + bar: str + +# Define subgraph +def subgraph_node(state: SubgraphState): + # note that this subgraph node can communicate with the parent graph via the shared "foo" key + return {"foo": state["foo"] + "bar"} + +subgraph_builder = StateGraph(SubgraphState) +subgraph_builder.add_node(subgraph_node) +... +subgraph = subgraph_builder.compile() + +# Define parent graph +builder = StateGraph(State) +builder.add_node("subgraph", subgraph) +... +graph = builder.compile() +``` + +### As a function + +You might want to define a subgraph with a completely different schema. In this case, you can create a node function that invokes the subgraph. This function will need to [transform](../how-tos/subgraph-transform-state.ipynb) the input (parent) state to the subgraph state before invoking the subgraph, and transform the results back to the parent state before returning the state update from the node. + +```python +class State(TypedDict): + foo: str + +class SubgraphState(TypedDict): + # note that none of these keys are shared with the parent graph state + bar: str + baz: str + +# Define subgraph +def subgraph_node(state: SubgraphState): + return {"bar": state["bar"] + "baz"} + +subgraph_builder = StateGraph(SubgraphState) +subgraph_builder.add_node(subgraph_node) +... +subgraph = subgraph_builder.compile() + +# Define parent graph +def node(state: State): + # transform the state to the subgraph state + response = subgraph.invoke({"bar": state["foo"]}) + # transform response back to the parent state + return {"foo": response["bar"]} + +builder = StateGraph(State) +# note that we are using `node` function instead of a compiled subgraph +builder.add_node(node) +... +graph = builder.compile() +``` + ## Visualization It's often nice to be able to visualize graphs, especially as they get more complex. LangGraph comes with several built-in ways to visualize graphs. See [this how-to guide](../how-tos/visualization.ipynb) for more info. ## Streaming -LangGraph is built with first class support for streaming, including streaming updates from graph nodes during the execution, streaming tokens from LLM calls and more. See this [conceptual guide](./streaming.md) for more information. \ No newline at end of file +LangGraph is built with first class support for streaming, including streaming updates from graph nodes during the execution, streaming tokens from LLM calls and more. See this [conceptual guide](./streaming.md) for more information. diff --git a/docs/docs/concepts/memory.md b/docs/docs/concepts/memory.md index 9be7aa505..49eb8e118 100644 --- a/docs/docs/concepts/memory.md +++ b/docs/docs/concepts/memory.md @@ -2,7 +2,7 @@ ## What is Memory? -Memory in AI applications refers to the ability to process, store, and effectively recall information from past interactions. With memory, your agents can learn from feedback and adapt to users' preferences. This guide is divided into two sections based on the scope of memory recall: short-term memory and long-term memory. +[Memory](https://pmc.ncbi.nlm.nih.gov/articles/PMC10410470/) is a cognitive function that allows people to store, retrieve, and use information to understand their present and future. Consider the frustration of working with a colleague who forgets everything you tell them, requiring constant repetition! As AI agents undertake more complex tasks involving numerous user interactions, equipping them with memory becomes equally crucial for efficiency and user satisfaction. With memory, agents can learn from feedback and adapt to users' preferences. This guide covers two types of memory based on recall scope: **Short-term memory**, or [thread](persistence.md#threads)-scoped memory, can be recalled at any time **from within** a single conversational thread with a user. LangGraph manages short-term memory as a part of your agent's [state](low_level.md#state). State is persisted to a database using a [checkpointer](persistence.md#checkpoints) so the thread can be resumed at any time. Short-term memory updates when the graph is invoked or a step is completed, and the State is read at the start of each step. @@ -173,6 +173,8 @@ trim_messages( Long-term memory in LangGraph allows systems to retain information across different conversations or sessions. Unlike short-term memory, which is thread-scoped, long-term memory is saved within custom "namespaces." +### Storing memories + LangGraph stores long-term memories as JSON documents in a [store](persistence.md#memory-store) ([reference doc](https://langchain-ai.github.io/langgraph/reference/store/#langgraph.store.base.BaseStore)). Each memory is organized under a custom `namespace` (similar to a folder) and a distinct `key` (like a filename). Namespaces often include user or org IDs or other labels that makes it easier to organize information. This structure enables hierarchical organization of memories. Cross-namespace searching is then supported through content filters. See the example below for an example. ```python @@ -183,101 +185,82 @@ store = InMemoryStore() user_id = "my-user" application_context = "chitchat" namespace = (user_id, application_context) -store.put(namespace, key="a-memory", {"rules": ["User likes short, direct language", "User only speaks English & python"], "my-key": "my-value"}) +store.put(namespace, "a-memory", {"rules": ["User likes short, direct language", "User only speaks English & python"], "my-key": "my-value"}) # get the "memory" by ID -item = store.get(namespace) +item = store.get(namespace, "a-memory") # list "memories" within this namespace, filtering on content equivalence items = store.search(namespace, filter={"my-key": "my-value"}) ``` -When adding long-term memory to your agent, it's important to think about how to **write memories**, how to **store and manage memory updates**, and how to **recall & represent memories** for the LLM in your application. These questions are all interdependent: how you want to recall & format memories for the LLM dictates what you should store and how to manage it. Furthermore, each technique has tradeoffs. The right approach for you largely depends on your application's needs. -LangGraph aims to give you the low-level primitives to directly control the long-term memory of your application, based on memory [Store](persistence.md#memory-store)'s. +### Framework for thinking about long-term memory -Long-term memory is far from a solved problem. While it is hard to provide generic advice, we have provided a few reliable patterns below for your consideration as you implement long-term memory. +Long-term memory is a complex challenge without a one-size-fits-all solution. However, the following questions provide a structure framework to help you navigate the different techniques: -**Do you want to write memories "on the hot path" or "in the background"** +**What is the type of memory?** -Memory can be updated either as part of your primary application logic (e.g. "on the hot path" of the application) or as a background task (as a separate function that generates memories based on the primary application's state). We document some tradeoffs for each approach in [the writing memories section below](#writing-memories). +Humans use memories to remember [facts](https://en.wikipedia.org/wiki/Semantic_memory), [experiences](https://en.wikipedia.org/wiki/Episodic_memory), and [rules](https://en.wikipedia.org/wiki/Procedural_memory). AI agents can use memory in the same ways. For example, AI agents can use memory to remember specific facts about a user to accomplish a task. We expand on several types of memories in the [section below](#memory-types). -**Do you want to manage memories as a single profile or as a collection of documents?** +**When do you want to update memories?** -We provide two main approaches to managing long-term memory: a single, continuously updated document (referred to as a "profile" or "schema") or a collection of documents. Each method offers its own benefits, depending on the type of information you need to store and how you intend to access it. +Memory can be updated as part of an agent's application logic (e.g. "on the hot path"). In this case, the agent typically decides to remember facts before responding to a user. Alternatively, memory can be updated as a background task (logic that runs in the background / asynchronously and generates memories). We explain the tradeoffs between these approaches in the [section below](#writing-memories). -Managing memories as a single, continuously updated "profile" or "schema" is useful when there is well-scoped, specific information you want to remember about a user, organization, or other entity (including the agent itself). You can define the schema of the profile ahead of time, and then use an LLM to update this based on interactions. Querying the "memory" is easy since it's a simple GET operation on a JSON document. We explain this in more detail in [remember a profile](#manage-individual-profiles). This technique can provide higher precision (on known information use cases) at the expense of lower recall (since you have to anticipate and model your domain, and updates to the doc tend to delete or rewrite away old information at a greater frequency). +## Memory types -Managing long-term memory as a collection of documents, on the other hand, lets you store an unbounded amount of information. This technique is useful when you want to repeatedly extract & remember items over a long time horizon but can be more complicated to query and manage over time. -Similar to the "profile" memory, you still define schema(s) for each memory. Rather than overwriting a single document, you instead will insert new ones (and potentially update or re-contextualize existing ones in the process). We explain this approach in more detail in ["managing a collection of memories"](#manage-a-collection-of-memories). +Different applications require various types of memory. Although the analogy isn't perfect, examining [human memory types](https://www.psychologytoday.com/us/basics/memory/types-of-memory?ref=blog.langchain.dev) can be insightful. Some research (e.g., the [CoALA paper](https://arxiv.org/pdf/2309.02427)) have even mapped these human memory types to those used in AI agents. -**Do you want to present memories to your agent as updated instructions or as few-shot examples?** +| Memory Type | What is Stored | Human Example | Agent Example | +|-------------|----------------|---------------|---------------| +| Semantic | Facts | Things I learned in school | Facts about a user | +| Episodic | Experiences | Things I did | Past agent actions | +| Procedural | Instructions | Instincts or motor skills | Agent system prompt | -Memories are typically provided to the LLM as a part of the system prompt. Some common ways to "frame" memories for the LLM include providing raw information as "memories from previous interactions with user A", as system instructions or rules, or as few-shot examples. +### Semantic Memory -Framing memories as "learning rules or instructions" typically means dedicating a portion of the system prompt to instructions the LLM can manage itself. After each conversation, you can prompt the LLM to evaluate its performance and update the instructions to better handle this type of task in the future. We explain this approach in more detail in [this section](#update-own-instructions). +[Semantic memory](https://en.wikipedia.org/wiki/Semantic_memory), both in humans and AI agents, involves the retention of specific facts and concepts. In humans, it can include information learned in school and the understanding of concepts and their relationships. For AI agents, semantic memory is often used to personalize applications by remembering facts or concepts from past interactions. -Storing memories as few-shot examples lets you store and manage instructions as cause and effect. Each memory stores an input or context and expected response. Including a reasoning trajectory (a chain-of-thought) can also help provide sufficient context so that the memory is less likely to be mis-used in the future. We elaborate on this concept more in [this section](#few-shot-examples). +#### Profile -We will expand on techniques for writing, managing, and recalling & formatting memories in the following section. +Semantic memories can be managed in different ways. For example, memories can be a single, continuously updated "profile" of well-scoped and specific information about a user, organization, or other entity (including the agent itself). A profile is generally just a JSON document with various key-value pairs you've selected to represent your domain. -### Writing memories - -Humans form long-term memories when we sleep, but when and how should our agents create new memories? The two most common ways we see agents write memories are "on the hot path" and "in the background". - -![](img/memory/hot_path_vs_background.png) - -#### Writing memories in the hot path - -This involves creating memories while the application is running. To provide a popular production example, ChatGPT manages memories using a "save_memories" tool to upsert memories as content strings. It decides whether (and how) to use this tool every time it receives a user message and multi-tasks memory management with the rest of the user instructions. - -This has a few benefits. First of all, it happens "in real time". If the user starts a new thread right away that memory will be present. The user also transparently sees when memories are stored, since the bot has to explicitly decide to store information and can relate that to the user. - -This also has several downsides. It complicates the decisions the agent must make (what to commit to memory). This complication can degrade its tool-calling performance and reduce task completion rates. It will slow down the final response since it needs to decide what to commit to memory. It also typically leads to fewer things being saved to memory (since the assistant is multi-tasking), which will cause **lower recall** in later conversations. - -#### Writing memories in the background - -This involves updating memory as a conceptually separate task, typically as a completely separate graph or function. Since it happens in the background, it incurs no latency. It also splits up the application logic from the memory logic, making it more modular and easy to manage. It also lets you separate the timing of memory creation, letting you avoid redundant work. Your agent can focus on accomplishing its immediate task without having to consciously think about what it needs to remember. - -This approach is not without its downsides, however. You have to think about how often to write memories. If it doesn't run in realtime, the user's interactions on other threads won't benefit from the new context. You also have to think about when to trigger this job. We typically recommend scheduling memories after some point of time, cancelling and re-scheduling for the future if new events occur on a given thread. Other popular choices are to form memories on some cron schedule or to let the user or application logic manually trigger memory formation. - -### Managing memories - -Once you've sorted out memory scheduling, it's important to think about **how to update memory with new information**. - -There are two main approaches: you can either continuously update a single document (memory profile) or insert new documents each time you receive new information. - -We will outline some tradeoffs between these two approaches below, understanding that most people will find it most appropriate to combine approaches and to settle somewhere in the middle. - -#### Manage individual profiles - -A profile is generally just a JSON document with various key-value pairs you've selected to represent your domain. When remembering a profile, you will want to make sure that you are **updating** the profile each time. As a result, you will want to pass in the previous profile and ask the LLM to generate a new profile (or some JSON patch to apply to the old profile). - -The larger the document, the more error-prone this can become. If your document becomes **too** large, you may want to consider splitting up the profiles into separate sections. You will likely need to use generation with retries and/or **strict** decoding when generating documents to ensure the memory schemas remains valid. +When remembering a profile, you will want to make sure that you are **updating** the profile each time. As a result, you will want to pass in the previous profile and [ask the model to generate a new profile](https://github.com/langchain-ai/memory-template) (or some [JSON patch](https://github.com/hinthornw/trustcall) to apply to the old profile). This can be become error-prone as the profile gets larger, and may benefit from splitting a profile into multiple documents or **strict** decoding when generating documents to ensure the memory schemas remains valid. ![](img/memory/update-profile.png) -#### Manage a collection of memories +#### Collection -Saving memories as a collection of documents simplifies some things. Each individual memory can be more narrowly scoped and easier to generate. It also means you're less likely to **lose** information over time, since it's easier for an LLM to generate _new_ objects for new information than it is for it to reconcile that new information with information in a dense profile. This tends to lead to higher recall downstream. +Alternatively, memories can be a collection of documents that are continuously updated and extended over time. Each individual memory can be more narrowly scoped and easier to generate, which means that you're less likely to **lose** information over time. It's easier for an LLM to generate _new_ objects for new information than reconcile new information with an existing profile. As a result, a document collection tends to lead to [higher recall downstream](https://en.wikipedia.org/wiki/Precision_and_recall). -This approach shifts some complexity to how you prompt the LLM to apply memory updates. You now have to enable the LLM to _delete_ or _update_ existing items in the list. This can be tricky to prompt the LLM to do. Some LLMs may default to over-inserting; others may default to over-updating. Tuning the behavior here is best done through evals, something you can do with a tool like [LangSmith](https://docs.smith.langchain.com/tutorials/Developers/evaluation). +However, this shifts some complexity memory updating. The model must now _delete_ or _update_ existing items in the list, which can be tricky. In addition, some models may default to over-inserting and others may default to over-updating. See the [Trustcall](https://github.com/hinthornw/trustcall) package for one way to manage this and consider evaluation (e.g., with a tool like [LangSmith](https://docs.smith.langchain.com/tutorials/Developers/evaluation)) to help you tune the behavior. -This also shifts complexity to memory **search** (recall). You have to think about what relevant items to use. Right now we support filtering by metadata. We will be adding semantic search shortly. +Working with document collections also shifts complexity to memory **search** over the list. The `Store` currently supports [filtering by metadata](https://langchain-ai.github.io/langgraph/reference/store/#storage) and will soon add [semantic search shortly](https://python.langchain.com/docs/concepts/vectorstores/), but selecting the most relevant documents can be tricky as the list grows. -Finally, this shifts some complexity to how you represent the memories for the LLM (and by extension, the schemas you use to save each memories). It's very easy to write memories that can easily be mistaken out-of-context. It's important to prompt the LLM to include all necessary contextual information in the given memory so that when you use it in later conversations it doesn't mistakenly mis-apply that information. +Finally, using a collection of memories can make it challenging to provide comprehensive context to the model. While individual memories may follow a specific schema, this structure might not capture the full context or relationships between memories. As a result, when using these memories to generate responses, the model may lack important contextual information that would be more readily available in a unified profile approach. ![](img/memory/update-list.png) -### Representing memories +Regardless of memory management approach, the central point is that the agent will use the semantic memories to [ground its responses](https://python.langchain.com/docs/concepts/rag/), which often leads to more personalized and relevant interactions. -Once you have saved memories, the way you then retrieve and present the memory content for the LLM can play a large role in how well your LLM incorporates that information in its responses. -The following sections present a couple of common approaches. Note that these sections also will largely inform how you write and manage memories. Everything in memory is connected! +### Episodic Memory -#### Update own instructions +[Episodic memory](https://en.wikipedia.org/wiki/Episodic_memory), in both humans and AI agents, involves recalling past events or actions. The [CoALA paper](https://arxiv.org/pdf/2309.02427) frames this well: facts can be written to semantic memory, whereas *experiences* can be written to episodic memory. For AI agents, episodic memory is often used to help an agent remember how to accomplish a task. -While instructions are often static text written by the developer, many AI applications benefit from letting the users personalize the rules and instructions the agent should follow whenever it interacts with that user. This ideally can be inferred by its interactions with the user (so the user doesn't have to explicitly change settings in yoru app). In this sense, instructions are a form of long-form memory! +In practice, episodic memories are often implemented through [few-shot example prompting](https://python.langchain.com/docs/concepts/few_shot_prompting/), where agents learn from past sequences to perform tasks correctly. Sometimes it's easier to "show" than "tell" and LLMs learn well from examples. Few-shot learning lets you ["program"](https://x.com/karpathy/status/1627366413840322562) your LLM by updating the prompt with input-output examples to illustrate the intended behavior. While various [best-practices](https://python.langchain.com/docs/concepts/#1-generating-examples) can be used to generate few-shot examples, often the challenge lies in selecting the most relevant examples based on user input. -One way to apply this is using "reflection" or "Meta-prompting" steps. Prompt the LLM with the current instruction set (from the system prompt) and a conversation with the user, and instruct the LLM to refine its instructions. This approach allows the system to dynamically update and improve its own behavior, potentially leading to better performance on various tasks. This is particularly useful for tasks where the instructions are challenging to specify a priori. +Note that the memory [store](persistence.md#memory-store) is just one way to store data as few-shot examples. If you want to have more developer involvement, or tie few-shots more closely to your evaluation harness, you can also use a [LangSmith Dataset](https://docs.smith.langchain.com/evaluation/how_to_guides/datasets/index_datasets_for_dynamic_few_shot_example_selection) to store your data. Then dynamic few-shot example selectors can be used out-of-the box to achieve this same goal. LangSmith will index the dataset for you and enable retrieval of few shot examples that are most relevant to the user input based upon keyword similarity ([using a BM25-like algorithm](https://docs.smith.langchain.com/how_to_guides/datasets/index_datasets_for_dynamic_few_shot_example_selection) for keyword based similarity). -Meta-prompting uses past information to refine prompts. For instance, a [Tweet generator](https://www.youtube.com/watch?v=Vn8A3BxfplE) employs meta-prompting to enhance its paper summarization prompt for Twitter. You could implement this using LangGraph's memory store to save updated instructions in a shared namespace. In this case, we will namespace the memories as "agent_instructions" and key the memory based on the agent. +See this how-to [video](https://www.youtube.com/watch?v=37VaU7e7t5o) for example usage of dynamic few-shot example selection in LangSmith. Also, see this [blog post](https://blog.langchain.dev/few-shot-prompting-to-improve-tool-calling-performance/) showcasing few-shot prompting to improve tool calling performance and this [blog post](https://blog.langchain.dev/aligning-llm-as-a-judge-with-human-preferences/) using few-shot example to align an LLMs to human preferences. + +### Procedural Memory + +[Procedural memory](https://en.wikipedia.org/wiki/Procedural_memory), in both humans and AI agents, involves remembering the rules used to perform tasks. In humans, procedural memory is like the internalized knowledge of how to perform tasks, such as riding a bike via basic motor skills and balance. Episodic memory, on the other hand, involves recalling specific experiences, such as the first time you successfully rode a bike without training wheels or a memorable bike ride through a scenic route. For AI agents, procedural memory is a combination of model weights, agent code, and agent's prompt that collectively determine the agent's functionality. + +In practice, it is fairly uncommon for agents to modify their model weights or rewrite their code. However, it is more common for agents to [modify their own prompts](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/prompt-generator). + +One effective approach to refining an agent's instructions is through ["Reflection"](https://blog.langchain.dev/reflection-agents/) or meta-prompting. This involves prompting the agent with its current instructions (e.g., the system prompt) along with recent conversations or explicit user feedback. The agent then refines its own instructions based on this input. This method is particularly useful for tasks where instructions are challenging to specify upfront, as it allows the agent to learn and adapt from its interactions. + +For example, we built a [Tweet generator](https://www.youtube.com/watch?v=Vn8A3BxfplE) using external feedback and prompt re-writing to produce high-quality paper summaries for Twitter. In this case, the specific summarization prompt was difficult to specify *a priori*, but it was fairly easy for a user to critique the generated Tweets and provide feedback on how to improve the summarization process. + +The below pseudo-code shows how you might implement this with the LangGraph memory [store](persistence.md#memory-store), using the store to save a prompt, the `update_instructions` node to get the current prompt (as well as feedback from the conversation with the user captured in `state["messages"]`), update the prompt, and save the new prompt back to the store. Then, the `call_model` get the updated prompt from the store and uses it to generate a response. ```python # Node that *uses* the instructions @@ -288,7 +271,6 @@ def call_model(state: State, store: BaseStore): prompt = prompt_template.format(instructions=instructions.value["instructions"]) ... - # Node that updates instructions def update_instructions(state: State, store: BaseStore): namespace = ("instructions",) @@ -303,8 +285,24 @@ def update_instructions(state: State, store: BaseStore): ![](img/memory/update-instructions.png) -#### Few-shot examples +## Writing memories -Sometimes it's easier to "show" than "tell." LLMs learn well from examples. Few-shot learning lets you ["program"](https://x.com/karpathy/status/1627366413840322562) your LLM by updating the prompt with input-output examples to illustrate the intended behavior. While various [best-practices](https://python.langchain.com/docs/concepts/#1-generating-examples) can be used to generate few-shot examples, often the challenge lies in selecting the most relevant examples based on user input. +While [humans often form long-term memories during sleep](https://medicine.yale.edu/news-article/sleeps-crucial-role-in-preserving-memory/), AI agents need a different approach. When and how should agents create new memories? There are at least two primary methods for agents to write memories: "on the hot path" and "in the background". -Note that the memory store is just one way to store data as few-shot examples. If you want to have more developer involvement, or tie few-shots more closely to your evaluation harness, you can also use a [LangSmith Dataset](https://docs.smith.langchain.com/how_to_guides/datasets) to store your data. Then dynamic few-shot example selectors can be used out-of-the box to achieve this same goal. LangSmith will index the dataset for you and enable retrieval of few shot examples that are most relevant to the user input based upon keyword similarity ([using a BM25-like algorithm](https://docs.smith.langchain.com/how_to_guides/datasets/index_datasets_for_dynamic_few_shot_example_selection) for keyword based similarity). See this how-to [video](https://www.youtube.com/watch?v=37VaU7e7t5o) for example usage of dynamic few-shot example selection in LangSmith. Also, see this [blog post](https://blog.langchain.dev/few-shot-prompting-to-improve-tool-calling-performance/) showcasing few-shot prompting to improve tool calling performance and this [blog post](https://blog.langchain.dev/aligning-llm-as-a-judge-with-human-preferences/) using few-shot example to align an LLMs to human preferences. +![](img/memory/hot_path_vs_background.png) + +### Writing memories in the hot path + +Creating memories during runtime offers both advantages and challenges. On the positive side, this approach allows for real-time updates, making new memories immediately available for use in subsequent interactions. It also enables transparency, as users can be notified when memories are created and stored. + +However, this method also presents challenges. It may increase complexity if the agent requires a new tool to decide what to commit to memory. In addition, the process of reasoning about what to save to memory can impact agent latency. Finally, the agent must multitask between memory creation and its other responsibilities, potentially affecting the quantity and quality of memories created. + +As an example, ChatGPT uses a [save_memories](https://openai.com/index/memory-and-new-controls-for-chatgpt/) tool to upsert memories as content strings, deciding whether and how to use this tool with each user message. See our [memory-agent](https://github.com/langchain-ai/memory-agent) template as an reference implementation. + +### Writing memories in the background + +Creating memories as a separate background task offers several advantages. It eliminates latency in the primary application, separates application logic from memory management, and allows for more focused task completion by the agent. This approach also provides flexibility in timing memory creation to avoid redundant work. + +However, this method has its own challenges. Determining the frequency of memory writing becomes crucial, as infrequent updates may leave other threads without new context. Deciding when to trigger memory formation is also important. Common strategies include scheduling after a set time period (with rescheduling if new events occur), using a cron schedule, or allowing manual triggers by users or the application logic. + +See our [memory-service](https://github.com/langchain-ai/memory-template) template as an reference implementation. diff --git a/docs/docs/concepts/multi_agent.md b/docs/docs/concepts/multi_agent.md index 073c49a94..46bf4eeda 100644 --- a/docs/docs/concepts/multi_agent.md +++ b/docs/docs/concepts/multi_agent.md @@ -1,138 +1,281 @@ # Multi-agent Systems -A multi-agent system is a system with multiple independent actors powered by LLMs that are connected in a specific way. These actors can be as simple as a prompt and an LLM call, or as complex as a [ReAct](./agentic_concepts.md#react-implementation) agent. +An [agent](./agentic_concepts.md#agent-architectures) is _a system that uses an LLM to decide the control flow of an application_. As you develop these systems, they might grow more complex over time, making them harder to manage and scale. For example, you might run into the following problems: -The primary benefits of this architecture are: +- agent has too many tools at its disposal and makes poor decisions about which tool to call next +- context grows too complex for a single agent to keep track of +- there is a need for multiple specialization areas in the system (e.g. planner, researcher, math expert, etc.) -* **Modularity**: Separate agents facilitate easier development, testing, and maintenance of agentic systems. -* **Specialization**: You can create expert agents focused on specific domains, and compose them into more complex applications -* **Control**: You can explicitly control how agents communicate (as opposed to relying on function calling) +To tackle these, you might consider breaking your application into multiple smaller, independent agents and composing them into a **multi-agent system**. These independent agents can be as simple as a prompt and an LLM call, or as complex as a [ReAct](./agentic_concepts.md#react-implementation) agent (and more!). -## Multi-agent systems in LangGraph +The primary benefits of using multi-agent systems are: -### Agents as nodes +- **Modularity**: Separate agents make it easier to develop, test, and maintain agentic systems. +- **Specialization**: You can create expert agents focused on specific domains, which helps with the overall system performance. +- **Control**: You can explicitly control how agents communicate (as opposed to relying on function calling). -Agents can be defined as nodes in LangGraph. As any other node in the LangGraph, these agent nodes receive the graph state as an input and return an update to the state as their output. +## Multi-agent architectures -* Simple **LLM nodes**: single LLMs with custom prompts -* **Subgraph nodes**: complex graphs called inside the orchestrator graph node +![](./img/multi_agent/architectures.png) -![](./img/multi_agent/subgraph.png) +There are several ways to connect agents in a multi-agent system: -### Agents as tools +- **Network**: each agent can communicate with [every other agent](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/multi-agent-collaboration/). Any agent can decide which other agent to call next. +- **Supervisor**: each agent communicates with a single [supervisor](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/agent_supervisor/) agent. Supervisor agent makes decisions on which agent should be called next. +- **Supervisor (tool-calling)**: this is a special case of supervisor architecture. Individual agents can be represented as tools. In this case, a supervisor agent uses a tool-calling LLM to decide which of the agent tools to call, as well as the arguments to pass to those agents. +- **Hierarchical**: you can define a multi-agent system with [a supervisor of supervisors](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/hierarchical_agent_teams/). This is a generalization of the supervisor architecture and allows for more complex control flows. +- **Custom multi-agent workflow**: each agent communicates with only a subset of agents. Parts of the flow are deterministic, and only some agents can decide which other agents to call next. -Agents can also be defined as tools. In this case, the orchestrator agent (e.g. ReAct agent) would use a tool-calling LLM to decide which of the agent tools to call, as well as the arguments to pass to those agents. +### Network -You could also take a "mega-graph" approach – incorporating subordinate agents' nodes directly into the parent, orchestrator graph. However, this is not recommended for complex subordinate agents, as it would make the overall system harder to scale, maintain and debug – you should use subgraphs or tools in those cases. +In this architecture, agents are defined as graph nodes. Each agent can communicate with every other agent (many-to-many connections) and can decide which agent to call next. While very flexible, this architecture doesn't scale well as the number of agents grows: -## Communication in multi-agent systems +- hard to enforce which agent should be called next +- hard to determine how much [information](#shared-message-list) should be passed between the agents -A big question in multi-agent systems is how the agents communicate amongst themselves and with the orchestrator agent. This involves both the schema of how they communicate, as well as the sequence in which they communicate. LangGraph is perfect for orchestrating these types of systems and allows you to define both. +We recommend avoiding this architecture in production and using one of the below architectures instead. -### Schema +### Supervisor -LangGraph provides a lot of flexibility for how to communicate within multi-agent architectures. - -* A node in LangGraph can have a [private input state schema](https://langchain-ai.github.io/langgraph/how-tos/pass_private_state/) that is distinct from the graph state schema. This allows passing additional information during the graph execution that is only needed for executing a particular node. -* Subgraph node agents can have independent [input / output state schemas](https://langchain-ai.github.io/langgraph/how-tos/input_output_schema/). In this case it’s important to [add input / output transformations](https://langchain-ai.github.io/langgraph/how-tos/subgraph-transform-state/) so that the parent graph knows how to communicate with the subgraphs. -* For tool-based subordinate agents, the orchestrator determines the inputs based on the tool schema. Additionally, LangGraph allows passing state to individual tools at runtime, so subordinate agents can access parent state, if needed. - -### Sequence - -LangGraph provides multiple methods to control agent communication sequence: - -* **Explicit control flow (graph edges)**: LangGraph allows you to define the control flow of your application (i.e. the sequence of how agents communicate) explicitly, via [graph edges](./low_level.md#edges). +In this architecture, we define agents as nodes and add a supervisor node (LLM) that decides which agent nodes should be called next. We use [conditional edges](./low_level.md#conditional-edges) to route execution to the appropriate agent node based on supervisor's decision. This architecture also lends itself well to running multiple agents in parallel or using [map-reduce](../how-tos/map-reduce.ipynb) pattern. ```python +from typing import Literal from langchain_openai import ChatOpenAI -from langchain_core.messages import SystemMessage -from langgraph.graph import StateGraph, MessagesState, START, END +from langgraph.graph import StateGraph, MessagesState, START -model = ChatOpenAI(model="gpt-4o-mini") +model = ChatOpenAI() -def research_agent(state: MessagesState): - """Call research agent""" - messages = [SystemMessage(content="You are a research assistant. Given a topic, provide key facts and information.")] + state["messages"] - response = model.invoke(messages) +class AgentState(MessagesState): + next: Literal["agent_1", "agent_2", "__end__"] + +def supervisor(state: AgentState): + # you can pass relevant parts of the state to the LLM (e.g., state["messages"]) + # to determine which agent to call next. a common pattern is to call the model + # with a structured output (e.g. force it to return an output with a "next_agent" field) + response = model.invoke(...) + # the "next" key will be used by the conditional edges to route execution + # to the appropriate agent + return {"next": response["next_agent"]} + +def agent_1(state: AgentState): + # you can pass relevant parts of the state to the LLM (e.g., state["messages"]) + # and add any additional logic (different models, custom prompts, structured output, etc.) + response = model.invoke(...) return {"messages": [response]} -def summarize_agent(state: MessagesState): - """Call summarization agent""" - messages = [SystemMessage(content="You are a summarization expert. Condense the given information into a brief summary.")] + state["messages"] - response = model.invoke(messages) +def agent_2(state: AgentState): + response = model.invoke(...) return {"messages": [response]} -graph = StateGraph(MessagesState) -graph.add_node("research", research_agent) -graph.add_node("summarize", summarize_agent) +builder = StateGraph(AgentState) +builder.add_node(supervisor) +builder.add_node(agent_1) +builder.add_node(agent_2) -# define the flow explicitly -graph.add_edge(START, "research") -graph.add_edge("research", "summarize") -graph.add_edge("summarize", END) +builder.add_edge(START, "supervisor") +# route to one of the agents or exit based on the supervisor's decisiion +# if the supervisor returns "__end__", the graph will finish execution +builder.add_conditional_edges("supervisor", lambda state: state["next"]) +builder.add_edge("agent_1", "supervisor") +builder.add_edge("agent_2", "supervisor") + +supervisor = builder.compile() ``` -* **Dynamic control flow (conditional edges)**: LangGraph also allows you to define [conditional edges](./low_level.md#conditional-edges), where the control flow is dependent on satisfying a given condition. In such cases, you can use an LLM to decide which subordinate agent to call next. +Check out this [tutorial](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/agent_supervisor/) for an example of supervisor multi-agent architecture. +### Supervisor (tool-calling) -* **Implicit control flow (tool calling)**: if the orchestrator agent treats subordinate agents as tools, the tool-calling LLM powering the orchestrator will make decisions about the order in which the tools (agents) are being called. +In this variant of the [supervisor](#supervisor) architecture, we define individual agents as **tools** and use a tool-calling LLM in the supervisor node. This can be implemented as a [ReAct](./agentic_concepts.md#react-implementation)-style agent with two nodes — an LLM node (supervisor) and a tool-calling node that executes tools (agents in this case). ```python from typing import Annotated -from langchain_core.messages import SystemMessage, ToolMessage from langchain_openai import ChatOpenAI -from langgraph.prebuilt import ToolNode, InjectedState, create_react_agent +from langgraph.prebuilt import InjectedState, create_react_agent -model = ChatOpenAI(model="gpt-4o-mini") +model = ChatOpenAI() -def research_agent(state: Annotated[dict, InjectedState]): - """Call research agent""" - messages = [SystemMessage(content="You are a research assistant. Given a topic, provide key facts and information.")] + state["messages"][:-1] - response = model.invoke(messages) - tool_call = state["messages"][-1].tool_calls[0] - return {"messages": [ToolMessage(response.content, tool_call_id=tool_call["id"])]} +# this is the agent function that will be called as tool +# notice that you can pass the state to the tool via InjectedState annotation +def agent_1(state: Annotated[dict, InjectedState]): + # you can pass relevant parts of the state to the LLM (e.g., state["messages"]) + # and add any additional logic (different models, custom prompts, structured output, etc.) + response = model.invoke(...) + # return the LLM response as a string (expected tool response format) + # this will be automatically turned to ToolMessage + # by the prebuilt create_react_agent (supervisor) + return response.content -def summarize_agent(state: Annotated[dict, InjectedState]): - """Call summarization agent""" - messages = [SystemMessage(content="You are a summarization expert. Condense the given information into a brief summary.")] + state["messages"][:-1] - response = model.invoke(messages) - tool_call = state["messages"][-1].tool_calls[0] - return {"messages": [ToolMessage(response.content, tool_call_id=tool_call["id"])]} +def agent_2(state: Annotated[dict, InjectedState]): + response = model.invoke(...) + return response.content -tool_node = ToolNode([research_agent, summarize_agent]) -graph = create_react_agent(model, [research_agent, summarize_agent], state_modifier="First research and then summarize information on a given topic.") +tools = [agent_1, agent_2] +# the simplest way to build a supervisor w/ tool-calling is to use prebuilt ReAct agent graph +# that consists of a tool-calling LLM node (i.e. supervisor) and a tool-executing node +supervisor = create_react_agent(model, tools) ``` -## Example architectures +### Hierarchical -Below are several examples of complex multi-agent architectures that can be implemented in LangGraph. +As you add more agents to your system, it might become too hard for the supervisor to manage all of them. The supervisor might start making poor decisions about which agent to call next, the context might become too complex for a single supervisor to keep track of. In other words, you end up with the same problems that motivated the multi-agent architecture in the first place. -### Multi-Agent Collaboration +To address this, you can design your system _hierarchically_. For example, you can create separate, specialized teams of agents managed by individual supervisors, and a top-level supervisor to manage the teams. -In this example, different agents collaborate on a **shared** scratchpad of messages (i.e. shared graph state). This means that all the work any of them do is visible to the other ones. The benefit is that the other agents can see all the individual steps done. The downside is that sometimes is it overly verbose and unnecessary to pass ALL this information along, and sometimes only the final answer from an agent is needed. We call this **collaboration** because of the shared nature the scratchpad. +```python +from typing import Literal +from langchain_openai import ChatOpenAI +from langgraph.graph import StateGraph, MessagesState, START -In this case, the independent agents are actually just a single LLM call with a custom system message. +model = ChatOpenAI() -Here is a visualization of how these agents are connected: +# define team 1 (same as the single supervisor example above) +class Team1State(MessagesState): + next: Literal["team_1_agent_1", "team_1_agent_2", "__end__"] -![](./img/multi_agent/collaboration.png) +def team_1_supervisor(state: Team1State): + response = model.invoke(...) + return {"next": response["next_agent"]} -See full code example in this [tutorial](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/multi-agent-collaboration/). +def team_1_agent_1(state: Team1State): + response = model.invoke(...) + return {"messages": [response]} -### Agent Supervisor +def team_1_agent_2(state: Team1State): + response = model.invoke(...) + return {"messages": [response]} -In this example, multiple agents are connected, but compared to above they do NOT share a shared scratchpad. Rather, they have their own independent scratchpads (i.e. their own state), and then their final responses are appended to a global scratchpad. +team_1_builder = StateGraph(Team1State) +team_1_builder.add_node(team_1_supervisor) +team_1_builder.add_node(team_1_agent_1) +team_1_builder.add_node(team_1_agent_2) +team_1_builder.add_edge(START, "team_1_supervisor") +# route to one of the agents or exit based on the supervisor's decisiion +# if the supervisor returns "__end__", the graph will finish execution +team_1_builder.add_conditional_edges("team_1_supervisor", lambda state: state["next"]) +team_1_builder.add_edge("team_1_agent_1", "team_1_supervisor") +team_1_builder.add_edge("team_1_agent_2", "team_1_supervisor") -In this case, the independent agents are a LangGraph ReAct agent (graph). This means they have their own individual prompt, LLM, and tools. When called, it's not just a single LLM call, but rather an invocation of the graph powering the ReAct agent. +team_1_graph = team_1_builder.compile() -![](./img/multi_agent/supervisor.png) +# define team 2 (same as the single supervisor example above) +class Team2State(MessagesState): + next: Literal["team_2_agent_1", "team_2_agent_2", "__end__"] -See full code example in this [tutorial](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/agent_supervisor/). +def team_2_supervisor(state: Team2State): + ... -### Hierarchical Agent Teams +def team_2_agent_1(state: Team2State): + ... -What if the job for a single worker in agent supervisor example becomes too complex? What if the number of workers becomes too large? For some applications, the system may be more effective if work is distributed hierarchically. You can do this by creating additional level of subgraphs and creating a top-level supervisor, along with mid-level supervisors: +def team_2_agent_2(state: Team2State): + ... -![](./img/multi_agent/hierarchical.png) +team_2_builder = StateGraph(Team2State) +... +team_2_graph = team_2_builder.compile() -See full code example in this [tutorial](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/hierarchical_agent_teams/). \ No newline at end of file + +# define top-level supervisor + +class TopLevelState(MessagesState): + next: Literal["team_1", "team_2", "__end__"] + +builder = StateGraph(TopLevelState) +def top_level_supervisor(state: TopLevelState): + # you can pass relevant parts of the state to the LLM (e.g., state["messages"]) + # to determine which team to call next. a common pattern is to call the model + # with a structured output (e.g. force it to return an output with a "next_team" field) + response = model.invoke(...) + # the "next" key will be used by the conditional edges to route execution + # to the appropriate team + return {"next": response["next_team"]} + +builder = StateGraph(TopLevelState) +builder.add_node(top_level_supervisor) +builder.add_node(team_1_graph) +builder.add_node(team_2_graph) + +builder.add_edge(START, "top_level_supervisor") +# route to one of the teams or exit based on the supervisor's decision +# if the top-level supervisor returns "__end__", the graph will finish execution +builder.add_conditional_edges("top_level_supervisor", lambda state: state["next"]) +builder.add_edge("team_1_graph", "top_level_supervisor") +builder.add_edge("team_2_graph", "top_level_supervisor") + +graph = builder.compile() +``` + +### Custom multi-agent workflow + +In this architecture we add individual agents as graph nodes and define the order in which agents are called ahead of time, in a custom workflow. In LangGraph the workflow can be defined in two ways: + +- **Explicit control flow (normal edges)**: LangGraph allows you to explicitly define the control flow of your application (i.e. the sequence of how agents communicate) explicitly, via [normal graph edges](./low_level.md#normal-edges). This is the most deterministic variant of this architecture above — we always know which agent will be called next ahead of time. + +- **Dynamic control flow (conditional edges)**: in LangGraph you can allow LLMs to decide parts of your application control flow. This can be achieved by using [conditional edges](./low_level.md#conditional-edges). A special case of this is a [supervisor tool-calling](#supervisor-tool-calling) architecture. In that case, the tool-calling LLM powering the supervisor agent will make decisions about the order in which the tools (agents) are being called. + +```python +from langchain_openai import ChatOpenAI +from langgraph.graph import StateGraph, MessagesState, START + +model = ChatOpenAI() + +def agent_1(state: MessagesState): + response = model.invoke(...) + return {"messages": [response]} + +def agent_2(state: MessagesState): + response = model.invoke(...) + return {"messages": [response]} + +builder = StateGraph(MessagesState) +builder.add_node(agent_1) +builder.add_node(agent_2) +# define the flow explicitly +builder.add_edge(START, "agent_1") +builder.add_edge("agent_1", "agent_2") +``` + +## Communication between agents + +The most important thing when building multi-agent systems is figuring out how the agents communicate. There are few different considerations: + +- Do agents communicate via [**via graph state or via tool calls**](#graph-state-vs-tool-calls)? +- What if two agents have [**different state schemas**](#different-state-schemas)? +- How to communicate over a [**shared message list**](#shared-message-list)? + +### Graph state vs tool calls + +What is the "payload" that is being passed around between agents? In most of the architectures discussed above the agents communicate via the [graph state](./low_level.md#state). In the case of the [supervisor with tool-calling](#supervisor-tool-calling), the payloads are tool call arguments. + +![](./img/multi_agent/request.png) + +#### Graph state + +To communicate via graph state, individual agents need to be defined as [graph nodes](./low_level.md#nodes). These can be added as functions or as entire [subgraphs](./low_level.md#subgraphs). At each step of the graph execution, agent node receives the current state of the graph, executes the agent code and then passes the updated state to the next nodes. + +Typically agent nodes share a single [state schema](./low_level.md#schema). However, you might want to design agent nodes with [different state schemas](#different-state-schemas). + +### Different state schemas + +An agent might need to have a different state schema from the rest of the agents. For example, a search agent might only need to keep track of queries and retrieved documents. There are two ways to achieve this in LangGraph: + +- Define [subgraph](./low_level.md#subgraphs) agents with a separate state schema. If there are no shared state keys (channels) between the subgraph and the parent graph, it’s important to [add input / output transformations](https://langchain-ai.github.io/langgraph/how-tos/subgraph-transform-state/) so that the parent graph knows how to communicate with the subgraphs. +- Define agent node functions with a [private input state schema](https://langchain-ai.github.io/langgraph/how-tos/pass_private_state/) that is distinct from the overall graph state schema. This allows passing information that is only needed for executing that particular agent. + +### Shared message list + +The most common way for the agents to communicate is via a shared state channel, typically a list of messages. This assumes that there is always at least a single channel (key) in the state that is shared by the agents. When communicating via a shared message list there is an additional consideration: should the agents [share the full history](#share-full-history) of their thought process or only [the final result](#share-final-result)? + +![](./img/multi_agent/response.png) + +#### Share full history + +Agents can **share the full history** of their thought process (i.e. "scratchpad") with all other agents. This "scratchpad" would typically look like a [list of messages](./low_level.md#why-use-messages). The benefit of sharing full thought process is that it might help other agents make better decisions and improve reasoning ability for the system as a whole. The downside is that as the number of agents and their complexity grows, the "scratchpad" will grow quickly and might require additional strategies for [memory management](./memory.md/#managing-long-conversation-history). + +#### Share final result + +Agents can have their own private "scratchpad" and only **share the final result** with the rest of the agents. This approach might work better for systems with many agents or agents that are more complex. In this case, you would need to define agents with [different state schemas](#different-state-schemas) + +For agents called as tools, the supervisor determines the inputs based on the tool schema. Additionally, LangGraph allows [passing state](https://langchain-ai.github.io/langgraph/how-tos/pass-run-time-values-to-tools/#pass-graph-state-to-tools) to individual tools at runtime, so subordinate agents can access parent state, if needed. diff --git a/docs/docs/concepts/persistence.md b/docs/docs/concepts/persistence.md index 7bdb2f5da..d5ccd6d15 100644 --- a/docs/docs/concepts/persistence.md +++ b/docs/docs/concepts/persistence.md @@ -159,7 +159,7 @@ You must pass these when invoking the graph as part of the `configurable` portio # {"configurable": {"thread_id": "1", "checkpoint_id": "0c62ca34-ac19-445d-bbb0-5b4984975b2a"}} # also valid config config = {"configurable": {"thread_id": "1"}} -graph.invoke(inputs, config=config) +graph.invoke(None, config=config) ``` Importantly, LangGraph knows whether a particular checkpoint has been executed previously. If it has, LangGraph simply *re-plays* that particular step in the graph and does not re-execute the step. See this [how to guide on time-travel to learn more about replaying](../how-tos/human_in_the_loop/time-travel.ipynb). diff --git a/docs/docs/concepts/plans.md b/docs/docs/concepts/plans.md new file mode 100644 index 000000000..49b98ee11 --- /dev/null +++ b/docs/docs/concepts/plans.md @@ -0,0 +1,39 @@ +# LangGraph Platform Plans + + +## Overview +LangGraph Platform is a commercial solution for deploying agentic applications in production. +There are three different plans for using it. + +- **Developer**: All [LangSmith](https://smith.langchain.com/) users have access to this plan. You can sign up for this plan simply by creating a LangSmith account. This gives you access to the [Self-Hosted Lite](./deployment_options.md#self-hosted-lite) deployment option. +- **Plus**: All [LangSmith](https://smith.langchain.com/) users with a [Plus account](https://docs.smith.langchain.com/administration/pricing) have access to this plan. You can sign up for this plan simply by upgrading your LangSmith account to the Plus plan type. This gives you access to the [Cloud](./deployment_options.md#cloud-saas) deployment option. +- **Enterprise**: This is separate from LangSmith plans. You can sign up for this plan by contacting sales@langchain.dev. This gives you access to all deployment options: [Cloud](./deployment_options.md#cloud-saas), [Bring-Your-Own-Cloud](./deployment_options.md#bring-your-own-cloud), and [Self Hosted Enterprise](./deployment_options.md#self-hosted-enterprise) + + +## Plan Details + +| | Developer | Plus | Enterprise | +|------------------------------------------------------------------|---------------------------------------------|-------------------------------------------------------|-----------------------------------------------------| +| Deployment Options | Self-Hosted Lite | Cloud | Self-Hosted Enterprise, Cloud, Bring-Your-Own-Cloud | +| Usage | Free, limited to 1M nodes executed per year | Free while in Beta, will be charged per node executed | Custom | +| APIs for retrieving and updating state and conversational history | ✅ | ✅ | ✅ | +| APIs for retrieving and updating long-term memory | ✅ | ✅ | ✅ | +| Horizontally scalable task queues and servers | ✅ | ✅ | ✅ | +| Real-time streaming of outputs and intermediate steps | ✅ | ✅ | ✅ | +| Assistants API (configurable templates for LangGraph apps) | ✅ | ✅ | ✅ | +| Cron scheduling | -- | ✅ | ✅ | +| LangGraph Studio for prototyping | Desktop only | Coming Soon! | Coming Soon! | +| Authentication & authorization to call the LangGraph APIs | -- | Coming Soon! | Coming Soon! | +| Smart caching to reduce traffic to LLM API | -- | Coming Soon! | Coming Soon! | +| Publish/subscribe API for state | -- | Coming Soon! | Coming Soon! | +| Scheduling prioritization | -- | Coming Soon! | Coming Soon! | + +Please see the [LangGraph Platform Pricing](https://www.langchain.com/langgraph-platform-pricing) for information on pricing. + +## Related + +For more information, please see: + +* [Deployment Options conceptual guide](./deployment_options.md) +* [LangGraph Platform Pricing](https://www.langchain.com/langgraph-platform-pricing) +* [LangSmith Plans](https://docs.smith.langchain.com/administration/pricing) diff --git a/docs/docs/concepts/sdk.md b/docs/docs/concepts/sdk.md new file mode 100644 index 000000000..b3b07bc9d --- /dev/null +++ b/docs/docs/concepts/sdk.md @@ -0,0 +1,56 @@ +# LangGraph SDK + +!!! info "Prerequisites" + - [LangGraph Platform](./langgraph_platform.md) + - [LangGraph Server](./langgraph_server.md) + +The LangGraph Platform provides both a Python and JS SDK for interacting with the [LangGraph Server API](./langgraph_server.md). + +## Installation + +You can install the packages using the appropriate package manager for your language. + +=== "Python" + ```bash + pip install langgraph-sdk + ``` + +=== "JS" + ```bash + yarn add @langchain/langgraph-sdk + ``` + + +## API Reference + +You can find the API reference for the SDKs here: + +- [Python SDK Reference](../cloud/reference/sdk/python_sdk_ref.md) +- [JS/TS SDK Reference](../cloud/reference/sdk/js_ts_sdk_ref.md) + +## Python Sync vs. Async + +The Python SDK provides both synchronous (`get_sync_client`) and asynchronous (`get_client`) clients for interacting with the LangGraph Server API. + +=== "Async" + ```python + from langgraph_sdk import get_client + + client = get_client(url=..., api_key=...) + await client.assistants.search() + ``` + +=== "Sync" + + ```python + from langgraph_sdk import get_sync_client + + client = get_sync_client(url=..., api_key=...) + client.assistants.search() + ``` + +## Related + +- [LangGraph CLI API Reference](../cloud/reference/cli.md) +- [Python SDK Reference](../cloud/reference/sdk/python_sdk_ref.md) +- [JS/TS SDK Reference](../cloud/reference/sdk/js_ts_sdk_ref.md) \ No newline at end of file diff --git a/docs/docs/concepts/self_hosted.md b/docs/docs/concepts/self_hosted.md new file mode 100644 index 000000000..ffa26a873 --- /dev/null +++ b/docs/docs/concepts/self_hosted.md @@ -0,0 +1,39 @@ +# Self-Hosted + +!!! note Prerequisites + + - [LangGraph Platform](./langgraph_platform.md) + - [Deployment Options](./deployment_options.md) + +## Versions + +There are two versions of the self hosted deployment: [Self-Hosted Enterprise](./deployment_options.md#self-hosted-enterprise) and [Self-Hosted Lite](./deployment_options.md#self-hosted-lite). + +### Self-Hosted Lite + +The Self-Hosted Lite version is a limited version of LangGraph Platform that you can run locally or in a self-hosted manner (up to 1 million nodes executed). + +When using the Self-Hosted Lite version, you authenticate with a [LangSmith](https://smith.langchain.com/) API key. + +### Self-Hosted Enterprise + +The Self-Hosted Enterprise version is the full version of LangGraph Platform. + +To use the Self-Hosted Enterprise version, you must acquire a license key that you will need to pass in when running the Docker image. To acquire a license key, please email sales@langchain.dev. + +## Requirements + +- You use `langgraph-cli` and/or [LangGraph Studio](./langgraph_studio.md) app to test graph locally. +- You use `langgraph build` command to build image. + +## How it works + +- Deploy Redis and Postgres instances on your own infrastructure. +- Build the docker image for [LangGraph Server](./langgraph_server.md) using the [LangGraph CLI](./langgraph_cli.md). +- Deploy a web server that will run the docker image and pass in the necessary environment variables. + +For step-by-step instructions, see [How to set up a self-hosted deployment of LangGraph](../how-tos/deploy-self-hosted.md). + +## Related + +- [How to set up a self-hosted deployment of LangGraph](../how-tos/deploy-self-hosted.md). diff --git a/docs/docs/concepts/streaming.md b/docs/docs/concepts/streaming.md index 8c557558d..4cff01497 100644 --- a/docs/docs/concepts/streaming.md +++ b/docs/docs/concepts/streaming.md @@ -9,8 +9,23 @@ There are several different modes you can specify when calling these methods (e. - [`"values"`](../how-tos/stream-values.ipynb): This streams the full value of the state after each step of the graph. - [`"updates"`](../how-tos/stream-updates.ipynb): This streams the updates to the state after each step of the graph. If multiple updates are made in the same step (e.g. multiple nodes are run) then those updates are streamed separately. +- [`"custom"`](../how-tos/streaming-content.ipynb): This streams custom data from inside your graph nodes. +- [`"messages"`](../how-tos/streaming-tokens.ipynb): This streams LLM tokens and metadata for the graph node where LLM is invoked. - `"debug"`: This streams as much information as possible throughout the execution of the graph. +You can also specify multiple streaming modes at the same time by passing them as a list. When you do this, the streamed outputs will be tuples `(stream_mode, data)`. For example: + +```python +graph.stream(..., stream_mode=["updates", "messages"]) +``` + +``` +... +('messages', (AIMessageChunk(content='Hi'), {'langgraph_step': 3, 'langgraph_node': 'agent', ...})) +... +('updates', {'agent': {'messages': [AIMessage(content="Hi, how can I help you?")]}}) +``` + The below visualization shows the difference between the `values` and `updates` modes: ![values vs updates](../static/values_vs_updates.png) @@ -130,4 +145,28 @@ guide for that [here](../how-tos/streaming-tokens.ipynb). !!! warning "ASYNC IN PYTHON<=3.10" - You may fail to see events being emitted from inside a node when using `.astream_events` in Python <= 3.10. If you're using a Langchain RunnableLambda, a RunnableGenerator, or Tool asynchronously inside your node, you will have to propagate callbacks to these objects manually. This is because LangChain cannot automatically propagate callbacks to child objects in this case. Please see examples [here](../how-tos/streaming-content.ipynb) and [here](../how-tos/streaming-events-from-within-tools.ipynb). \ No newline at end of file + You may fail to see events being emitted from inside a node when using `.astream_events` in Python <= 3.10. If you're using a Langchain RunnableLambda, a RunnableGenerator, or Tool asynchronously inside your node, you will have to propagate callbacks to these objects manually. This is because LangChain cannot automatically propagate callbacks to child objects in this case. Please see examples [here](../how-tos/streaming-content.ipynb) and [here](../how-tos/streaming-events-from-within-tools.ipynb). + + +## LangGraph Platform + +Streaming is critical for making LLM applications feel responsive to end users. When creating a streaming run, the streaming mode determines what data is streamed back to the API client. LangGraph Platform supports five streaming modes: + +- `values`: Stream the full state of the graph after each [super-step](https://langchain-ai.github.io/langgraph/concepts/low_level/#graphs) is executed. See the [how-to guide](../cloud/how-tos/stream_values.md) for streaming values. +- `messages-tuple`: Stream LLM tokens for any messages generated inside a node. This mode is primarily meant for powering chat applications. See the [how-to guide](../cloud/how-tos/stream_messages.md) for streaming messages. +- `updates`: Streams updates to the state of the graph after each node is executed. See the [how-to guide](../cloud/how-tos/stream_updates.md) for streaming updates. +- `events`: Stream all events (including the state of the graph) that occur during graph execution. See the [how-to guide](../cloud/how-tos/stream_events.md) for streaming events. This can be used to do token-by-token streaming for LLMs. +- `debug`: Stream debug events throughout graph execution. See the [how-to guide](../cloud/how-tos/stream_debug.md) for streaming debug events. + +You can also specify multiple streaming modes at the same time. See the [how-to guide](../cloud/how-tos/stream_multiple.md) for configuring multiple streaming modes at the same time. + +See the [API reference](../cloud/reference/api/api_ref.html#tag/threads-runs/POST/threads/{thread_id}/runs/stream) for how to create streaming runs. + +Streaming modes `values`, `updates`, `messages-tuple` and `debug` are very similar to modes available in the LangGraph library - for a deeper conceptual explanation of those, you can see the [previous section](#streaming-graph-outputs-stream-and-astream). + +Streaming mode `events` is the same as using `.astream_events` in the LangGraph library - for a deeper conceptual explanation of this, you can see the [previous section](#streaming-graph-outputs-stream-and-astream). + +All events emitted have two attributes: + +- `event`: This is the name of the event +- `data`: This is data associated with the event \ No newline at end of file diff --git a/docs/docs/concepts/template_applications.md b/docs/docs/concepts/template_applications.md new file mode 100644 index 000000000..df8bc5e63 --- /dev/null +++ b/docs/docs/concepts/template_applications.md @@ -0,0 +1,19 @@ +# Template Applications + +!!! note Prerequisites + + - [LangGraph Studio](./langgraph_studio.md) + +Templates are open source reference applications designed to help you get started quickly when building with LangGraph. They provide working examples of common agentic workflows that can be customized to your needs. + +Templates can be accessed via [LangGraph Studio (macOS only)](langgraph_studio.md), or cloned directly from Github. You can download LangGraph Studio and see available templates [here](https://studio.langchain.com/). + +## Available templates + +| Template | Description | Python | JS/TS | +|---------------------------|------------------------------------------------------------------------------------------|------------------------------------------------------------------|---------------------------------------------------------------------| +| **New LangGraph Project** | A simple, minimal chatbot with memory. | [Repo](https://github.com/langchain-ai/new-langgraph-project) | [Repo](https://github.com/langchain-ai/new-langgraphjs-project) | +| **ReAct Agent** | A simple agent that can be flexibly extended to many tools. | [Repo](https://github.com/langchain-ai/react-agent) | [Repo](https://github.com/langchain-ai/react-agent-js) | +| **Memory Agent** | A ReAct-style agent with an additional tool to store memories for use across threads. | [Repo](https://github.com/langchain-ai/memory-agent) | [Repo](https://github.com/langchain-ai/memory-agent-js) | +| **Retrieval Agent** | An agent that includes a retrieval-based question-answering system. | [Repo](https://github.com/langchain-ai/retrieval-agent-template) | [Repo](https://github.com/langchain-ai/retrieval-agent-template-js) | +| **Data-Enrichment Agent** | An agent that performs web searches and organizes its findings into a structured format. | [Repo](https://github.com/langchain-ai/data-enrichment) | [Repo](https://github.com/langchain-ai/data-enrichment-js) | diff --git a/docs/docs/how-tos/autogen-integration.ipynb b/docs/docs/how-tos/autogen-integration.ipynb new file mode 100644 index 000000000..207a3b6f3 --- /dev/null +++ b/docs/docs/how-tos/autogen-integration.ipynb @@ -0,0 +1,332 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "100c0c81-6a9f-4ba1-b1a8-42aae82b7172", + "metadata": {}, + "source": [ + "# How to integrate LangGraph with AutoGen, CrewAI, and other frameworks\n", + "\n", + "LangGraph is a framework for building agentic and multi-agent applications. This includes integrating with other agent frameworks.\n", + "\n", + "This guides shows how to integrate LangGraph with other frameworks. The framework we show off integrating with is AutoGen, but this can easily be done with other frameworks.\n", + "\n", + "At a high level, the way this works is by wrapping the other agent inside a LangGraph node. LangGraph nodes can be anything - arbitrary code. This makes it easy to define an AutoGen (or CrewAI, or LlamaIndex, or other framework) agent and then reference it inside your graph. This allows you to create multi-agent systems where some of the sub-agents are actually defined in other frameworks." + ] + }, + { + "cell_type": "markdown", + "id": "b189ceb2-132b-4c7b-81b4-c7b8b062f833", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62417d3a-94f9-4a52-9962-12639d714966", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install autogen bs4 langgraph langchain-openai langchain-community" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d46da41d-0a71-4654-aec8-9e6ad8765236", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "\n", + "def _set_env(var: str):\n", + " if not os.environ.get(var):\n", + " os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "\n", + "\n", + "_set_env(\"OPENAI_API_KEY\")\n", + "_set_env(\"TAVILY_API_KEY\")" + ] + }, + { + "cell_type": "markdown", + "id": "1926bbc3-6b06-41e0-9604-860a2bbf8fa3", + "metadata": {}, + "source": [ + "## Define AutoGen agent\n", + "\n", + "Here we define our AutoGen agent. From https://github.com/microsoft/autogen/blob/0.2/notebook/agentchat_web_info.ipynb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "524de117-ff09-4b26-bfe8-a9f85a46ffd5", + "metadata": {}, + "outputs": [], + "source": [ + "import autogen\n", + "import os\n", + "\n", + "config_list = [{\"model\": \"gpt-4o\", \"api_key\": os.environ[\"OPENAI_API_KEY\"]}]\n", + "\n", + "llm_config = {\n", + " \"timeout\": 600,\n", + " \"cache_seed\": 42,\n", + " \"config_list\": config_list,\n", + " \"temperature\": 0,\n", + "}\n", + "\n", + "autogen_agent = autogen.AssistantAgent(\n", + " name=\"assistant\",\n", + " llm_config=llm_config,\n", + ")\n", + "\n", + "user_proxy = autogen.UserProxyAgent(\n", + " name=\"user_proxy\",\n", + " human_input_mode=\"NEVER\",\n", + " max_consecutive_auto_reply=10,\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\").rstrip().endswith(\"TERMINATE\"),\n", + " code_execution_config={\n", + " \"work_dir\": \"web\",\n", + " \"use_docker\": False,\n", + " }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n", + " llm_config=llm_config,\n", + " system_message=\"Reply TERMINATE if the task has been solved at full satisfaction. Otherwise, reply CONTINUE, or the reason why the task is not solved yet.\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "8aa858e2-4acb-4f75-be20-b9ccbbcb5073", + "metadata": {}, + "source": [ + "---" + ] + }, + { + "cell_type": "markdown", + "id": "d6bc7b69-4a36-44dc-a501-7e17122cc385", + "metadata": {}, + "source": [ + "## Define LangGraph agent\n", + "\n", + "We now define our LangGraph agent. We will create a simple ReAct-style agent with a web search tool" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a0fcdaac-8fbe-4589-8e61-8092165356cd", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.graph import StateGraph, START, MessagesState\n", + "from langgraph.prebuilt import ToolNode, create_react_agent\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "from langchain_openai import ChatOpenAI\n", + "from langchain_core.messages import HumanMessage, AIMessage\n", + "\n", + "model = ChatOpenAI(model=\"gpt-4o\")\n", + "tools = [TavilySearchResults(max_results=1)]\n", + "web_search_agent = create_react_agent(\n", + " model, tools, state_modifier=\"You are an agent specializing in web search\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "dcc478f5-4a35-43f8-bf59-9cb71289cd00", + "metadata": {}, + "source": [ + "## Create the multi-agent graph\n", + "\n", + "We will now create our multi-agent system combining the AutoGen agent with the LangGraph agent. We can do this by creating a graph that routes user query to the appropriate agent and executes the agent" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d129e4e1-3766-429a-b806-cde3d8bc0469", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Literal, TypedDict\n", + "\n", + "\n", + "class Route(TypedDict):\n", + " \"\"\"Decide where to go next\"\"\"\n", + "\n", + " goto: Literal[\"web_search_assistant\", \"coding_assistant\"]\n", + "\n", + "\n", + "def route(state: MessagesState) -> Literal[\"web_search_assistant\", \"coding_assistant\"]:\n", + " messages = [\n", + " {\n", + " \"role\": \"system\",\n", + " \"content\": \"Based on the conversation so far, decide who to call next: web search assistant or coding assistant.\",\n", + " }\n", + " ] + state[\"messages\"]\n", + " response = model.with_structured_output(Route).invoke(messages)\n", + " return response[\"goto\"]\n", + "\n", + "\n", + "def call_autogen_agent(state: MessagesState):\n", + " last_message = state[\"messages\"][-1]\n", + " response = user_proxy.initiate_chat(autogen_agent, message=last_message.content)\n", + " # get the final response from the agent\n", + " content = response.chat_history[-1][\"content\"]\n", + " return {\"messages\": AIMessage(content=content)}\n", + "\n", + "\n", + "builder = StateGraph(MessagesState)\n", + "builder.add_conditional_edges(START, route)\n", + "builder.add_node(\"coding_assistant\", call_autogen_agent)\n", + "builder.add_node(\"web_search_assistant\", web_search_agent)\n", + "graph = builder.compile()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "c761fc05-e8b6-4905-a793-eb7522d20060", + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import display, Image\n", + "\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "markdown", + "id": "23d629c3-1d6b-40af-adf6-915e15657566", + "metadata": {}, + "source": [ + "## Run the graph\n", + "\n", + "We can now run the graph. We can see in the examples below how we first route to the appropriate agent, then respond with the subagent." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "b528ddb9-ec12-433c-a174-33d94dc49d80", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33muser_proxy\u001b[0m (to assistant):\n", + "\n", + "Find numbers between 10 and 30 in fibonacci sequence\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33massistant\u001b[0m (to user_proxy):\n", + "\n", + "To find numbers between 10 and 30 in the Fibonacci sequence, we can generate the Fibonacci sequence and check which numbers fall within this range. Here's a plan:\n", + "\n", + "1. Generate Fibonacci numbers starting from 0.\n", + "2. Continue generating until the numbers exceed 30.\n", + "3. Collect and print the numbers that are between 10 and 30.\n", + "\n", + "Let's implement this in Python:\n", + "\n", + "```python\n", + "# filename: fibonacci_range.py\n", + "\n", + "def fibonacci_sequence():\n", + " a, b = 0, 1\n", + " while a <= 30:\n", + " if 10 <= a <= 30:\n", + " print(a)\n", + " a, b = b, a + b\n", + "\n", + "fibonacci_sequence()\n", + "```\n", + "\n", + "Save this code in a file named `fibonacci_range.py` and execute it. It will print the Fibonacci numbers between 10 and 30. TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n", + "{'coding_assistant': {'messages': AIMessage(content=\"To find numbers between 10 and 30 in the Fibonacci sequence, we can generate the Fibonacci sequence and check which numbers fall within this range. Here's a plan:\\n\\n1. Generate Fibonacci numbers starting from 0.\\n2. Continue generating until the numbers exceed 30.\\n3. Collect and print the numbers that are between 10 and 30.\\n\\nLet's implement this in Python:\\n\\n```python\\n# filename: fibonacci_range.py\\n\\ndef fibonacci_sequence():\\n a, b = 0, 1\\n while a <= 30:\\n if 10 <= a <= 30:\\n print(a)\\n a, b = b, a + b\\n\\nfibonacci_sequence()\\n```\\n\\nSave this code in a file named `fibonacci_range.py` and execute it. It will print the Fibonacci numbers between 10 and 30. TERMINATE\", additional_kwargs={}, response_metadata={}, id='e95a8aa1-5aa8-4ff2-ba74-2b2993ea0a5a')}}\n" + ] + } + ], + "source": [ + "for chunk in graph.stream(\n", + " {\n", + " \"messages\": [\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": \"Find numbers between 10 and 30 in fibonacci sequence\",\n", + " }\n", + " ]\n", + " }\n", + "):\n", + " print(chunk)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "b120f9ba-f640-482b-a457-1893d6db5543", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(('web_search_assistant:d08ae326-b6b2-1749-e8ea-4d308f22d819',), {'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_wZ5w5uO733Cc4CvWbc4Axq5F', 'function': {'arguments': '{\"query\":\"current weather in New York City\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 23, 'prompt_tokens': 96, 'total_tokens': 119, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_45cf54deae', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-0658af11-b90b-407c-a6a5-6a3b4a9f61e0-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'current weather in New York City'}, 'id': 'call_wZ5w5uO733Cc4CvWbc4Axq5F', 'type': 'tool_call'}], usage_metadata={'input_tokens': 96, 'output_tokens': 23, 'total_tokens': 119, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}})\n", + "(('web_search_assistant:d08ae326-b6b2-1749-e8ea-4d308f22d819',), {'tools': {'messages': [ToolMessage(content='[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'New York\\', \\'region\\': \\'New York\\', \\'country\\': \\'United States of America\\', \\'lat\\': 40.714, \\'lon\\': -74.006, \\'tz_id\\': \\'America/New_York\\', \\'localtime_epoch\\': 1732021037, \\'localtime\\': \\'2024-11-19 07:57\\'}, \\'current\\': {\\'last_updated_epoch\\': 1732020300, \\'last_updated\\': \\'2024-11-19 07:45\\', \\'temp_c\\': 8.3, \\'temp_f\\': 46.9, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Sunny\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/113.png\\', \\'code\\': 1000}, \\'wind_mph\\': 7.2, \\'wind_kph\\': 11.5, \\'wind_degree\\': 332, \\'wind_dir\\': \\'NNW\\', \\'pressure_mb\\': 1016.0, \\'pressure_in\\': 29.99, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 60, \\'cloud\\': 0, \\'feelslike_c\\': 6.3, \\'feelslike_f\\': 43.4, \\'windchill_c\\': 4.3, \\'windchill_f\\': 39.8, \\'heatindex_c\\': 7.0, \\'heatindex_f\\': 44.5, \\'dewpoint_c\\': 2.7, \\'dewpoint_f\\': 36.8, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 0.0, \\'gust_mph\\': 10.0, \\'gust_kph\\': 16.2}}\"}]', name='tavily_search_results_json', id='e955ebe9-631f-4dd1-b0a4-ee6a3caeba9f', tool_call_id='call_wZ5w5uO733Cc4CvWbc4Axq5F', artifact={'query': 'current weather in New York City', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'title': 'Weather in New York City', 'url': 'https://www.weatherapi.com/', 'content': \"{'location': {'name': 'New York', 'region': 'New York', 'country': 'United States of America', 'lat': 40.714, 'lon': -74.006, 'tz_id': 'America/New_York', 'localtime_epoch': 1732021037, 'localtime': '2024-11-19 07:57'}, 'current': {'last_updated_epoch': 1732020300, 'last_updated': '2024-11-19 07:45', 'temp_c': 8.3, 'temp_f': 46.9, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 7.2, 'wind_kph': 11.5, 'wind_degree': 332, 'wind_dir': 'NNW', 'pressure_mb': 1016.0, 'pressure_in': 29.99, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 60, 'cloud': 0, 'feelslike_c': 6.3, 'feelslike_f': 43.4, 'windchill_c': 4.3, 'windchill_f': 39.8, 'heatindex_c': 7.0, 'heatindex_f': 44.5, 'dewpoint_c': 2.7, 'dewpoint_f': 36.8, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 0.0, 'gust_mph': 10.0, 'gust_kph': 16.2}}\", 'score': 0.9997275, 'raw_content': None}], 'response_time': 3.24})]}})\n", + "(('web_search_assistant:d08ae326-b6b2-1749-e8ea-4d308f22d819',), {'agent': {'messages': [AIMessage(content='The current weather in New York City is sunny with a temperature of 8.3°C (46.9°F). The wind is coming from the north-northwest at 7.2 mph (11.5 kph), and the humidity level is 60%. The weather feels slightly cooler at 6.3°C (43.4°F) due to the wind chill.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 79, 'prompt_tokens': 535, 'total_tokens': 614, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_159d8341cc', 'finish_reason': 'stop', 'logprobs': None}, id='run-43d275f1-aacb-44f4-bdd0-8233c3765699-0', usage_metadata={'input_tokens': 535, 'output_tokens': 79, 'total_tokens': 614, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}})\n", + "((), {'web_search_assistant': {'messages': [HumanMessage(content=\"what's the weather in nyc?\", additional_kwargs={}, response_metadata={}, id='756466d3-18ce-4b8e-b4fc-ee59932ce9f4'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_wZ5w5uO733Cc4CvWbc4Axq5F', 'function': {'arguments': '{\"query\":\"current weather in New York City\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 23, 'prompt_tokens': 96, 'total_tokens': 119, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_45cf54deae', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-0658af11-b90b-407c-a6a5-6a3b4a9f61e0-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'current weather in New York City'}, 'id': 'call_wZ5w5uO733Cc4CvWbc4Axq5F', 'type': 'tool_call'}], usage_metadata={'input_tokens': 96, 'output_tokens': 23, 'total_tokens': 119, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), ToolMessage(content='[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'New York\\', \\'region\\': \\'New York\\', \\'country\\': \\'United States of America\\', \\'lat\\': 40.714, \\'lon\\': -74.006, \\'tz_id\\': \\'America/New_York\\', \\'localtime_epoch\\': 1732021037, \\'localtime\\': \\'2024-11-19 07:57\\'}, \\'current\\': {\\'last_updated_epoch\\': 1732020300, \\'last_updated\\': \\'2024-11-19 07:45\\', \\'temp_c\\': 8.3, \\'temp_f\\': 46.9, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Sunny\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/113.png\\', \\'code\\': 1000}, \\'wind_mph\\': 7.2, \\'wind_kph\\': 11.5, \\'wind_degree\\': 332, \\'wind_dir\\': \\'NNW\\', \\'pressure_mb\\': 1016.0, \\'pressure_in\\': 29.99, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 60, \\'cloud\\': 0, \\'feelslike_c\\': 6.3, \\'feelslike_f\\': 43.4, \\'windchill_c\\': 4.3, \\'windchill_f\\': 39.8, \\'heatindex_c\\': 7.0, \\'heatindex_f\\': 44.5, \\'dewpoint_c\\': 2.7, \\'dewpoint_f\\': 36.8, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 0.0, \\'gust_mph\\': 10.0, \\'gust_kph\\': 16.2}}\"}]', name='tavily_search_results_json', id='e955ebe9-631f-4dd1-b0a4-ee6a3caeba9f', tool_call_id='call_wZ5w5uO733Cc4CvWbc4Axq5F', artifact={'query': 'current weather in New York City', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'title': 'Weather in New York City', 'url': 'https://www.weatherapi.com/', 'content': \"{'location': {'name': 'New York', 'region': 'New York', 'country': 'United States of America', 'lat': 40.714, 'lon': -74.006, 'tz_id': 'America/New_York', 'localtime_epoch': 1732021037, 'localtime': '2024-11-19 07:57'}, 'current': {'last_updated_epoch': 1732020300, 'last_updated': '2024-11-19 07:45', 'temp_c': 8.3, 'temp_f': 46.9, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 7.2, 'wind_kph': 11.5, 'wind_degree': 332, 'wind_dir': 'NNW', 'pressure_mb': 1016.0, 'pressure_in': 29.99, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 60, 'cloud': 0, 'feelslike_c': 6.3, 'feelslike_f': 43.4, 'windchill_c': 4.3, 'windchill_f': 39.8, 'heatindex_c': 7.0, 'heatindex_f': 44.5, 'dewpoint_c': 2.7, 'dewpoint_f': 36.8, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 0.0, 'gust_mph': 10.0, 'gust_kph': 16.2}}\", 'score': 0.9997275, 'raw_content': None}], 'response_time': 3.24}), AIMessage(content='The current weather in New York City is sunny with a temperature of 8.3°C (46.9°F). The wind is coming from the north-northwest at 7.2 mph (11.5 kph), and the humidity level is 60%. The weather feels slightly cooler at 6.3°C (43.4°F) due to the wind chill.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 79, 'prompt_tokens': 535, 'total_tokens': 614, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_159d8341cc', 'finish_reason': 'stop', 'logprobs': None}, id='run-43d275f1-aacb-44f4-bdd0-8233c3765699-0', usage_metadata={'input_tokens': 535, 'output_tokens': 79, 'total_tokens': 614, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}})\n" + ] + } + ], + "source": [ + "for chunk in graph.stream(\n", + " {\"messages\": [{\"role\": \"user\", \"content\": \"what's the weather in nyc?\"}]},\n", + " subgraphs=True,\n", + "):\n", + " print(chunk)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/how-tos/autogen-langgraph-platform.ipynb b/docs/docs/how-tos/autogen-langgraph-platform.ipynb new file mode 100644 index 000000000..29bd24ba5 --- /dev/null +++ b/docs/docs/how-tos/autogen-langgraph-platform.ipynb @@ -0,0 +1,171 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "8381b6e0-29a6-48c5-b451-5d2549351249", + "metadata": {}, + "source": [ + "# How to use LangGraph Platform to deploy CrewAI, AutoGen, and other frameworks\n", + "\n", + "[LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) provides infrastructure for deploying agents. This integrates seamlessly with LangGraph, but can also work with other frameworks. The way to make this work is to wrap the agent in a single LangGraph node, and have that be the entire graph.\n", + "\n", + "Doing so will allow you to deploy to LangGraph Platform, and allows you to get a lot of the [benefits](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/). You get horizontally scalable infrastructure, a task queue to handle bursty operations, a persistence layer to power short term memory, and long term memory support.\n", + "\n", + "In this guide we show how to do this with an AutoGen agent, but this method should work for agents defined in other frameworks like CrewAI, LlamaIndex, and others as well." + ] + }, + { + "cell_type": "markdown", + "id": "1113cb16-b538-448c-924c-85731ce96ebd", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "f05993fa-9d03-4f45-bc13-0a8d87260d86", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "%pip install autogen langgraph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f4e0ca12-1714-4776-a30a-9527e519799b", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "\n", + "def _set_env(var: str):\n", + " if not os.environ.get(var):\n", + " os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "\n", + "\n", + "_set_env(\"OPENAI_API_KEY\")" + ] + }, + { + "cell_type": "markdown", + "id": "1926bbc3-6b06-41e0-9604-860a2bbf8fa3", + "metadata": {}, + "source": [ + "## Define autogen agent\n", + "\n", + "Here we define our AutoGen agent. From https://github.com/microsoft/autogen/blob/0.2/notebook/agentchat_web_info.ipynb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d4a14dc7-d565-4207-8788-525f85b9fb27", + "metadata": {}, + "outputs": [], + "source": [ + "import autogen\n", + "import os\n", + "\n", + "config_list = [{\"model\": \"gpt-4o\", \"api_key\": os.environ[\"OPENAI_API_KEY\"]}]\n", + "\n", + "llm_config = {\n", + " \"timeout\": 600,\n", + " \"cache_seed\": 42,\n", + " \"config_list\": config_list,\n", + " \"temperature\": 0,\n", + "}\n", + "\n", + "autogen_agent = autogen.AssistantAgent(\n", + " name=\"assistant\",\n", + " llm_config=llm_config,\n", + ")\n", + "\n", + "user_proxy = autogen.UserProxyAgent(\n", + " name=\"user_proxy\",\n", + " human_input_mode=\"NEVER\",\n", + " max_consecutive_auto_reply=10,\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\").rstrip().endswith(\"TERMINATE\"),\n", + " code_execution_config={\n", + " \"work_dir\": \"web\",\n", + " \"use_docker\": False,\n", + " }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n", + " llm_config=llm_config,\n", + " system_message=\"Reply TERMINATE if the task has been solved at full satisfaction. Otherwise, reply CONTINUE, or the reason why the task is not solved yet.\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "b1170836-f23e-4e4c-ab83-ce791cd7fbd2", + "metadata": {}, + "source": [ + "## Wrap in LangGraph\n", + "\n", + "We now wrap the AutoGen agent in a single LangGraph node, and make that the entire graph.\n", + "The main thing this involves is defining an Input and Output schema for the node, which you would need to do if deploying this manually, so it's no extra work" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "7b417c16-ff4e-4d5c-a9a9-0aaeeef6ede5", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.graph import StateGraph, MessagesState\n", + "\n", + "\n", + "def call_autogen_agent(state: MessagesState):\n", + " last_message = state[\"messages\"][-1]\n", + " response = user_proxy.initiate_chat(autogen_agent, message=last_message.content)\n", + " # get the final response from the agent\n", + " content = response.chat_history[-1][\"content\"]\n", + " return {\"messages\": {\"role\": \"assistant\", \"content\": content}}\n", + "\n", + "\n", + "graph = StateGraph(MessagesState)\n", + "graph.add_node(call_autogen_agent)\n", + "graph.set_entry_point(\"call_autogen_agent\")\n", + "graph = graph.compile()" + ] + }, + { + "cell_type": "markdown", + "id": "f6a18377-ac29-478f-a76a-b213f1a3c85d", + "metadata": {}, + "source": [ + "## Deploy with LangGraph Platform\n", + "\n", + "You can now deploy this as you normally would with LangGraph Platform. See [these instructions](https://langchain-ai.github.io/langgraph/concepts/deployment_options/) for more details." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/how-tos/branching.ipynb b/docs/docs/how-tos/branching.ipynb index 0a9f7e169..cbecf4f55 100644 --- a/docs/docs/how-tos/branching.ipynb +++ b/docs/docs/how-tos/branching.ipynb @@ -89,7 +89,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "09372b8b-edea-4b9d-9ec3-3d93ce1ba819", "metadata": {}, "outputs": [], @@ -132,7 +132,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "66f52a20", "metadata": {}, "outputs": [ @@ -163,7 +163,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "38846b01", "metadata": {}, "outputs": [ @@ -183,7 +183,7 @@ "{'aggregate': [\"I'm A\", \"I'm B\", \"I'm C\", \"I'm D\"]}" ] }, - "execution_count": 5, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -220,7 +220,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "259a7704-5aa0-4e4c-aeef-cca04e8be0ff", "metadata": {}, "outputs": [], @@ -238,6 +238,15 @@ " aggregate: Annotated[list, operator.add]\n", "\n", "\n", + "class ReturnNodeValue:\n", + " def __init__(self, node_secret: str):\n", + " self._value = node_secret\n", + "\n", + " def __call__(self, state: State) -> Any:\n", + " print(f\"Adding {self._value} to {state['aggregate']}\")\n", + " return {\"aggregate\": [self._value]}\n", + "\n", + "\n", "builder = StateGraph(State)\n", "builder.add_node(\"a\", ReturnNodeValue(\"I'm A\"))\n", "builder.add_edge(START, \"a\")\n", @@ -255,7 +264,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "id": "83320227-8ab3-44c0-b6cf-064a7a425b9f", "metadata": {}, "outputs": [ @@ -278,7 +287,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "3f971fa3-29e4-466f-a85e-2863bfecf7fe", "metadata": {}, "outputs": [ @@ -299,7 +308,7 @@ "{'aggregate': [\"I'm A\", \"I'm B\", \"I'm C\", \"I'm B2\", \"I'm D\"]}" ] }, - "execution_count": 8, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -322,7 +331,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "95f5e026", "metadata": {}, "outputs": [], @@ -341,6 +350,15 @@ " which: str\n", "\n", "\n", + "class ReturnNodeValue:\n", + " def __init__(self, node_secret: str):\n", + " self._value = node_secret\n", + "\n", + " def __call__(self, state: State) -> Any:\n", + " print(f\"Adding {self._value} to {state['aggregate']}\")\n", + " return {\"aggregate\": [self._value]}\n", + "\n", + "\n", "builder = StateGraph(State)\n", "builder.add_node(\"a\", ReturnNodeValue(\"I'm A\"))\n", "builder.add_edge(START, \"a\")\n", @@ -372,7 +390,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "id": "1d0e6c56", "metadata": {}, "outputs": [ @@ -395,7 +413,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "id": "7134f652", "metadata": {}, "outputs": [ @@ -415,7 +433,7 @@ "{'aggregate': [\"I'm A\", \"I'm B\", \"I'm C\", \"I'm E\"], 'which': 'bc'}" ] }, - "execution_count": 11, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -426,7 +444,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "id": "b130e694", "metadata": {}, "outputs": [ @@ -446,7 +464,7 @@ "{'aggregate': [\"I'm A\", \"I'm C\", \"I'm D\", \"I'm E\"], 'which': 'cd'}" ] }, - "execution_count": 12, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -471,7 +489,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "id": "836bc12d", "metadata": {}, "outputs": [], @@ -564,7 +582,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "id": "932c497e", "metadata": {}, "outputs": [ @@ -587,7 +605,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "id": "933b3afd", "metadata": {}, "outputs": [ @@ -608,7 +626,7 @@ " 'which': 'bc'}" ] }, - "execution_count": 15, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -619,7 +637,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "id": "e30531bf", "metadata": {}, "outputs": [ @@ -640,7 +658,7 @@ " 'which': 'cd'}" ] }, - "execution_count": 16, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -666,7 +684,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/docs/docs/how-tos/configuration.ipynb b/docs/docs/how-tos/configuration.ipynb index 7ea271681..589131b95 100644 --- a/docs/docs/how-tos/configuration.ipynb +++ b/docs/docs/how-tos/configuration.ipynb @@ -345,7 +345,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.11.1" } }, "nbformat": 4, diff --git a/docs/docs/how-tos/deploy-self-hosted.md b/docs/docs/how-tos/deploy-self-hosted.md new file mode 100644 index 000000000..5e1bf93e4 --- /dev/null +++ b/docs/docs/how-tos/deploy-self-hosted.md @@ -0,0 +1,137 @@ +# How to do a Self-hosted deployment of LangGraph + +!!! info "Prerequisites" + + - [Application Structure](../concepts/application_structure.md) + - [Deployment Options](../concepts/deployment_options.md) + +This how-to guide will walk you through how to create a docker image from an existing LangGraph application, so you can deploy it on your own infrastructure. + +## How it works + +With the self-hosted deployment option, you are responsible for managing the infrastructure, including setting up and maintaining necessary databases, Redis instances, and other services. + +You will need to do the following: + +1. Deploy Redis and Postgres instances on your own infrastructure. +2. Build a docker image with the [LangGraph Server](../concepts/langgraph_server.md) using the [LangGraph CLI](../concepts/langgraph_cli.md). +3. Deploy a web server that will run the docker image and pass in the necessary environment variables. + +## Environment Variables + +You will eventually need to pass in the following environment variables to the LangGraph Deploy server: + +- `REDIS_URI`: Connection details to a Redis instance. Redis will be used as a pub-sub broker to enable streaming real time output from background runs. +- `DATABASE_URI`: Postgres connection details. Postgres will be used to store assistants, threads, runs, persist thread state and long term memory, and to manage the state of the background task queue with 'exactly once' semantics. +- `LANGSMITH_API_KEY`: (If using [Self-Hosted Lite](../concepts/deployment_options.md#self-hosted-lite)) LangSmith API key. This will be used to authenticate ONCE at server start up. +- `LANGGRAPH_CLOUD_LICENSE_KEY`: (If using [Self-Hosted Enterprise](../concepts/deployment_options.md#self-hosted-enterprise)) LangGraph Platform license key. This will be used to authenticate ONCE at server start up. + + +## Build the Docker Image + +Please read the [Application Structure](../concepts/application_structure.md) guide to understand how to structure your LangGraph application. + +If the application is structured correctly, you can build a docker image with the LangGraph Deploy server. + +To build the docker image, you first need to install the CLI: + +```shell +pip install -U langgraph-cli +``` + +You can then use: + +``` +langgraph build -t my-image +``` + +This will build a docker image with the LangGraph Deploy server. The `-t my-image` is used to tag the image with a name. + +When running this server, you need to pass three environment variables: + +## Running the application locally + +### Using Docker + +```shell +docker run \ + --env-file .env \ + -p 8123:8000 \ + -e REDIS_URI="foo" \ + -e DATABASE_URI="bar" \ + -e LANGSMITH_API_KEY="baz" \ + my-image +``` + +If you want to run this quickly without setting up a separate Redis and Postgres instance, you can use this docker compose file. + +!!! note + + * You need to replace `my-image` with the name of the image you built in the previous step (from `langgraph build`). + and you should provide appropriate values for `REDIS_URI`, `DATABASE_URI`, and `LANGSMITH_API_KEY`. + * If your application requires additional environment variables, you can pass them in a similar way. + * If using [Self-Hosted Enterprise](../concepts/deployment_options.md#self-hosted-enterprise), you must provide `LANGGRAPH_CLOUD_LICENSE_KEY` as an additional environment variable. + + +### Using Docker Compose + +```yml +volumes: + langgraph-data: + driver: local +services: + langgraph-redis: + image: redis:6 + healthcheck: + test: redis-cli ping + interval: 5s + timeout: 1s + retries: 5 + langgraph-postgres: + image: postgres:16 + ports: + - "5433:5432" + environment: + POSTGRES_DB: postgres + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + volumes: + - langgraph-data:/var/lib/postgresql/data + healthcheck: + test: pg_isready -U postgres + start_period: 10s + timeout: 1s + retries: 5 + interval: 5s + langgraph-api: + image: ${IMAGE_NAME} + ports: + - "8123:8000" + depends_on: + langgraph-redis: + condition: service_healthy + langgraph-postgres: + condition: service_healthy + env_file: + - .env + environment: + REDIS_URI: redis://langgraph-redis:6379 + LANGSMITH_API_KEY: ${LANGSMITH_API_KEY} + POSTGRES_URI: postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable +``` + +You can then run `docker compose up` with this Docker compose file in the same folder. + +This will spin up LangGraph Deploy on port `8123` (if you want to change this, you can change this by changing the ports in the `langgraph-api` volume). + +You can test that the application is up by checking: + +```shell +curl --request GET --url 0.0.0.0:8123/ok +``` +Assuming everything is running correctly, you should see a response like: + +```shell +{"ok":true} +``` + diff --git a/docs/docs/how-tos/index.md b/docs/docs/how-tos/index.md index 2ecf2d853..a224e0f31 100644 --- a/docs/docs/how-tos/index.md +++ b/docs/docs/how-tos/index.md @@ -1,32 +1,38 @@ --- hide: - - toc + - navigation +title: How-to Guides +description: How to accomplish common tasks in LangGraph --- -# How-to guides +# How-to Guides -Welcome to the LangGraph how-to guides! These guides provide practical, step-by-step instructions for accomplishing key tasks in LangGraph. +Here you’ll find answers to “How do I...?” types of questions. These guides are **goal-oriented** and concrete; they're meant to help you complete a specific task. For conceptual explanations see the [Conceptual guide](../concepts/index.md). For end-to-end walk-throughs see [Tutorials](../tutorials/index.md). For comprehensive descriptions of every class and function see the [API Reference](../reference/index.md). -## Controllability +## LangGraph + +### Controllability + +LangGraph offers a high level of control over the execution of your graph. -LangGraph is known for being a highly controllable agent framework. These how-to guides show how to achieve that controllability. - [How to create branches for parallel execution](branching.ipynb) - [How to create map-reduce branches for parallel execution](map-reduce.ipynb) - [How to control graph recursion limit](recursion-limit.ipynb) -## Persistence +### Persistence -LangGraph makes it easy to persist state across graph runs (thread-level persistence) and across threads (cross-thread persistence). These how-to guides show how to add persistence to your graph. +[LangGraph Persistence](../concepts/persistence.md) makes it easy to persist state across graph runs (thread-level persistence) and across threads (cross-thread persistence). These how-to guides show how to add persistence to your graph. - [How to add thread-level persistence to your graph](persistence.ipynb) +- [How to add thread-level persistence to subgraphs](subgraph-persistence.ipynb) - [How to add cross-thread persistence to your graph](cross-thread-persistence.ipynb) - [How to use Postgres checkpointer for persistence](persistence_postgres.ipynb) - [How to create a custom checkpointer using MongoDB](persistence_mongodb.ipynb) - [How to create a custom checkpointer using Redis](persistence_redis.ipynb) -## Memory +### Memory LangGraph makes it easy to manage conversation [memory](../concepts/memory.md) in your graph. These how-to guides show how to implement different strategies for that. @@ -34,22 +40,21 @@ LangGraph makes it easy to manage conversation [memory](../concepts/memory.md) i - [How to delete messages](memory/delete-messages.ipynb) - [How to add summary conversation memory](memory/add-summary-conversation-history.ipynb) -## Human in the Loop +### Human-in-the-loop -One of LangGraph's main benefits is that it makes human-in-the-loop workflows easy. -These guides cover common examples of that. +[Human-in-the-loop](../concepts/human_in_the_loop.md) functionality allows +you to involve humans in the decision-making process of your graph. These how-to guides show how to implement human-in-the-loop workflows in your graph. - [How to add breakpoints](human_in_the_loop/breakpoints.ipynb) - [How to add dynamic breakpoints](human_in_the_loop/dynamic_breakpoints.ipynb) - [How to edit graph state](human_in_the_loop/edit-graph-state.ipynb) - [How to wait for user input](human_in_the_loop/wait-user-input.ipynb) - [How to view and update past graph state](human_in_the_loop/time-travel.ipynb) -- [Review tool calls](human_in_the_loop/review-tool-calls.ipynb) +- [How to review tool calls](human_in_the_loop/review-tool-calls.ipynb) -## Streaming +### Streaming -LangGraph is built to be streaming first. -These guides show how to use different streaming modes. +[Streaming](../concepts/streaming.md) is crucial for enhancing the responsiveness of applications built on LLMs. By displaying output progressively, even before a complete response is ready, streaming significantly improves user experience (UX), particularly when dealing with the latency of LLMs. - [How to stream full state of your graph](stream-values.ipynb) - [How to stream state updates of your graph](stream-updates.ipynb) @@ -63,7 +68,11 @@ These guides show how to use different streaming modes. - [How to stream from subgraphs](streaming-subgraphs.ipynb) - [How to disable streaming for models that don't support it](disable-streaming.ipynb) -## Tool calling +### Tool calling + +[Tool calling](https://python.langchain.com/docs/concepts/tool_calling/) is a type of chat model API that accepts tool schemas, along with messages, as input and returns invocations of those tools as part of the output message. + +These how-to guides show common patterns for tool calling with LangGraph: - [How to call tools using ToolNode](tool-calling.ipynb) - [How to handle tool calling errors](tool-calling-errors.ipynb) @@ -71,36 +80,153 @@ These guides show how to use different streaming modes. - [How to pass config to tools](pass-config-to-tools.ipynb) - [How to handle large numbers of tools](many-tools.ipynb) -## Subgraphs +### Subgraphs -- [How to create subgraphs](subgraph.ipynb) -- [How to manage state in subgraphs](subgraphs-manage-state.ipynb) +[Subgraphs](../concepts/low_level.md#subgraphs) allow you to reuse an existing graph from another graph. These how-to guides show how to use subgraphs: + +- [How to add and use subgraphs](subgraph.ipynb) +- [How to view and update state in subgraphs](subgraphs-manage-state.ipynb) - [How to transform inputs and outputs of a subgraph](subgraph-transform-state.ipynb) -## State Management +### State Management -- [Use Pydantic model as state](state-model.ipynb) -- [Have a separate input and output schema](input_output_schema.ipynb) -- [Pass private state between nodes inside the graph](pass_private_state.ipynb) +- [How to use Pydantic model as state](state-model.ipynb) +- [How to define input/output schema for your graph](input_output_schema.ipynb) +- [How to pass private state between nodes inside the graph](pass_private_state.ipynb) -## Other +### Other - [How to run graph asynchronously](async.ipynb) - [How to visualize your graph](visualization.ipynb) - [How to add runtime configuration to your graph](configuration.ipynb) -- [How to use a Pydantic model as your state](state-model.ipynb) - [How to add node retries](node-retries.ipynb) - [How to force function calling agent to structure output](react-agent-structured-output.ipynb) - [How to pass custom LangSmith run ID for graph runs](run-id-langsmith.ipynb) - [How to return state before hitting recursion limit](return-when-recursion-limit-hits.ipynb) +- [How to integrate LangGraph with AutoGen, CrewAI, and other frameworks](autogen-integration.ipynb) -## Prebuilt ReAct Agent +### Prebuilt ReAct Agent -These guides show how to use the prebuilt ReAct agent. -Please note that here will we use a **prebuilt agent**. One of the big benefits of LangGraph is that you can easily create your own agent architectures. So while it's fine to start here to build an agent quickly, we would strongly recommend learning how to build your own agent so that you can take full advantage of LangGraph. +The LangGraph [prebuilt ReAct agent](../reference/prebuilt.md#langgraph.prebuilt.chat_agent_executor.create_react_agent) is pre-built implementation of a [tool calling agent](../concepts/agentic_concepts.md#tool-calling-agent). + +One of the big benefits of LangGraph is that you can easily create your own agent architectures. So while it's fine to start here to build an agent quickly, we would strongly recommend learning how to build your own agent so that you can take full advantage of LangGraph. + +These guides show how to use the prebuilt ReAct agent: - [How to create a ReAct agent](create-react-agent.ipynb) - [How to add memory to a ReAct agent](create-react-agent-memory.ipynb) - [How to add a custom system prompt to a ReAct agent](create-react-agent-system-prompt.ipynb) - [How to add human-in-the-loop processes to a ReAct agent](create-react-agent-hitl.ipynb) -- [How to create prebuilt ReAct agent from scratch](react-agent-from-scratch.ipynb) \ No newline at end of file +- [How to create prebuilt ReAct agent from scratch](react-agent-from-scratch.ipynb) + +## LangGraph Platform + +This section includes how-to guides for LangGraph Platform. + +LangGraph Platform is a commercial solution for deploying agentic applications in production, built on the open-source LangGraph framework. + +The LangGraph Platform offers a few different deployment options described in the [deployment options guide](../concepts/deployment_options.md). + +!!! tip + + * LangGraph is an MIT-licensed open-source library, which we are committed to maintaining and growing for the community. + * You can always deploy LangGraph applications on your own infrastructure using the open-source LangGraph project without using LangGraph Platform. + +### Application Structure + +Learn how to set up your app for deployment to LangGraph Platform: + +- [How to set up app for deployment (requirements.txt)](../cloud/deployment/setup.md) +- [How to set up app for deployment (pyproject.toml)](../cloud/deployment/setup_pyproject.md) +- [How to set up app for deployment (JavaScript)](../cloud/deployment/setup_javascript.md) +- [How to customize Dockerfile](../cloud/deployment/custom_docker.md) +- [How to test locally](../cloud/deployment/test_locally.md) +- [How to rebuild graph at runtime](../cloud/deployment/graph_rebuild.md) +- [How to use LangGraph Platform to deploy CrewAI, AutoGen, and other frameworks](autogen-langgraph-platform.ipynb) + +### Deployment + +LangGraph applications can be deployed using LangGraph Cloud, which provides a range of services to help you deploy, manage, and scale your applications. + +- [How to deploy to LangGraph cloud](../cloud/deployment/cloud.md) +- [How to deploy to a self-hosted environment](./deploy-self-hosted.md) +- [How to interact with the deployment using RemoteGraph](./use-remote-graph.md) +### Assistants + +[Assistants](../concepts/assistants.md) is a configured instance of a template. + +- [How to configure agents](../cloud/how-tos/configuration_cloud.md) +- [How to version assistants](../cloud/how-tos/assistant_versioning.md) + +### Threads + +- [How to copy threads](../cloud/how-tos/copy_threads.md) +- [How to check status of your threads](../cloud/how-tos/check_thread_status.md) + +### Runs + +LangGraph Cloud supports multiple types of runs besides streaming runs. + +- [How to run an agent in the background](../cloud/how-tos/background_run.md) +- [How to run multiple agents in the same thread](../cloud/how-tos/same-thread.md) +- [How to create cron jobs](../cloud/how-tos/cron_jobs.md) +- [How to create stateless runs](../cloud/how-tos/stateless_runs.md) + +### Streaming + +Streaming the results of your LLM application is vital for ensuring a good user experience, especially when your graph may call multiple models and take a long time to fully complete a run. Read about how to stream values from your graph in these how to guides: + +- [How to stream values](../cloud/how-tos/stream_values.md) +- [How to stream updates](../cloud/how-tos/stream_updates.md) +- [How to stream messages](../cloud/how-tos/stream_messages.md) +- [How to stream events](../cloud/how-tos/stream_events.md) +- [How to stream in debug mode](../cloud/how-tos/stream_debug.md) +- [How to stream multiple modes](../cloud/how-tos/stream_multiple.md) + +### Human-in-the-loop + +When creating complex graphs, leaving every decision up to the LLM can be dangerous, especially when the decisions involve invoking certain tools or accessing specific documents. To remedy this, LangGraph allows you to insert human-in-the-loop behavior to ensure your graph does not have undesired outcomes. Read more about the different ways you can add human-in-the-loop capabilities to your LangGraph Cloud projects in these how-to guides: + +- [How to add a breakpoint](../cloud/how-tos/human_in_the_loop_breakpoint.md) +- [How to wait for user input](../cloud/how-tos/human_in_the_loop_user_input.md) +- [How to edit graph state](../cloud/how-tos/human_in_the_loop_edit_state.md) +- [How to replay and branch from prior states](../cloud/how-tos/human_in_the_loop_time_travel.md) +- [How to review tool calls](../cloud/how-tos/human_in_the_loop_review_tool_calls.md) + +### Double-texting + +Graph execution can take a while, and sometimes users may change their mind about the input they wanted to send before their original input has finished running. For example, a user might notice a typo in their original request and will edit the prompt and resend it. Deciding what to do in these cases is important for ensuring a smooth user experience and preventing your graphs from behaving in unexpected ways. The following how-to guides provide information on the various options LangGraph Cloud gives you for dealing with double-texting: + +- [How to use the interrupt option](../cloud/how-tos/interrupt_concurrent.md) +- [How to use the rollback option](../cloud/how-tos/rollback_concurrent.md) +- [How to use the reject option](../cloud/how-tos/reject_concurrent.md) +- [How to use the enqueue option](../cloud/how-tos/enqueue_concurrent.md) + +### Webhooks + +- [How to integrate webhooks](../cloud/how-tos/webhooks.md) + +### Cron Jobs + +- [How to create cron jobs](../cloud/how-tos/cron_jobs.md) + +### LangGraph Studio + +LangGraph Studio is a built-in UI for visualizing, testing, and debugging your agents. + +- [How to connect to a LangGraph Cloud deployment](../cloud/how-tos/test_deployment.md) +- [How to connect to a local deployment](../cloud/how-tos/test_local_deployment.md) +- [How to test your graph in LangGraph Studio](../cloud/how-tos/invoke_studio.md) +- [How to interact with threads in LangGraph Studio](../cloud/how-tos/threads_studio.md) + +## Troubleshooting + +These are the guides for resolving common errors you may find while building with LangGraph. Errors referenced below will have an `lc_error_code` property corresponding to one of the below codes when they are thrown in code. + +- [GRAPH_RECURSION_LIMIT](../troubleshooting/errors/GRAPH_RECURSION_LIMIT.md) +- [INVALID_CONCURRENT_GRAPH_UPDATE](../troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE.md) +- [INVALID_GRAPH_NODE_RETURN_VALUE](../troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE.md) +- [MULTIPLE_SUBGRAPHS](../troubleshooting/errors/MULTIPLE_SUBGRAPHS.md) +- [INVALID_CHAT_HISTORY](../troubleshooting/errors/INVALID_CHAT_HISTORY.md) + + diff --git a/docs/docs/how-tos/local-studio.md b/docs/docs/how-tos/local-studio.md new file mode 100644 index 000000000..8e4e29f89 --- /dev/null +++ b/docs/docs/how-tos/local-studio.md @@ -0,0 +1,86 @@ +# How to connect a local agent to LangGraph Studio + +This guide shows you how to connect your local agent to [LangGraph Studio](../concepts/langgraph_studio.md) for visualization, interaction, and debugging. + +## Connection Options + +There are two ways to connect your local agent to LangGraph Studio: + +- [LangGraph Desktop](../concepts/langgraph_studio.md#desktop-app): Application, Mac only, requires Docker +- [Development Server](../concepts/langgraph_studio.md#dev-server): Python package, all platforms, no Docker + +In this guide we will cover how to use the development server as that is generally an easier and better experience. + +## Setup your application + +First, you will need to setup your application in the proper format. +This means defining a `langgraph.json` file which contains paths to your agent(s). +See [this guide](../concepts/application_structure.md) for information on how to do so. + +## Install langgraph-cli + +You will need to install [`langgraph-cli`](../cloud/reference/cli.md#langgraph-cli) (version `0.1.55` or higher). +You will need to make sure to install the `inmem` extras. + +```shell +pip install "langgraph-cli[inmem]==0.1.55" +``` + +## Run the development server + +1. Navigate to your project directory (where `langgraph.json` is located) + +2. Start the server: + ```bash + langgraph dev + ``` + +This will look for the `langgraph.json` file in your current directory. +In there, it will find the paths to the graph(s), and start those up. +It will then automatically connect to the cloud-hosted studio. + +## Use the studio + +After connecting to the studio, a browser window should automatically pop up. +This will use the cloud hosted studio UI to connect to your local development server. +Your graph is still running locally, the UI is connecting to visualizing the agent and threads that are defined locally. + +The graph will always use the most up-to-date code, so you will be able to change the underlying code and have it automatically reflected in the studio. +This is useful for debugging workflows. +You can run your graph in the UI until it messes up, go in and change your code, and then rerun from the node that failed. + +# (Optional) Attach a debugger + +For step-by-step debugging with breakpoints and variable inspection: + +```bash +# Install debugpy package +pip install debugpy + +# Start server with debugging enabled +langgraph dev --debug-port 5678 +``` + +Then attach your preferred debugger: + +=== "VS Code" + Add this configuration to `launch.json`: + ```json + { + "name": "Attach to LangGraph", + "type": "debugpy", + "request": "attach", + "connect": { + "host": "0.0.0.0", + "port": 5678 + } + } + ``` + Specify the port number you chose in the previous step. + +=== "PyCharm" + 1. Go to Run → Edit Configurations + 2. Click + and select "Python Debug Server" + 3. Set IDE host name: `localhost` + 4. Set port: `5678` (or the port number you chose in the previous step) + 5. Click "OK" and start debugging \ No newline at end of file diff --git a/docs/docs/how-tos/subgraph-persistence.ipynb b/docs/docs/how-tos/subgraph-persistence.ipynb new file mode 100644 index 000000000..4488fae6c --- /dev/null +++ b/docs/docs/how-tos/subgraph-persistence.ipynb @@ -0,0 +1,379 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "176e8dbb-1a0a-49ce-a10e-2417e8ea17a0", + "metadata": {}, + "source": [ + "# How to add thread-level persistence to subgraphs" + ] + }, + { + "cell_type": "markdown", + "id": "8c67581a-49fb-4597-a7fc-6774581c2160", + "metadata": {}, + "source": [ + "
\n", + "

Prerequisites

\n", + "

\n", + " This guide assumes familiarity with the following:\n", + "

\n", + "

\n", + "
\n", + "\n", + "This guide shows how you can add [thread-level](https://langchain-ai.github.io/langgraph/how-tos/persistence/) persistence to graphs that use [subgraphs](https://langchain-ai.github.io/langgraph/how-tos/subgraph/)." + ] + }, + { + "cell_type": "markdown", + "id": "8f83b855-ab23-4de7-9559-702cad9a29c6", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "First, let's install the required packages" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "77d1eafa-3252-45f6-9af0-d94e1f9c5c9e", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install -U langgraph" + ] + }, + { + "cell_type": "markdown", + "id": "2e60c6cd-bf4e-46af-9761-b872d0fbe3b6", + "metadata": {}, + "source": [ + "
\n", + "

Set up LangSmith for LangGraph development

\n", + "

\n", + " Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started here. \n", + "

\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "871b9056-fec7-4683-8c22-f56c91f5b13b", + "metadata": {}, + "source": [ + "## Define the graph with persistence" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "9f1303ef-df37-48e0-8a59-8ff169c52c5b", + "metadata": {}, + "source": [ + "To add persistence to a graph with subgraphs, all you need to do is pass a [checkpointer](https://langchain-ai.github.io/langgraph/reference/checkpoints/#langgraph.checkpoint.base.BaseCheckpointSaver) when **compiling the parent graph**. LangGraph will automatically propagate the checkpointer to the child subgraphs." + ] + }, + { + "cell_type": "markdown", + "id": "c74cde2e-c127-4326-8d36-b6acef987f0a", + "metadata": {}, + "source": [ + "!!! note\n", + " You **shouldn't provide** a checkpointer when compiling a subgraph. Instead, you must define a **single** checkpointer that you pass to `parent_graph.compile()`, and LangGraph will automatically propagate the checkpointer to the child subgraphs. If you pass the checkpointer to the `subgraph.compile()`, it will simply be ignored. This also applies when you [add a node function that invokes the subgraph](../subgraph#add-a-node-function-that-invokes-the-subgraph)." + ] + }, + { + "cell_type": "markdown", + "id": "c3a1fe22-1ca9-45eb-a35b-71b9c905e8c5", + "metadata": {}, + "source": [ + "Let's define a simple graph with a single subgraph node to show how to do this." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0d76f0c0-bd77-4eca-9527-27bcdf85dd42", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langgraph.graph import START, StateGraph\n", + "from langgraph.checkpoint.memory import MemorySaver\n", + "from typing import TypedDict\n", + "\n", + "\n", + "# subgraph\n", + "\n", + "\n", + "class SubgraphState(TypedDict):\n", + " foo: str # note that this key is shared with the parent graph state\n", + " bar: str\n", + "\n", + "\n", + "def subgraph_node_1(state: SubgraphState):\n", + " return {\"bar\": \"bar\"}\n", + "\n", + "\n", + "def subgraph_node_2(state: SubgraphState):\n", + " # note that this node is using a state key ('bar') that is only available in the subgraph\n", + " # and is sending update on the shared state key ('foo')\n", + " return {\"foo\": state[\"foo\"] + state[\"bar\"]}\n", + "\n", + "\n", + "subgraph_builder = StateGraph(SubgraphState)\n", + "subgraph_builder.add_node(subgraph_node_1)\n", + "subgraph_builder.add_node(subgraph_node_2)\n", + "subgraph_builder.add_edge(START, \"subgraph_node_1\")\n", + "subgraph_builder.add_edge(\"subgraph_node_1\", \"subgraph_node_2\")\n", + "subgraph = subgraph_builder.compile()\n", + "\n", + "\n", + "# parent graph\n", + "\n", + "\n", + "class State(TypedDict):\n", + " foo: str\n", + "\n", + "\n", + "def node_1(state: State):\n", + " return {\"foo\": \"hi! \" + state[\"foo\"]}\n", + "\n", + "\n", + "builder = StateGraph(State)\n", + "builder.add_node(\"node_1\", node_1)\n", + "# note that we're adding the compiled subgraph as a node to the parent graph\n", + "builder.add_node(\"node_2\", subgraph)\n", + "builder.add_edge(START, \"node_1\")\n", + "builder.add_edge(\"node_1\", \"node_2\")" + ] + }, + { + "cell_type": "markdown", + "id": "47084b1f-9fd5-40a9-9d75-89eb5f853d02", + "metadata": {}, + "source": [ + "We can now compile the graph with an in-memory checkpointer (`MemorySaver`)." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "7657d285-c896-40c9-a569-b4a3b9c230c7", + "metadata": {}, + "outputs": [], + "source": [ + "checkpointer = MemorySaver()\n", + "# You must only pass checkpointer when compiling the parent graph.\n", + "# LangGraph will automatically propagate the checkpointer to the child subgraphs.\n", + "graph = builder.compile(checkpointer=checkpointer)" + ] + }, + { + "cell_type": "markdown", + "id": "0d193e3c-4ec3-4034-beed-8e5550c6542c", + "metadata": {}, + "source": [ + "## Verify persistence works" + ] + }, + { + "cell_type": "markdown", + "id": "eb69a5f0-b92e-4d4e-9aa9-c4c4ec7de91a", + "metadata": {}, + "source": [ + "Let's now run the graph and inspect the persisted state for both the parent graph and the subgraph to verify that persistence works. We should expect to see the final execution results for both the parent and subgraph in `state.values`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "13da686e-6ed6-4b83-93e8-1631fcc8c2a9", + "metadata": {}, + "outputs": [], + "source": [ + "config = {\"configurable\": {\"thread_id\": \"1\"}}" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "8721f045-2e82-4bf0-9d85-5ba6ecf899d6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'node_1': {'foo': 'hi! foo'}}\n", + "{'subgraph_node_1': {'bar': 'bar'}}\n", + "{'subgraph_node_2': {'foo': 'hi! foobar'}}\n", + "{'node_2': {'foo': 'hi! foobar'}}\n" + ] + } + ], + "source": [ + "for _, chunk in graph.stream({\"foo\": \"foo\"}, config, subgraphs=True):\n", + " print(chunk)" + ] + }, + { + "cell_type": "markdown", + "id": "ec6b5ce4-becc-4910-8a6d-d6b60d9d6f60", + "metadata": {}, + "source": [ + "We can now view the parent graph state by calling `graph.get_state()` with the same config that we used to invoke the graph." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "3e817283-142d-4fda-8cb1-8de34717f833", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'foo': 'hi! foobar'}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph.get_state(config).values" + ] + }, + { + "cell_type": "markdown", + "id": "fbc4f30b-941e-4140-8bfa-3b8cc670489c", + "metadata": {}, + "source": [ + "To view the subgraph state, we need to do two things:\n", + "\n", + "1. Find the most recent config value for the subgraph\n", + "2. Use `graph.get_state()` to retrieve that value for the most recent subgraph config.\n", + "\n", + "To find the correct config, we can examine the state history from the parent graph and find the state snapshot before we return results from `node_2` (the node with subgraph):" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e896628f-36b2-45eb-b7c5-c64c1098f328", + "metadata": {}, + "outputs": [], + "source": [ + "state_with_subgraph = [\n", + " s for s in graph.get_state_history(config) if s.next == (\"node_2\",)\n", + "][0]" + ] + }, + { + "cell_type": "markdown", + "id": "7af49977-42b1-40a1-88f1-f07437f8b7f9", + "metadata": {}, + "source": [ + "The state snapshot will include the list of `tasks` to be executed next. When using subgraphs, the `tasks` will contain the config that we can use to retrieve the subgraph state:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "21e96df3-946d-40f8-8d6d-055ae4177452", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'configurable': {'thread_id': '1',\n", + " 'checkpoint_ns': 'node_2:6ef111a6-f290-7376-0dfc-a4152307bc5b'}}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "subgraph_config = state_with_subgraph.tasks[0].state\n", + "subgraph_config" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "1d2401b3-d52b-4895-a5d1-dccf015ba216", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'foo': 'hi! foobar', 'bar': 'bar'}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph.get_state(subgraph_config).values" + ] + }, + { + "cell_type": "markdown", + "id": "40aded92-99dd-427b-932d-aa78f474c271", + "metadata": {}, + "source": [ + "If you want to learn more about how to modify the subgraph state for human-in-the-loop workflows, check out this [how-to guide](https://langchain-ai.github.io/langgraph/how-tos/subgraphs-manage-state/)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/how-tos/subgraph.ipynb b/docs/docs/how-tos/subgraph.ipynb index b847d7760..84e677e52 100644 --- a/docs/docs/how-tos/subgraph.ipynb +++ b/docs/docs/how-tos/subgraph.ipynb @@ -9,7 +9,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# How to create subgraphs\n", + "# How to add and use subgraphs\n", "\n", "
\n", "

Prerequisites

\n", @@ -17,25 +17,27 @@ " This guide assumes familiarity with the following:\n", " \n", "

\n", "
\n", "\n", - "For more complex systems, subgraphs are a useful design principle. Subgraphs allow you to create and manage different states in different parts of your graph. This allows you build things like [multi-agent teams](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/hierarchical_agent_teams/), where each team can track its own separate state. This guide shows how you can add subgraphs to your graph.\n", + "[Subgraphs](https://langchain-ai.github.io/langgraph/concepts/low_level/#subgraphs) allow you to build complex systems with multiple components that are themselves graphs. A common use case for using subgraphs is building [multi-agent systems](https://langchain-ai.github.io/langgraph/concepts/multi_agent).\n", + "\n", + "The main question when adding subgraphs is how the parent graph and subgraph communicate, i.e. how they pass the [state](https://langchain-ai.github.io/langgraph/concepts/low_level/#state) between each other during the graph execution. There are two scenarios:\n", + "\n", + "* parent graph and subgraph **share schema keys**. In this case, you can [add a node with the compiled subgraph](#add-a-node-with-the-compiled-subgraph)\n", + "* parent graph and subgraph have **different schemas**. In this case, you have to [add a node function that invokes the subgraph](#add-a-node-function-that-invokes-the-subgraph): this is useful when the parent graph and the subgraph have different state schemas and you need to transform state before or after calling the subgraph\n", + "\n", + "Below we show to to add subgraphs for each scenario.\n", "\n", "![Screenshot 2024-07-11 at 1.01.28 PM.png](attachment:71516aef-9c00-4730-a676-a54e90cb6472.png)" ] @@ -72,28 +74,19 @@ ] }, { - "attachments": { - "9145adc1-ce9d-4a22-8183-e13796d4a388.png": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABekAAALVCAYAAABUR2peAAAMP2lDQ1BJQ0MgUHJvZmlsZQAASImVVwdYU8kWnluSkEBCCSAgJfQmCEgJICWEFkB6EWyEJEAoMQaCiB1dVHDtYgEbuiqi2AGxI3YWwd4XRRSUdbFgV96kgK77yvfO9829//3nzH/OnDu3DADqp7hicQ6qAUCuKF8SGxLAGJucwiB1AwTggAYIgMDl5YlZ0dERANrg+e/27ib0hnbNQab1z/7/app8QR4PACQa4jR+Hi8X4kMA4JU8sSQfAKKMN5+aL5Zh2IC2BCYI8UIZzlDgShlOU+B9cp/4WDbEzQCoqHG5kgwAaG2QZxTwMqAGrQ9iJxFfKAJAnQGxb27uZD7EqRDbQB8xxDJ9ZtoPOhl/00wb0uRyM4awYi5yUwkU5olzuNP+z3L8b8vNkQ7GsIJNLVMSGiubM6zb7ezJ4TKsBnGvKC0yCmItiD8I+XJ/iFFKpjQ0QeGPGvLy2LBmQBdiJz43MBxiQ4iDRTmREUo+LV0YzIEYrhC0UJjPiYdYD+KFgrygOKXPZsnkWGUstC5dwmYp+QtciTyuLNZDaXYCS6n/OlPAUepjtKLM+CSIKRBbFAgTIyGmQeyYlx0XrvQZXZTJjhz0kUhjZflbQBwrEIUEKPSxgnRJcKzSvzQ3b3C+2OZMISdSiQ/kZ8aHKuqDNfO48vzhXLA2gYiVMKgjyBsbMTgXviAwSDF3rFsgSohT6nwQ5wfEKsbiFHFOtNIfNxPkhMh4M4hd8wrilGPxxHy4IBX6eLo4PzpekSdelMUNi1bkgy8DEYANAgEDSGFLA5NBFhC29tb3witFTzDgAgnIAALgoGQGRyTJe0TwGAeKwJ8QCUDe0LgAea8AFED+6xCrODqAdHlvgXxENngKcS4IBznwWiofJRqKlgieQEb4j+hc2Hgw3xzYZP3/nh9kvzMsyEQoGelgRIb6oCcxiBhIDCUGE21xA9wX98Yj4NEfNheciXsOzuO7P+EpoZ3wmHCD0EG4M0lYLPkpyzGgA+oHK2uR9mMtcCuo6YYH4D5QHSrjurgBcMBdYRwW7gcju0GWrcxbVhXGT9p/m8EPd0PpR3Yio+RhZH+yzc8jaXY0tyEVWa1/rI8i17SherOHen6Oz/6h+nx4Dv/ZE1uIHcTOY6exi9gxrB4wsJNYA9aCHZfhodX1RL66BqPFyvPJhjrCf8QbvLOySuY51Tj1OH1R9OULCmXvaMCeLJ4mEWZk5jNY8IsgYHBEPMcRDBcnF1cAZN8XxevrTYz8u4Hotnzn5v0BgM/JgYGBo9+5sJMA7PeAj/+R75wNE346VAG4cIQnlRQoOFx2IMC3hDp80vSBMTAHNnA+LsAdeAN/EATCQBSIB8lgIsw+E65zCZgKZoC5oASUgWVgNVgPNoGtYCfYAw6AenAMnAbnwGXQBm6Ae3D1dIEXoA+8A58RBCEhVISO6CMmiCVij7ggTMQXCUIikFgkGUlFMhARIkVmIPOQMmQFsh7ZglQj+5EjyGnkItKO3EEeIT3Ia+QTiqFqqDZqhFqhI1EmykLD0Xh0ApqBTkGL0PnoEnQtWoXuRuvQ0+hl9Abagb5A+zGAqWK6mCnmgDExNhaFpWDpmASbhZVi5VgVVos1wvt8DevAerGPOBGn4wzcAa7gUDwB5+FT8Fn4Ynw9vhOvw5vxa/gjvA//RqASDAn2BC8ChzCWkEGYSighlBO2Ew4TzsJnqYvwjkgk6hKtiR7wWUwmZhGnExcTNxD3Ek8R24mdxH4SiaRPsif5kKJIXFI+qYS0jrSbdJJ0ldRF+qCiqmKi4qISrJKiIlIpVilX2aVyQuWqyjOVz2QNsiXZixxF5pOnkZeSt5EbyVfIXeTPFE2KNcWHEk/JosylrKXUUs5S7lPeqKqqmql6qsaoClXnqK5V3ad6QfWR6kc1LTU7NbbaeDWp2hK1HWqn1O6ovaFSqVZUf2oKNZ+6hFpNPUN9SP1Ao9McaRwanzabVkGro12lvVQnq1uqs9Qnqhepl6sfVL+i3qtB1rDSYGtwNWZpVGgc0bil0a9J13TWjNLM1VysuUvzoma3FknLSitIi681X2ur1hmtTjpGN6ez6Tz6PPo2+ll6lzZR21qbo52lXaa9R7tVu09HS8dVJ1GnUKdC57hOhy6ma6XL0c3RXap7QPem7qdhRsNYwwTDFg2rHXZ12Hu94Xr+egK9Ur29ejf0Pukz9IP0s/WX69frPzDADewMYgymGmw0OGvQO1x7uPdw3vDS4QeG3zVEDe0MYw2nG241bDHsNzI2CjESG60zOmPUa6xr7G+cZbzK+IRxjwndxNdEaLLK5KTJc4YOg8XIYaxlNDP6TA1NQ02lpltMW00/m1mbJZgVm+01e2BOMWeap5uvMm8y77MwsRhjMcOixuKuJdmSaZlpucbyvOV7K2urJKsFVvVW3dZ61hzrIusa6/s2VBs/myk2VTbXbYm2TNts2w22bXaonZtdpl2F3RV71N7dXmi/wb59BGGE5wjRiKoRtxzUHFgOBQ41Do8cdR0jHIsd6x1fjrQYmTJy+cjzI785uTnlOG1zuues5RzmXOzc6Pzaxc6F51Lhcn0UdVTwqNmjGka9crV3FbhudL3tRncb47bArcntq7uHu8S91r3Hw8Ij1aPS4xZTmxnNXMy84EnwDPCc7XnM86OXu1e+1wGvv7wdvLO9d3l3j7YeLRi9bXSnj5kP12eLT4cvwzfVd7Nvh5+pH9evyu+xv7k/33+7/zOWLSuLtZv1MsApQBJwOOA924s9k30qEAsMCSwNbA3SCkoIWh/0MNgsOCO4JrgvxC1kesipUEJoeOjy0FscIw6PU83pC/MImxnWHK4WHhe+PvxxhF2EJKJxDDombMzKMfcjLSNFkfVRIIoTtTLqQbR19JToozHEmOiYipinsc6xM2LPx9HjJsXtinsXHxC/NP5egk2CNKEpUT1xfGJ14vukwKQVSR1jR46dOfZyskGyMLkhhZSSmLI9pX9c0LjV47rGu40vGX9zgvWEwgkXJxpMzJl4fJL6JO6kg6mE1KTUXalfuFHcKm5/GietMq2Px+at4b3g+/NX8XsEPoIVgmfpPukr0rszfDJWZvRk+mWWZ/YK2cL1wldZoVmbst5nR2XvyB7IScrZm6uSm5p7RKQlyhY1TzaeXDi5XWwvLhF3TPGasnpKnyRcsj0PyZuQ15CvDX/kW6Q20l+kjwp8CyoKPkxNnHqwULNQVNgyzW7aomnPioKLfpuOT+dNb5phOmPujEczWTO3zEJmpc1qmm0+e/7srjkhc3bOpczNnvt7sVPxiuK385LmNc43mj9nfucvIb/UlNBKJCW3Fngv2LQQXyhc2Lpo1KJ1i76V8ksvlTmVlZd9WcxbfOlX51/X/jqwJH1J61L3pRuXEZeJlt1c7rd85wrNFUUrOleOWVm3irGqdNXb1ZNWXyx3Ld+0hrJGuqZjbcTahnUW65at+7I+c/2NioCKvZWGlYsq32/gb7i60X9j7SajTWWbPm0Wbr69JWRLXZVVVflW4taCrU+3JW47/xvzt+rtBtvLtn/dIdrRsTN2Z3O1R3X1LsNdS2vQGmlNz+7xu9v2BO5pqHWo3bJXd2/ZPrBPuu/5/tT9Nw+EH2g6yDxYe8jyUOVh+uHSOqRuWl1ffWZ9R0NyQ/uRsCNNjd6Nh486Ht1xzPRYxXGd40tPUE7MPzFwsuhk/ynxqd7TGac7myY13Tsz9sz15pjm1rPhZy+cCz535jzr/MkLPheOXfS6eOQS81L9ZffLdS1uLYd/d/v9cKt7a90VjysNbZ5tje2j209c9bt6+lrgtXPXOdcv34i80X4z4ebtW+Nvddzm3+6+k3Pn1d2Cu5/vzblPuF/6QONB+UPDh1V/2P6xt8O94/ijwEctj+Me3+vkdb54kvfkS9f8p9Sn5c9MnlV3u3Qf6wnuaXs+7nnXC/GLz70lf2r+WfnS5uWhv/z/aukb29f1SvJq4PXiN/pvdrx1fdvUH93/8F3uu8/vSz/of9j5kfnx/KekT88+T/1C+rL2q+3Xxm/h3+4P5A4MiLkSrvxXAIMNTU8H4PUOAKjJANDh/owyTrH/kxui2LPKEfhPWLFHlJs7ALXw/z2mF/7d3AJg3za4/YL66uMBiKYCEO8J0FGjhtrgXk2+r5QZEe4DNkd+TctNA//GFHvOH/L++Qxkqq7g5/O/AFFLfCfKufu9AAAAVmVYSWZNTQAqAAAACAABh2kABAAAAAEAAAAaAAAAAAADkoYABwAAABIAAABEoAIABAAAAAEAAAXpoAMABAAAAAEAAALVAAAAAEFTQ0lJAAAAU2NyZWVuc2hvdPVvNR0AAAHXaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2LjAuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjcyNTwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4xNTEzPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6VXNlckNvbW1lbnQ+U2NyZWVuc2hvdDwvZXhpZjpVc2VyQ29tbWVudD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CnAFrKkAAEAASURBVHgB7N0HfJXV/fjxb0IGMyFkksUIYYQ9RfZSUBzgrlpXq1bb/mr7r9raVlttXbXO1lG1rVvBhYoKDhDZeybskZCEhAQIgQAJJP/zPeGJNyGQQZJ7c/M5vpJ77zPOeD9XlO9znu/xKTFFKAgggAACCCCAAAIIIIAAAggggAACCCCAAAIIINDgAr4N3iINIoAAAggggAACCCCAAAIIIIAAAggggAACCCCAgBUgSM8XAQEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABNwkQpHcTPM0igAACCCCAAAIIIIAAAggggAACCCCAAAIIIECQnu8AAggggAACCCCAAAIIIIAAAggggAACCCCAAAJuEiBI7yZ4mkUAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAgCA93wEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBNwkQJDeTfA0iwACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAQXq+AwgggAACCCCAAAIIIIAAAggggAACCCCAAAIIuEmAIL2b4GkWAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAGC9HwHEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBwkwBBejfB0ywCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgTp+Q4ggAACCCCAAAIIIIAAAggggAACCCCAAAIIIOAmAYL0boKnWQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEECNLzHUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwE0CBOndBE+zCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggQpOc7gAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAmwQI0rsJnmYRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEECBIz3cAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAE3CRCkdxM8zSKAAAIIIIAAAggggAACCCCAAAIIIIAAAgggQJCe7wACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAm4SIEjvJniaRQABBBBAAAEEEEAAAQQQQAABBBBAAAEEEECAID3fAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEE3CRAkN5N8DSLAAIIIIAAAggggAACCCCAAAIIIIAAAggggABBer4DCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgi4SYAgvZvgaRYBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAYL0fAcQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEHCTAEF6N8HTLAIIIIAAAggggAACCCCAAAIIIIAAAggggAACBOn5DiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg4CYBgvRugqdZBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQI0vMdQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDATQIE6d0ET7MIIIAAAggggAACCCCAAAIIIIAAAggggAACCBCk5zuAAAIIIIAAAggggAACCCCAAAIIIIAAAggggICbBAjSuwmeZhFAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQIEjPdwABBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAATcJEKR3EzzNIoAAAggggAACCCCAAAIIIIAAAggggAACCCBAkJ7vAAIIIIAAAggggAACCCCAAAIIIIAAAggggAACbhIgSO8meJpFAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQIAgPd8BBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQTcJECQ3k3wNIsAAggggAACCCCAAAIIIIAAAggggAACCCCAAEF6vgMIIIAAAggggAACCCCAAAIIIIAAAggggAACCLhJgCC9m+BpFgEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABgvR8BxBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQcJMAQXo3wdMsAggggAACCCCAAAIIIIAAAggggAACCCCAAAIE6fkOIIAAAggggAACCCCAAAIIIIAAAggggAACCCDgJgGC9G6Cp1kEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBAjS8x1AAAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQMBNAgTp3QRPswgggAACCCCAAAIIIIAAAggggAACCCCAAAIIEKTnO4AAAggggAACCCCAAAIIIIAAAggggAACCCCAgJsECNK7CZ5mEUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBAgSM93AAEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABNwkQpHcTPM0igAACCCCAAAIIIIAAAggggAACCCCAAAIIIECQnu8AAggggAACCCCAAAIIIIAAAggggAACCCCAAAJuEiBI7yZ4mkUAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAgCA93wEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBNwkQJDeTfA0iwACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAQXq+AwgggAACCCCAAAIIIIAAAggggAACCCCAAAIIuEmAIL2b4GkWAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAGC9HwHEEAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBwkwBBejfB0ywCCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgTp+Q4ggAACCCCAAAIIIIAAAggggAACCCCAAAIIIOAmAYL0boKnWQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEECNLzHUAAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAwE0CBOndBE+zCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAggQpOc7gAACCCCAAAIIIIAAAggggAACCCCAAAIIIICAmwQI0rsJnmYRQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEECBIz3cAAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAE3CRCkdxM8zSKAAAIIIIAAAggggAACCCCAAAIIIIAAAgggQJCe7wACCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAm4SIEjvJniaRQABBBBAAAEEEEAAAQQQQAABBBBAAAEEEECAID3fAQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEE3CRAkN5N8DSLAAIIIIAAAggggAACCCCAAAIIIIAAAggggABBer4DCCCAAAIIIIAAAggggAACCCCAAAIIIIAAAgi4SYAgvZvgaRYBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAYL0fAcQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEEHCTAEF6N8HTLAIIIIAAAggggAACCCCAAAIIIIAAAggggAACBOn5DiCAAAIIIIAAAggggAACCCCAAAIIIIAAAggg4CYBPze1S7MIIIAAAggggAACCCCAwBkFSqRE1mSsk3nbv5ecQ3vlmv7XSFJk9zOew85TBb7YNEs2Z2+RX438xak72YIAAggggAACCCDgdgGC9G6/BHQAAQQQQAABBBBAAAEEXAWyDmWLBpa/SZklBccOle36okVbrwnS6xgDmgVIiBlTfZeFOxZIcvpauW3oT6SFf4tyzRWXlMjWnK3SNTyx3HY+IIAAAggggAACCDScAEH6hrOmJQQQQAABBBBAAAEEEDiDwPb9O+SDNR/K4m3flx3VtX1Pubrf1dImsLXEt40r296Y3+w/ckDufO828WvmL89d+YJEtAqr1+EUFxfb+nfs2yWtA1vK/iN5EmhuEHQN6yL/XPiSfLdptkzseZEJ4v+0XvtB5QgggAACCCCAAAKVC/iUmFL5LrYigAACCCCAAAIIIIAAAg0j8FnKF/JfEzDW4uvbTIZ3GS1X9L1cYoNiGqYDDdjK0eOF8ofP75Ode7dKp4iu8sTFj9d567sO7JLkrI2SfjBT5mycLUcLC05p4/9NuFcKTxTKv757VoqLT8hDFz3qNU8qnDJYNiCAAAIIIIAAAh4swEx6D744dA0BBBBAAAEEEEAAgaYikJWfVTbUkYnj5c5hZqa5b+P/60pGXqYsTF0kew5mSVRQpIxNGCWhLcPkH5c8IZry5vCxw3bcmt6nmwnYxwXFir+ZYX82paCoQH7zwa9OqSLAv7l0jexh2+kSliiDYvrbGyIjOw6XjXu3SMd28aecwwYEEEAAAQQQQACB+hdgJn39G9MCAggggAACCCCAAAIIVCGgs8sf+eYRWb97lT1SA8oX9b5ULu15ibQOaFV2ts74fnnpf0wO9a4m4D26bLu+yS88LH4+vqfkXdd9ugitj/lHy2YTkE4I7STNKrkJ4Hqcvk/J2nTK7HLN4z5321w7U72ZaS+6bYxc3GOy+PqU1m8bMb9S96fJ3TN+I8dPFDmbbFD8hqG32OOdjRpU//Hr1zof7TFBJle91hvftoNcP/DacmOqqv0jRUfkjul3yIni49I1Kkl2798lOeYmwd+nPCWdzbirKtmHc+S1Za/JWnMtdE2A1s2DZEzXCTLVXI+2zYPt6Qt3LZLI1pHGsbO8s3qafLL2QxnVZazcMez2qqpnPwIIIIAAAggggEAFgWZ/NqXCNj4igAACCCCAAAIIIIAAAg0q4GdS3IztMkb6xw2U3KN5km7yp6dkbpBP1s+QIimWPu172/5syN4kry54QZL3JMvUPlPL9fF3n/1evtnyrUzqPlHeXTPdzsQvLC6SO03A+qO1H8mlvS6Rb7bOkcdm/1W+2vKN+XypFJkA+guL/i3dzSz2JWnL5e6PfyObcrfJ6M4j5Ym5T8obS/4je0zQemiHIbYtvZnwe5OqZnby57IzZ5vs3LdDVqetkN0mrcy5HYeW3QjQgx+f83fJPrhH4sMS5Ir+V5uAeQ8TME+VxdsXiDTzk0U7F0l/M5tdF5CNCG4vgWZR15Ym936RCa4fOJwre83TBVv3bpaDJlXN4LhB1W5fZ+JP6T1FLutzmR1HtqlrS/ZGGWF8I1qH23pcf32c/KkcLTomUW0irctDXzwgaft2GptCiTGz6/OPHjTXYp18ufFLGRA7UEJahshdH/xStpljmplxvLboFXtDYLtZgHaIMWiIxXBd+897BBBAAAEEEECgsQs0/udHG/sVoP8IIIAAAggggAACCCBQJtA1PFH+NOE+s7jpAfl4/SfyuQnSf7jyPdmeu8NuTzuQao9NiOhedo6+yT92UFJN0LyNmYGuZea6GbI5e4uZLe9rZ4PrtiVpy+TFec/pWxsE1xn1bVu2NTnbZ0l0cJSZDT7D5mbXXPEa5HcWsJ1vAvv/N/Lnoqt5/e6zeyXN9KWfCdr/7NzbJNws+vqj16+RRdvmyezoPjLRzDjXojP+N2aut4vDPnTBg2VPA1zRe6qsNgFvDe5/ueFTGdF5hPSI6GafCtAnA3SW/Htr35f3l79l69HFZSf3uLC0TrOvuu3bE07+au4faN8dMzPsKysfrZou4UFR0i/6cZm7da7tu64LcN/EB6S/GZM+UTB323fyvLH7/af3yGvXv2Vn++80Qfnnv3vGVtkrtr99CmJDVop0atexsmbYhgACCCCAAAIIIHAaAd/TbGczAggggAACCCCAAAIIINBgApqixbXobOybB98g/5j6lA10r961VHILcuTo8WP2sISw8mlbXlz0st3eO6ZfWTVrUpfKyp2Lyz4/9e0T9n339r3s6/Z928v2vbXkNck3Nwa0HCjYJ9NNkFwD5FHBcTZovdfMptcULxqg7xs/xN4w0AD96oy1Ulh01J73upl1f9zMgteSe2S/fY1t16EsQK8bfE16nAHRfaV9ULTdn3ogzb7qL02P88sP/q8sQH+xmQn/v+tel/iQOHtMTdovq9S8OVFSbD/qDH2nrNi90t4I0c/NA1pIVl6G3RUUGGRff2LWBNAAvRZNEzQ2YYwMM/n0NXXPlpzN1kRvRGj5xZhfy73j7rbvN5kZ+xQEEEAAAQQQQACBmgkQpK+ZF0cjgAACCCCAAAIIIIBAHQusNTPLr3/9R/LQ1w/bQLVTvQaB/Xz8TXqVULspPW+PSckSZd9/u/ErE7AvtLO8XzHBcWfW+2GTGqZiCTMLtmrR+n426pfyk3N+Yj+n7d9tX51fmmpGA/NadCb536f8Q8Z2G2s/p5kA+nYz+13LJT0vsq+pZlb/37951L7X44+att9Y8bb9nF+Yb19PnAxk2w8uv0JbhNhP60yQX8v/lr0uv/7wl7InL006m5n1L17zitxkblK0MClwnFKT9p1z9DX4ZB75/GOlfdKbHQ/PelBmmCcVtLRt0c4+baBPLzQz49DS0mUdAP2sNxAWb59vXVr4NddNtkzpf6V9CqClf0tR500mDREFAQQQQAABBBBAoGYCpLupmRdHI4AAAggggAACCCCAQB0LND8Z9NXZ8vqji8bqjHMNejula/ueJi99L5sOJtTkVc89tFdufPM6G1TX43Rx00KTQ11nz7vOytfZ6BkHM+zCqSMTx8p5iePL0rm4zqTXdn47/l55/OtH7Gzxn4++yyzaGm9ztb9j9m3N3S5dzCKpWh764n4JNzcL9ubvsZ+vO+cmmzP+tx/9Wj4zC6juK8iV87udZ/dpPysrYWYWvhYNnG81aWM+Nec5pWVAS3llyatmkda2EhsSKzHBsZJk0vvUpP2fD79TmvuVprmJONnW6vTVco7Jbf/P+c/bprpHdrevYa3DZGuW6cvRfBllZstr6p7n5j4l6zRdjwnap5jAuz5BoOVPJnVP0ckbD21bhcp1/X9Y8LaLWcxXb5boAr5tKgT57cn8QgABBBBAAAEEEKhUgCB9pSxsRAABBBBAAAEEEEAAgYYS0Dz0D170iHye8rmsT18jBSbIq7Pem5tgdVRwtAzrNFym9ppiu+Pr4yNPX/aMDTQvMwuvahlrFoq90cw6X5e5Vp6Z86TsP3pANJAfYILUNwy8TpJNCpaINuFyy+BbSuswgecLzaKxK02O+jYBre3scE3l0tcsTnvt4B9LM3ODYIxZOFaL9i0utJMJpufJsA5XyFIT6P9+yxwboNeZ97ePuFNGdBxmj31o8sPyp5n3ycKt30noydn/2ofKSojJha/nx4d0MPnwo21/nYD++t2rTjlFZ/i/cPW/RW80VKf9uJB4uarP5baeruFd7KsG0J0nDjqZhXKHmrQ9WnqbtDa6PaJNhE2tM3XAVSan/yfybcqXdr8+JTDQLAh7db+rJMHcqNAc9RN6XCCjTQ59vR5OuTjpIlvPfpPqhyC9o8IrAggggAACCCBQtYBPiSlVH8YRCCCAAAIIIIAAAggggEDjEdBAsv5VR2fkn64Um1ztul9vCGgg+nRFF3PVWLTmZtdyyNxEKDK52TVvfsWis/l37ttlg/uaxse/WYBdGLbicfpZ0/X4mioDzDFaNN1Mrsl971ryzVMCOWabPh1wkVlAVoPi1W3ftZ6Pkz+V90ye/TBz8+K87pNksrmx0cz3hzlbB48dkiBz08C1HDyaZxR/SJfjuu9076uyPN15bEcAAQQQQAABBJqyAEH6pnz1GTsCCCCAAAIIIIAAAggggAACCCCAAAIIIICAWwVOP63Erd2icQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAEEvF+AIL33X2NGiAACCCCAAAIIIIAAAggggAACCCCAAAIIIOChAgTpPfTC0C0EEEAAAQQQQAABBBBAAAEEEEAAAQQQQAAB7xcgSO/915gRIoAAAggggAACCCCAAAIIIIAAAggggAACCHioAEF6D70wdAsBBBBAAAEEEEAAAQQQQAABBBBAAAEEEEDA+wUI0nv/NWaECCCAAAIIIIAAAggggAACCCCAAAIIIIAAAh4qQJDeQy8M3UIAAQQQQAABBBBAAAEEEEAAAQQQQAABBBDwfgGC9N5/jRkhAggggAACCCCAAAIIIIAAAggggAACCCCAgIcKEKT30AtDtxBAAAEEEEAAAQQQQAABBBBAAAEEEEAAAQS8X4AgvfdfY0aIAAIIIIAAAggggAACCCCAAAIIIIAAAggg4KECBOk99MLQLQQQQAABBBBAAAEEEEAAAQQQQAABBBBAAAHvFyBI7/3XmBEigAACCCCAAAIIIIAAAggggAACCCCAAAIIeKgAQXoPvTB0CwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQQMD7BQjSe/81ZoQIIIAAAggggAACCCCAAAIIIIAAAggggAACHirg56H9olsIIIAAAggggAACCCDgxQKHCg/L9tztp4ywc2hnaR3Q6pTtnrKhuKRYSqTY/GN+l5SImM/FPrqlREpKTsgJs83+o9v1GLNPzHbneHvuyWNOHmHOM8e4ofj6+IqPlM7bsu9NN3x8fOw2X/OqpXS7Hmfe+/qa4fpIgK+f+JmfZvqPj5+pwUf2HMqW7Pwse05lvzz9ulbWZ7YhgAACCCCAAAINJUCQvqGkaQcBBBBAAAEEEEAAgSYooMHbRbsWy7qM9ZJmgvL7Dud4hEKAf6BEhLQ3fdGQulNK32nQXGPUpcFzn5NHOK/OsQ33ml9wUA4fPthwDdZjS4EBLaR9SLRpwUfiQuKld3RvGdFhuAT6BtRjq1SNAAIIIIAAAgh4toCP+R/PH/6f1LP7Su8QQAABBBBAAAEEEECgEQhoYP7tlW9LignMO0H5TpFdpVNkJyk8cVR8A5pJu6CQSkeStnd3ue2FRcdk7/695bY5H3IP7JWi44XOR15rKBASHCaB5mZFdUpMRGyVh8WFn/kYvbbO9bQ3Hgryy+qMiYiXHlG95NLuF0l0Gw3iUxBAAAEEEEAAgaYjQJC+6VxrRooAAggggAACCCCAQL0KOMH5BVvmmuBvCxnYeYgkxiRKWGg7KSpp+GB66t60sxpvxRsGZ1VZJScHtwySoFZBleyp3abglsESXIf11a4X1T8rzzwdkGau0W4TvE83P4dPBu37dR4sNw+8SWKDYqpfGUcigAACCCCAAAKNWIAgfSO+eHQdAQQQQAABBBBAAAFPEXhp8csye8NMG5wf1n2E9OySJL7NSvOae0of6YdnC2zJ2CarNq+UPTnptqODEobKL4f90qPXKPBsUXqHAAIIIIAAAo1FgCB9Y7lS9BMBBBBAAAEEEEAAAQ8U0AVg//rV32TLnmSJj+gklwy/hOC8B16nxtSlLJPG6LvVc22wXnPYP3Th3yTBLChMQQABBBBAAAEEvFWAIL23XlnGhQACCCCAAAIIIIBAPQtsMwvBPvrVwzbvfM+OfWT8oHH13CLVNyWB5VtWycI139khXz/0Fpna85KmNHzGigACCCCAAAJNSIAgfRO62AwVAQQQQAABBBBAAIG6EtAZ9H+Y+QfZvW+njBt0nvTq2LOuqqYeBMoEdFb9h3On2wWCp/a7Uq4feF3ZPt4ggAACCCCAAALeIuDrLQNhHAgggAACCCCAAAIIINBwAk9+94wN0PdO7EeAvuHYm1xLkW3DZfLwi+24P1o9XRbuWtTkDBgwAggggAACCHi/AEF677/GjBABBBBAAAEEEEAAgToV+Gj9DFmTulRCgsNkbN8xdVo3lSFQUSA+PM4+raHb/zXvOdE0SxQEEEAAAQQQQMCbBAjSe9PVZCwIIIAAAggggAACCNSzgKa5mb7qXfH3C5Arx1xVz61RPQKlAppOqUN0ZzlaWCDPznsWFgQQQAABBBBAwKsECNJ71eVkMAgggAACCCCAAAII1K/AtLXT5FjhERnZb7Q09w+o38aoHQEXgTEnn9rQdRBIe+MCw1sEEEAAAQQQaPQCBOkb/SVkAAgggAACCCCAAAIINIyAzqL/KnmWtGrZhjz0DUNOKy4Cwa2CJCosxm75ZP2nLnt4iwACCCCAAAIING4BgvSN+/rRewQQQAABBBBAAAEEGkzgy+2zpbDoqHTvmNRgbdIQAq4CQ3sOtR+37EkmN70rDO8RQAABBBBAoFELEKRv1JePziOAAAIIIIAAAggg0HACS3cusrnoByYObLhGaQkBFwFdRFaf5NDy9eavXfbwFgEEEEAAAQQQaLwCBOkb77Wj5wgggAACCCCAAAIINJhAQXGBpGbtlOiIWHLRN5g6DVUm0DkmwW7eYXLTUxBAAAEEEEAAAW8QIEjvDVeRMSCAAAIIIIAAAgggUM8CyXs3StHxQkmILg2Q1nNzVI/AaQVizWx6LZryhoIAAggggAACCHiDAEF6b7iKjAEBBBBAAAEEEEAAgXoWWJK62LYQdzJAWs/NUT0CpxVw/Q7uOZR92uPYgQACCCCAAAIINBYBgvSN5UrRTwQQQAABBBBAAAEE3Ciwec9mCQkOk+BWQW7sBU0jIDbdUlRYjKXIys+CBAEEEEAAAQQQaPQCfo1+BAwAAQQQQAABBBBAAAEE6l+gpEQC/QPrvx0PbyGv4KAs27hckrevlQHdB0nBsSNy+GiB+Pr6SpBZ0DSoZbB97RDVUQKa8det+rqcgQF8F+vLlnoRQAABBBBAoOEF+L/GhjenRQQQQAABBBBAAAEEGp1A9oE90juxX6Prd112eO2OdbIsZakcLsgXPz9/2ZObJZGhUdK/S3/JP3JIFq1fIBsOrpPi4hO22S7x3aSfMTtedFziI0rzqNdlf5pyXWFtw2VXxvamTMDYEUAAAQQQQMCLBAjSe9HFZCgIIIAAAggggAACCNSXQGHRsfqq2uPrTc1Ok+WbV8juPTvL+nr8eJFk7E2zP6s2LrOpgLrGd5ekuO6Sf/SwWWS3SL5fM1fe/+Y9e46mCuqT0Fe6xnaVFswCL3Os7ZtA/+a1PZXzEEAAAQQQQAABjxMgSO9xl4QOIYAAAggggAACCCDgWQKHCg/bDsWFx3pWx+q5NwcLDpng/HJZv3V1WUuRJhd6YmyihAWHlm3TN5ryJjcvR75e9bV5zZUCM9vetew3+75b+Y0sWjdfEjt0k1G9R4t/s2auh/C+BgIRbcNqcDSHIoAAAggggAACni1AkN6zrw+9QwABBBBAAAEEEEDA7QLbcpteWpHN6dtkwbrvJf/QAWnTuq306dJPOpk88+3M+9OWuG5lu44UHpO9edmy3aRk2Wlm4B/M32/36RMJG7aulT05mXLRsIsluCUL8Zah8QYBBBBAAAEEEGiiAgTpm+iFZ9gIIIAAAggggAACCCBQucDqbWtk3qo5dqfm4R/SfYi0CmxZ+cGn2aopbeLD4+yP9B0tuQf3ybbMHZKWvUvSs1Il98BeeWPW63LF2KslyuRXpyCAAAIIIIAAAgg0XQGC9E332jNyBBBAAAEEEEAAAQQQqCCwIHmRrEheIq1bBcn4gROkQ0R8hSNq9zE0qJ3oz5BuA+XA4Tz5dOGnoilwpn39ltwy+VZp3aJV7SrmLAQQQAABBBBAAIFGL+Db6EfAABBAAAEEEEAAAQQQQKBeBfq2712v9XtK5Zt2b7EB+mbN/OS8wefXWYC+4vjatgqWH593vbRq2drumjZ3WsVD+IwAAggggAACCCDQhAQI0jehi81QEUAAAQQQQAABBBBAoHKBrSZ3/KzFM+3OsQPHS1xY/S+SO37geaI3BA6ZmfULNiysvGNsRQABBBBAAAEEEPB6AYL0Xn+JGSACCCCAAAIIIIAAAghUJbBuxzp7yLm9R0pSfI+qDq+T/R0jO0hS5162rhUpSyU5NaVO6m1KlWxvgosaN6Xry1gRQAABBBBoKgIE6ZvKlWacCCCAAAIIIIAAAgggUKmAzqJPM4u6xrXvJINNzviGLGP7jpGR/cfaJldvXd2QTXtFW4ePHfKKcTAIBBBAAAEEEGjaAgTpm/b1Z/QIIIAAAggggAACCDR5AWcWfZ/O7sm93z+hr/QwM+pz9mVJStrGJn89AEAAAQQQQAABBJqaAEH6pnbFGS8CCCCAAAIIIIAAAgiUCeQezC2bRZ/QvnPZ9oZ+07tTH9vkhh3JDd10o2wvuGVwo+w3nUYAAQQQQAABBCoTIEhfmQrbEEAAAQQQQAABBBBA4BSBtL27T9nW2Ddk5GbaIXQ2qW7cWaJCIqRTbKJkZKfKrqxd7uxKo2g7uFVQo+gnnUQAAQQQQAABBKojQJC+OkocgwACCCCAAAIIIIAAAl4pkJGbYccV0TbS7eMbkNjf9uHA4Ty394UOIIAAAggggAACCDScAEH6hrOmJQQQQAABBBBAAAEEEPAwgax9e8THx1cizUx2d5eY0GjbhYMFB93dFdpHAAEEEEAAAQQQaEABgvQNiE1TCCCAAAIIIIAAAggg4FkCBw7uk3Ztw8TXx8cjOhYY0EIOFuR7RF/oBAIIIIAAAggggEDDCBCkbxhnWkEAAQQQQAABBBBAAAEPFfD3C/CYnoUEh0pe/gGP6Y/ndsQzbqp4rg89QwABBBBAAIHGJODXmDpLXxFAAAEEEEAAAQQQQAABbxY4ZFLdHD1a4M1DrJOxNZNmtp6sQ3vrpD4qQQABBBBAAAEE3CnATHp36tM2AggggAACCCCAAAIIIOAicOjwQfHzoJn9Ll3zqLfNfEuD9HsPZXtUv+gMAggggAACCCBQGwGC9LVR4xwEEEAAAQQQQAABBBBAoI4F8kyAXktQ6+A6rtn7qvM9OZPe+0bGiBBAAAEEEECgKQoQpG+KV50xI4AAAggggAACCCCAgBVo1TJI9h/IcavGs+8/LRt2pUheQZ7tR3ArgvRVXZBmPvxVtioj9iOAAAIIIIBA4xHg/2waz7WipwgggAACCCCAAAIIIFDHAjHhMXKs6KhkHXBfbvOkzr3ku1VzZEfmDjs6ZtJXfZF9fUrT3VR9JEcggAACCCCAAAKeL0CQ3vOvET1EAAEEEEAAAQQQQACBehKICYu1NafnpNdTC1VXGxfRQY4fL5SNO1Pswd1iEqs+qYkfwUz6Jv4FYPgIIIAAAgh4mQBBei+7oAwHAQQQQAABBBBAAAEEqi8QHxlnD3ZnkL5bbKJ0jkuUY4VHJDQkQsKCw6o/gCZ6pE8THTfDRgABBBBAAAHvFCBI753XlVEhgAACCCCAAAIIIIBANQSCTU767ibdzI7dWyRzX1Y1zqifQ9qHxtiK9+flyv7Dpbnp66clb6mVML23XEnGgQACCCCAAAIiBOn5FiCAAAIIIIAAAggggECTFujTua8d//LNy93ioPnwl21YJBGh7aW4+IR8t3qOW/rRqBotIUjfqK4XnUUAAQQQQACBMwoQpD8jDzsRQAABBBBAAAEEEEDA2wWi2oZLj5Oz6XMO5jb4cBcnL5LAwBYyccgk6d99sKRm7pRV29Y0eD8aU4M+QpC+MV0v+ooAAggggAACZxYgSH9mH/YigAACCCCAAAIIIIBAExAY0WukhLWLlE8XfirHzCKuNS21TVGzYMNC2ZWxXUb3Gy0hrYJlZK/hEhIcKt+vmiN783Jq2o0mc7wPMfomc60ZKAIIIIAAAk1BgCB9U7jKjBEBBBBAAAEEEEAAAQTOKNAiIFDGDZgg+YcOyKyls6QmM+pXblstb3zx3xrnkv/WpLVZkbJURg8cL52jOpX17+Jhl9r389bOK9vGm/ICzKQv78EnBBBAAAEEEGjcAgTpG/f1o/cIIIAAAggggAACCCBQRwKa9uaysVfJvvx9Mv3b92SBSUNzrOjMs+pLSkRWbizNZd/SpKypTik4dkQ+X/qlrN+6RsaYAH3fTr3Lnda2VZCM7D9W0rNSZemmFeX28aFUgCA93wQEEEAAAQQQ8CYBgvTedDUZCwIIIIAAAggggAACCJyVQGxotJlRP16KTMqbFclL5N0578raHetPW+fu3HQpOHJIwttFSaBfwGmPc3as3Lpa3vnmHcnMyZApo6+QPhUC9M5x/RP6SkxkvCxe972k78t0NvOKAAIIIIAAAggg4IUCBOm98KIyJAQQQAABBBBAAAEEEKi9QHx4rFw66jIJD20veQf3ydwVX8sH338k63ZuOCWlze696baheBNQP1PZlZ0q782dJvNXz5XwkHC5auzVou2cqVw87BK7e8Ha+Wc6rEnu8yEpfZO87gwaAQQQQAABbxXw89aBMS4EEEAAAQQQQAABBBBAoLYCHSLiJSYsVhZuWCCrTcqZ9Kxd9kfr0wVmo8OipZPJI59pZtJriTPHu5aiEydkx54dkpGTLhm5GZKzL0vi2neSycMvkYT2nV0PPe37gGZ+MnHoZJm1eKZNvTM86dzTHtvUdvgI882a2jVnvAgggAACCHizAEF6b766jA0BBBBAoN4EcnNzJTQ0tN7qr2nFx48ft6f4+flJcXGx+PoSvKipIccjgAACFQX8zJ+lo3qPlNjwONm8e5Ns3pliD9GAu/6s3bxKxKf0rEUbFslil9nde/butjt8fZtJWEiETBgyUZLie1RsosrP3WITZUfHHjb1TmxYjOjNA4qyn4QHAwEEEEAAAQQQ8AIBgvRecBEZAgIIIIBA/QscOnRIXnzxRZkzZ440a9ZM1qxZI/Pnz5e4uLj6b/w0LfznP/+RDRs22D58+eWXkpaWZo/Uvt55551y7733nuZMNiOAAAII1ESgc1RH0Z9zzUz2dTvWSVpWmhQWHZOjZgHYY4VHTcTYRw4V5EurFi2lZfNW0rp5a0mITpAIE5yPNIvRBlQjV/2Z+jNp0ETZmb5DFqxbKNFjYsTf/HeIggACCCCAAAIIIOA9AgTpvedaMhIEEEAAgXoS2L17t1x77bWya9euci288847cs8995Tb1lAfjh07Jn/5y19Oaa5169bSoUOHU7azAQEEEEDg7AWCWwbJiJ7DRXqW1jXfpMJZmbJMzu0zQgYnDjz7Bs5Qw+ThF8tHc6eb9DsLZXSfkWc4smnsYh5907jOjBIBBBBAAIGmIsCz8E3lSjNOBBBAAIFaCRw9elR+/OMf2wD9b37zG9m2bZu88cYbtq5//etfcvjw4VrVe7YnBQYGygcffCCuNwp05rzOrJ83bx6z6M8WmPMRQACBagikn1w0Nib0zAvAVqOqKg+JM6lu+ncfLGs2r5CtmdurPN7bDyDdjbdfYcaHAAIIIIBA0xJgJn3Tut6MFgEEEECghgLTpk2T7du3y6233iq/+tWv7NmjRo2SXr16yfr162Xnzp3Ss+fJKZU1rPtsDx80aJCtIisry75qn2pbNm/eLIsXL5YlS5bIokWLJCEhQd58803RmwEVS1FRkezYsUPatWsnYWFhFXeXfVYbLR07drSv7vylRgsXLpSlS5fKggULpLCwUN5++23p3Ll6ize6s++VtZ2eni7ff/+9ZGRkSHR0tEyYMMFeC12PQPdVTMNUUlJi1yrQVE0UBBCoG4H8I4ckKzdTWgS2kmizkGxDlJG9hstOE6BftH6hxIRGS4uA5g3RLG0ggAACCCCAAAII1LMAQfp6BqZ6BBBAAIHGK6CBzaefftoO4Ne//nW5gVx99dU2QN+mTZty2/XD2rVr5bvvvhNNSRMeHi5Tp06VoKCgcsdt3brVBlg14K+B4/vuu8+mqXnhhRckICCg3LFVfcjJybGHxMfXfDFBDfLecsstkpJSuhiiVqQL4mrffVwWQHT6MGvWLHnggQckMzPTbho2bJhon9u2bescYuv685//bIP+ulHT7+gxNb2ZsWfPHvn0009l3759tv7zzjuvxkF1XVD3F7/4hXzxxRdl/dOUQDpGZ7Fd3aGGr776qlx66aXSvXv3smOdfXp8ZR76HXG2640ODfrr4r0Vi+tx+j45OfkUj4KCAjteTavUsmVLGTBggKhvxaL79Tulixc7Rcf0t7/9TZYtW2Zvrriul3DkyBEZO3as5Ofn2+/lmW6sOPXxigACVQuk52SImH+fYyIbdm2SyUMny5uzXpcFJlA/YcC4qjvqpUc4f/Z66fAYFgIIIIAAAgg0MQHS3TSxC85wEUAAAQSqL6ALsGogVNPdtGrVqtyJN9xwg00tUzEwrovLXnzxxfLEE0/Ic889J/fff79dxPXAgQPlztf9Wm9qaqrcfPPNsmXLFvn666/l448/LndcdT5oznwtsbE1T7fw3nvvlQXodUw6k37lypXyySefnHKzQJ8quO2222yA/sorr5S+ffva2em6gK1TNN3OFVdcYQP048aNk8mTJ9tUQepQk6Kz3s855xz561//Ks8//7w8++yzctNNN1nzmtSj43EC9PrkgS6w66QE6tq1a1lVOl5tZ/r06WXb9I0+NTB69GhxxqhPF+gNDb25oYFvHaMG+7VevYlw2WWX2fP1vCeffFLy8vJk7ty59smLP/zhD3afjunCCy+Uxx9/3H7WX1qfWukaB5pGSdv70Y9+ZK9D2UEn3/zjH/+w30udPa/fsUceecQ+raBPeujCxnpDoX379mWnqYHeVNGnIlxvppQdwBsEEKiVQGauCdKb0j70h3/falVRDU9q16adjOg/RpK3r5XktE01PNubDuevst50NRkLAggggAACTV2A/7Np6t8Axo8AAgggcFoBDbBqiYysXhoDDdBrwHTIkCE2aKsznnXBWU1L8tRTT5Vrx5ktf9VVV4neDNDjtKxatarccdX5oEH6083grur86667Tnr06GEPe/31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wASYABPQm0CJ+H80FVNTg4wn0Hud3JAJMAEmwASYABNgAkyACTABJsAEmIDBOenJsf7RRx+hd+/euPXWW2tcwdOnT2Pv3r2gCHvSrD948GCNNtUrzp49i65du1avNohje3t7LFy4EHPmzMGnn34KctRzYQJMgAkwASZg6AQUuRvOG2voV5LtZwJMgAkwASbABJgAE2ACTIAJMIH6CBick/6///6TUfTPPPMMjLQegSd9doqIP3PmTJU1BwUFSTkYqly+fDnc3Nzg7OwMikKn/uXl5fD396/Sx9AOHn74YZDcDSWQnTdvHszMzAxtCWwvE2ACTIAJMIEqBEpKy+Wxidb/66s04AMmwASYABNgAkyACTABJsAEmAATYALthIDBOekjIyMl+k6dOlW5BOSkJwe9nZ0dFixYgFGjRqFXr17iMXlTdO7cGba2tnjyySer9Kl+cPjwYezZswfZ2dno1q0bbr75ZlhZWVVvpvfx5cuXERsbix49esDGxqbWfiS7c/78eSlT4+HhUaVNaWkp9u/fj927d2PHjh04cOAAZsyYgffee69KO1rbY489JqVvdu3ahdGjR1c5zwdMgAkwASbABAyNQEmZKnGssamRoZnO9jIBJsAEmAATYAJMoFEELibmobNn7b6DRg3InZgAE2ACTMBgCBick56c8FSOHz8uk8cqpMmhTg72/v3713CskyOcoufrKr///nsN+ZzQ0FB89913GDBggOwaHx+PdevWITU1VY43YcIEUJvqJT8/X94Q+PLLLzWnfvnllxra96SpTzcO4uLiZLvrr78eP/30kxx78+bNmDZtmnxqQBnE3d0dhYWFymGV7fTp06WT/ueff2YnfRUyfKAQoCSMMSkF8HezgpmBaTzPXxOG5MzKz37fIAfMHh+sLE2z1bedpkMDdvKLyhCfXgA3ews42vDTKg1Ax02ZQKMIKIljzTiSvlH8uBMTYAJMgAkwASbQtgkkZRXh+KVMnLyUhRPidT42Wxo8oIszPp7Vr20bz9YxASbABJhAkxMwOCf94MGDJYTPPvsML730EpycnDRQhg8frtnX3qFEsRRRr6tQxDpFqFOZO3cuhg0bBorMX7ZsGQYOHIjo6GhcuHBBRucrY9DNglWrVmH9+vXo16/yf6BFRUW47bbbsGXLFhnVTw7+nTt3Soc73UhQpGjIgf/QQw/J4R54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AQIECBAgUD4BQfry9akWESBAgAABAgQIECBAgAABAgQIECBAgEBBBATpC9JRqkmAAAECBAgQIECAAAECBAgQIECAAAEC5RMQpC9fn2oRAQIECBAgQIAAAQIECBAgQIAAAQIECBREQJC+IB2lmgQIECBAgAABAgQIECBAgAABAgQIECBQPgFB+vL1qRYRIECAAAECBAgQIECAAAECBAgQIECAQEEEBOkL0lGqSYAAAQIECBAgQIAAAQIECBAgQIAAAQLlExCkL1+fahEBAgQIECBAgAABAgQIECBAgAABAgQIFERAkL4gHaWaBAgQIECAAAECBAgQIECAAAECBAgQIFA+AUH68vWpFhEgQIAAAQIECBAgQIAAAQIECBAgQIBAQQQE6QvSUapJgAABAgQIECBAgAABAgQIECBAgAABAuUTEKQvX59qEQECBAgQIECAAAECBAgQIECAAAECBAgURECQviAdpZoECBAgQIAAAQIECBAgQIAAAQIECBAgUD4BQfry9akWESBAgAABAgQIECBAgAABAgQIECBAgEBBBATpC9JRqkmAAAECBAgQIECAAAECBAgQIECAAAEC5RMQpC9fn2oRAQIECBAgQIAAAQIECBAgQIAAAQIECBREQJC+IB2lmgQIECBAgAABAgQIECBAgAABAgQIECBQPgFB+vL1qRYRIECAAAECBAgQIECAAAECBAgQIECAQEEEBOkL0lGqSYAAAQIECBAgQIAAAQIECBAgQIAAAQLlExCkL1+fahEBAgQIECBAgAABAgQIECBAgAABAgQIFERAkL4gHaWaBAgQIECAAAECBAgQIECAAAECBAgQIFA+AUH68vWpFhEgQIAAAQIECBAgQIAAAQIECBAgQIBAQQQE6QvSUapJgAABAgQIECBAgAABAgQIECBAgAABAuUTEKQvX59qEQECBAgQIECAAAECBAgQIECAAAECBAgURECQviAdpZoECBAgQIAAAQIECBAgQIAAAQIECBAgUD4BQfry9akWESBAgAABAgQIECBAgAABAgQIECBAgEBBBATpC9JRqkmAAAECBAgQIECAAAECBAgQIECAAAEC5RMQpC9fn2oRAQIECBAgQIAAAQIECBAgQIAAAQIECBREQJC+IB2lmgQIECBAgAABAgQIECBAgAABAgQIECBQPgFB+vL1qRYRIECAAAECBAgQIECAAAECBAgQIECAQEEEBOkL0lGqSYAAAQIECBAgQIAAAQIECBAgQIAAAQLlExCkL1+fahEBAgQIECBAgAABAgQIECBAgAABAgQIFERAkL4gHaWaBAgQIECAAAECBAgQIECAAAECBAgQIFA+AUH68vWpFhEgQIAAAQIECBAgQIAAAQIECBAgQIBAQQQE6QvSUapJgAABAgQIECBAgAABAgQIECBAgAABAuUTEKQvX59qEQECBAgQIECAAAECBAgQIECAAAECBAgURECQviAdpZoECBAgQIAAAQIECBAgQIAAAQIECBAgUD4BQfry9akWESBAgAABAgQIECBAgAABAgQIECBAgEBBBATpC9JRqkmAAAECBAgQIECAAAECBAgQIECAAAEC5RMQpC9fn2oRAQIECBAgQIAAAQIECBAgQIAAAQIECBREQJC+IB2lmgQIECBAgAABAgQIECBAgAABAgQIECBQPgFB+vL1qRYRIECAAAECBAgQIECAAAECBAgQIECAQEEEBOkL0lGqSYAAAQIECBAgQIAAAQIECBAgQIAAAQLlExCkL1+fahEBAgQIECBAgAABAgQIECBAgAABAgQIFERAkL4gHaWaBAgQIECAAAECBAgQIECAAAECBAgQIFA+AUH68vWpFhEgQIAAAQIECBAgQIAAAQIECBAgQIBAQQQE6QvSUapJgAABAgQIECBAgAABAgQIECBAgAABAuUTEKQvX59qEQECBAgQIECAAAECBAgQIECAAAECBAgURECQviAdpZoECBAgQIAAAQIECBAgQIAAAQIECBAgUD4BQfry9akWESBAgAABAgQIECBAgAABAgQIECBAgEBBBATpC9JRqkmAAAECBAgQIECAAAECBAgQIECAAAEC5RMQpC9fn2oRAQIECBAgQIAAAQIECBAgQIAAAQIECBREQJC+IB2lmgQIECBAgAABAgQIECBAgAABAgQIECBQPgFB+vL1qRYRIECAAAECBAgQIECAAAECBAgQIECAQEEEBOkL0lGqSYAAAQIECBAgQIAAAQIECBAgQIAAAQLlExCkL1+fahEBAgQIECBAgAABAgQIECBAgAABAgQIFERAkL4gHaWaBAgQIECAAAECBAgQIECAAAECBAgQIFA+AUH68vWpFhEgQIAAAQIECBAgQIAAAQIECBAgQIBAQQQE6QvSUapJgAABAgQIECBAgAABAgQIECBAgAABAuUTEKQvX59qEQECBAgQIECAAAECBAgQIECAAAECBAgURECQviAdpZoECBAgQIAAAQIECBAgQIAAAQIECBAgUD6B/w+jDwOxnklQVQAAAABJRU5ErkJggg==" - } - }, "cell_type": "markdown", "metadata": {}, "source": [ - "## Simple example\n", + "## Add a node with the compiled subgraph\n", "\n", - "Let's consider a toy example: a system that accepts logs and perform two separate sub-tasks. First, it will summarize them. Second, it will summarize any failure modes captured in the logs. These two operations will be performed by two different subgraphs.\n", + "A common case is for the parent graph and subgraph to communicate over a shared state key (channel). For example, in [multi-agent](https://langchain-ai.github.io/langgraph/concepts/multi_agent) systems, the agents often communicate over a shared [messages](https://langchain-ai.github.io/langgraph/concepts/low_level/#why-use-messages) key.\n", "\n", - "The most important thing to recognize is the information transfer between the graphs. `Entry Graph` is the parent, and each of the two subgraphs are defined as nodes in `Entry Graph`. Both subgraphs inherit state from the parent `Entry Graph`; I can access `docs` in each of the subgraphs simply by specifying it in the subgraph state (see diagram). Each subgraph can have its own private state. And any values that I want propagated back to the parent `Entry Graph` (for final reporting) simply need to be defined in my `Entry Graph` state (e.g., `summary report` and `failure report`).\n", + "If your subgraph shares state keys with the parent graph, you can follow these steps to add it to your graph:\n", "\n", - "![Screenshot 2024-07-12 at 10.35.41 AM.png](attachment:9145adc1-ce9d-4a22-8183-e13796d4a388.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Define subgraphs" + "1. Define the subgraph workflow (`subgraph_builder` in the example below) and compile it\n", + "2. Pass compiled subgraph to the `.add_node` method when defining the parent graph workflow\n", + "\n", + "Let's take a look at a simple example. " ] }, { @@ -102,115 +95,50 @@ "metadata": {}, "outputs": [], "source": [ - "from typing import Optional, Annotated\n", - "from typing_extensions import TypedDict\n", - "from langgraph.checkpoint.memory import MemorySaver\n", - "from langgraph.graph import StateGraph, START, END\n", + "from langgraph.graph import START, StateGraph\n", + "from typing import TypedDict\n", "\n", "\n", - "# The structure of the logs\n", - "class Logs(TypedDict):\n", - " id: str\n", - " question: str\n", - " answer: str\n", - " grade: Optional[int]\n", - " feedback: Optional[str]\n", + "# Define subgraph\n", + "class SubgraphState(TypedDict):\n", + " foo: str # note that this key is shared with the parent graph state\n", + " bar: str\n", "\n", "\n", - "# Define custom reducer (see more on this in the \"Custom reducer\" section below)\n", - "def add_logs(left: list[Logs], right: list[Logs]) -> list[Logs]:\n", - " if not left:\n", - " left = []\n", - "\n", - " if not right:\n", - " right = []\n", - "\n", - " logs = left.copy()\n", - " left_id_to_idx = {log[\"id\"]: idx for idx, log in enumerate(logs)}\n", - " # update if the new logs are already in the state, otherwise append\n", - " for log in right:\n", - " idx = left_id_to_idx.get(log[\"id\"])\n", - " if idx is not None:\n", - " logs[idx] = log\n", - " else:\n", - " logs.append(log)\n", - " return logs\n", + "def subgraph_node_1(state: SubgraphState):\n", + " return {\"bar\": \"bar\"}\n", "\n", "\n", - "# Failure Analysis Subgraph\n", - "class FailureAnalysisState(TypedDict):\n", - " # keys shared with the parent graph (EntryGraphState)\n", - " logs: Annotated[list[Logs], add_logs]\n", - " failure_report: str\n", - " # subgraph key\n", - " failures: list[Logs]\n", + "def subgraph_node_2(state: SubgraphState):\n", + " # note that this node is using a state key ('bar') that is only available in the subgraph\n", + " # and is sending update on the shared state key ('foo')\n", + " return {\"foo\": state[\"foo\"] + state[\"bar\"]}\n", "\n", "\n", - "def get_failures(state: FailureAnalysisState):\n", - " failures = [log for log in state[\"logs\"] if log[\"grade\"] == 0]\n", - " return {\"failures\": failures}\n", + "subgraph_builder = StateGraph(SubgraphState)\n", + "subgraph_builder.add_node(subgraph_node_1)\n", + "subgraph_builder.add_node(subgraph_node_2)\n", + "subgraph_builder.add_edge(START, \"subgraph_node_1\")\n", + "subgraph_builder.add_edge(\"subgraph_node_1\", \"subgraph_node_2\")\n", + "subgraph = subgraph_builder.compile()\n", "\n", "\n", - "def generate_summary(state: FailureAnalysisState):\n", - " failures = state[\"failures\"]\n", - " # NOTE: you can implement custom summarization logic here\n", - " failure_ids = [log[\"id\"] for log in failures]\n", - " fa_summary = f\"Poor quality of retrieval for document IDs: {', '.join(failure_ids)}\"\n", - " return {\"failure_report\": fa_summary}\n", + "# Define parent graph\n", + "class ParentState(TypedDict):\n", + " foo: str\n", "\n", "\n", - "fa_builder = StateGraph(FailureAnalysisState)\n", - "fa_builder.add_node(\"get_failures\", get_failures)\n", - "fa_builder.add_node(\"generate_summary\", generate_summary)\n", - "fa_builder.add_edge(START, \"get_failures\")\n", - "fa_builder.add_edge(\"get_failures\", \"generate_summary\")\n", - "fa_builder.add_edge(\"generate_summary\", END)\n", + "def node_1(state: ParentState):\n", + " return {\"foo\": \"hi! \" + state[\"foo\"]}\n", "\n", "\n", - "# Summarization subgraph\n", - "class QuestionSummarizationState(TypedDict):\n", - " # keys that are shared with the parent graph (EntryGraphState)\n", - " summary_report: str\n", - " logs: Annotated[list[Logs], add_logs]\n", - " # subgraph keys\n", - " summary: str\n", - "\n", - "\n", - "def generate_summary(state: QuestionSummarizationState):\n", - " docs = state[\"logs\"]\n", - " # NOTE: you can implement custom summarization logic here\n", - " summary = \"Questions focused on usage of ChatOllama and Chroma vector store.\"\n", - " return {\"summary\": summary}\n", - "\n", - "\n", - "def send_to_slack(state: QuestionSummarizationState):\n", - " summary = state[\"summary\"]\n", - " # NOTE: you can implement custom logic here, for example sending the summary generated in the previous step to Slack\n", - " return {\"summary_report\": summary}\n", - "\n", - "\n", - "qs_builder = StateGraph(QuestionSummarizationState)\n", - "qs_builder.add_node(\"generate_summary\", generate_summary)\n", - "qs_builder.add_node(\"send_to_slack\", send_to_slack)\n", - "qs_builder.add_edge(START, \"generate_summary\")\n", - "qs_builder.add_edge(\"generate_summary\", \"send_to_slack\")\n", - "qs_builder.add_edge(\"send_to_slack\", END)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that each subgraph has its own state, `QuestionSummarizationState` and `FailureAnalysisState`.\n", - " \n", - "After defining each subgraph, we put everything together." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Define parent graph" + "builder = StateGraph(ParentState)\n", + "builder.add_node(\"node_1\", node_1)\n", + "# note that we're adding the compiled subgraph as a node to the parent graph\n", + "builder.add_node(\"node_2\", subgraph)\n", + "builder.add_edge(START, \"node_1\")\n", + "builder.add_edge(\"node_1\", \"node_2\")\n", + "graph = builder.compile()" ] }, { @@ -219,69 +147,24 @@ "metadata": {}, "outputs": [ { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" + "name": "stdout", + "output_type": "stream", + "text": [ + "{'node_1': {'foo': 'hi! foo'}}\n", + "{'node_2': {'foo': 'hi! foobar'}}\n" + ] } ], "source": [ - "# Dummy logs\n", - "dummy_logs = [\n", - " Logs(\n", - " id=\"1\",\n", - " question=\"How can I import ChatOllama?\",\n", - " grade=1,\n", - " answer=\"To import ChatOllama, use: 'from langchain_community.chat_models import ChatOllama.'\",\n", - " ),\n", - " Logs(\n", - " id=\"2\",\n", - " question=\"How can I use Chroma vector store?\",\n", - " answer=\"To use Chroma, define: rag_chain = create_retrieval_chain(retriever, question_answer_chain).\",\n", - " grade=0,\n", - " feedback=\"The retrieved documents discuss vector stores in general, but not Chroma specifically\",\n", - " ),\n", - " Logs(\n", - " id=\"3\",\n", - " question=\"How do I create react agent in langgraph?\",\n", - " answer=\"from langgraph.prebuilt import create_react_agent\",\n", - " ),\n", - "]\n", - "\n", - "\n", - "# Entry Graph\n", - "class EntryGraphState(TypedDict):\n", - " raw_logs: Annotated[list[Logs], add_logs]\n", - " logs: Annotated[list[Logs], add_logs] # This will be used in subgraphs\n", - " failure_report: str # This will be generated in the FA subgraph\n", - " summary_report: str # This will be generated in the QS subgraph\n", - "\n", - "\n", - "def select_logs(state):\n", - " return {\"logs\": [log for log in state[\"raw_logs\"] if \"grade\" in log]}\n", - "\n", - "\n", - "entry_builder = StateGraph(EntryGraphState)\n", - "entry_builder.add_node(\"select_logs\", select_logs)\n", - "entry_builder.add_node(\"question_summarization\", qs_builder.compile())\n", - "entry_builder.add_node(\"failure_analysis\", fa_builder.compile())\n", - "\n", - "entry_builder.add_edge(START, \"select_logs\")\n", - "entry_builder.add_edge(\"select_logs\", \"failure_analysis\")\n", - "entry_builder.add_edge(\"select_logs\", \"question_summarization\")\n", - "entry_builder.add_edge(\"failure_analysis\", END)\n", - "entry_builder.add_edge(\"question_summarization\", END)\n", - "\n", - "graph = entry_builder.compile()\n", - "\n", - "from IPython.display import Image, display\n", - "\n", - "# Setting xray to 1 will show the internal structure of the nested graph\n", - "display(Image(graph.get_graph(xray=1).draw_mermaid_png()))" + "for chunk in graph.stream({\"foo\": \"foo\"}):\n", + " print(chunk)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can see that the final output from the parent graph includes the results of subgraph invocation (i.e. string `\"bar\"`). If you would like to see outputs from the subgraph, you can specify `subgraphs=True` when streaming. See more on streaming from subgraphs in this [how-to guide](https://langchain-ai.github.io/langgraph/how-tos/streaming-subgraphs/#stream-subgraph)." ] }, { @@ -290,51 +173,40 @@ "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "{'raw_logs': [{'id': '1',\n", - " 'question': 'How can I import ChatOllama?',\n", - " 'grade': 1,\n", - " 'answer': \"To import ChatOllama, use: 'from langchain_community.chat_models import ChatOllama.'\"},\n", - " {'id': '2',\n", - " 'question': 'How can I use Chroma vector store?',\n", - " 'answer': 'To use Chroma, define: rag_chain = create_retrieval_chain(retriever, question_answer_chain).',\n", - " 'grade': 0,\n", - " 'feedback': 'The retrieved documents discuss vector stores in general, but not Chroma specifically'},\n", - " {'id': '3',\n", - " 'question': 'How do I create react agent in langgraph?',\n", - " 'answer': 'from langgraph.prebuilt import create_react_agent'}],\n", - " 'logs': [{'id': '1',\n", - " 'question': 'How can I import ChatOllama?',\n", - " 'grade': 1,\n", - " 'answer': \"To import ChatOllama, use: 'from langchain_community.chat_models import ChatOllama.'\"},\n", - " {'id': '2',\n", - " 'question': 'How can I use Chroma vector store?',\n", - " 'answer': 'To use Chroma, define: rag_chain = create_retrieval_chain(retriever, question_answer_chain).',\n", - " 'grade': 0,\n", - " 'feedback': 'The retrieved documents discuss vector stores in general, but not Chroma specifically'}],\n", - " 'failure_report': 'Poor quality of retrieval for document IDs: 2',\n", - " 'summary_report': 'Questions focused on usage of ChatOllama and Chroma vector store.'}" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "((), {'node_1': {'foo': 'hi! foo'}})\n", + "(('node_2:e58e5673-a661-ebb0-70d4-e298a7fc28b7',), {'subgraph_node_1': {'bar': 'bar'}})\n", + "(('node_2:e58e5673-a661-ebb0-70d4-e298a7fc28b7',), {'subgraph_node_2': {'foo': 'hi! foobar'}})\n", + "((), {'node_2': {'foo': 'hi! foobar'}})\n" + ] } ], "source": [ - "graph.invoke({\"raw_logs\": dummy_logs}, debug=False)" + "for chunk in graph.stream({\"foo\": \"foo\"}, subgraphs=True):\n", + " print(chunk)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Custom reducer functions to manage state\n", + "## Add a node function that invokes the subgraph\n", "\n", - "You might have noticed that we defined a custom [reducer]([reducer](https://langchain-ai.github.io/langgraph/concepts/low_level/#reducers) function (`add_logs`) or the `logs` key in `EntryGraphState`. It is necessary to provide a reducer when using shared state keys across multiple subgraphs.\n", + "For more complex systems you might want to define subgraphs that have a completely different schema from the parent graph (no shared keys). For example, in a multi-agent RAG system, a search agent might only need to keep track of queries and retrieved documents.\n", "\n", - "Let's take a look at implementing a custom reducer. We will create two graphs: a parent graph with a few nodes and a child graph that is added as a node in the parent. We'll also define a custom reducer function (`reduce_list`) for our state. This is functionally equivalent to simply using `operator.add`." + "If that's the case for your application, you need to define a node **function that invokes the subgraph**. This function needs to transform the input (parent) state to the subgraph state before invoking the subgraph, and transform the results back to the parent state before returning the state update from the node.\n", + "\n", + "Below we show how to modify our original example to call a subgraph from inside the node." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "!!! warning\n", + " You **cannot** invoke more than one subgraph inside the same node." ] }, { @@ -343,337 +215,73 @@ "metadata": {}, "outputs": [], "source": [ - "from typing import Annotated\n", - "\n", - "from typing_extensions import TypedDict\n", + "# Define subgraph\n", + "class SubgraphState(TypedDict):\n", + " # note that none of these keys are shared with the parent graph state\n", + " bar: str\n", + " baz: str\n", "\n", "\n", - "# define a simple reducer\n", - "def reduce_list(left: list, right: list) -> list:\n", - " if not left:\n", - " left = []\n", - " if not right:\n", - " right = []\n", - " return left + right\n", + "def subgraph_node_1(state: SubgraphState):\n", + " return {\"baz\": \"baz\"}\n", "\n", "\n", - "# define parent and child state\n", - "class ChildState(TypedDict):\n", - " name: str\n", - " path: Annotated[list[str], reduce_list]\n", + "def subgraph_node_2(state: SubgraphState):\n", + " return {\"bar\": state[\"bar\"] + state[\"baz\"]}\n", "\n", "\n", + "subgraph_builder = StateGraph(SubgraphState)\n", + "subgraph_builder.add_node(subgraph_node_1)\n", + "subgraph_builder.add_node(subgraph_node_2)\n", + "subgraph_builder.add_edge(START, \"subgraph_node_1\")\n", + "subgraph_builder.add_edge(\"subgraph_node_1\", \"subgraph_node_2\")\n", + "subgraph = subgraph_builder.compile()\n", + "\n", + "\n", + "# Define parent graph\n", "class ParentState(TypedDict):\n", - " name: str\n", - " path: Annotated[list[str], reduce_list]\n", + " foo: str\n", "\n", "\n", - "# define a helper to build the graph\n", - "def make_graph(parent_schema, child_schema):\n", - " child_builder = StateGraph(child_schema)\n", - "\n", - " child_builder.add_node(\"child_start\", lambda state: {\"path\": [\"child_start\"]})\n", - " child_builder.add_edge(START, \"child_start\")\n", - " child_builder.add_node(\"child_middle\", lambda state: {\"path\": [\"child_middle\"]})\n", - " child_builder.add_node(\"child_end\", lambda state: {\"path\": [\"child_end\"]})\n", - " child_builder.add_edge(\"child_start\", \"child_middle\")\n", - " child_builder.add_edge(\"child_middle\", \"child_end\")\n", - " child_builder.add_edge(\"child_end\", END)\n", - "\n", - " builder = StateGraph(parent_schema)\n", - "\n", - " builder.add_node(\"grandparent\", lambda state: {\"path\": [\"grandparent\"]})\n", - " builder.add_edge(START, \"grandparent\")\n", - " builder.add_node(\"parent\", lambda state: {\"path\": [\"parent\"]})\n", - " builder.add_node(\"child\", child_builder.compile())\n", - " builder.add_node(\"sibling\", lambda state: {\"path\": [\"sibling\"]})\n", - " builder.add_node(\"fin\", lambda state: {\"path\": [\"fin\"]})\n", - "\n", - " # Add connections\n", - " builder.add_edge(\"grandparent\", \"parent\")\n", - " builder.add_edge(\"parent\", \"child\")\n", - " builder.add_edge(\"parent\", \"sibling\")\n", - " builder.add_edge(\"child\", \"fin\")\n", - " builder.add_edge(\"sibling\", \"fin\")\n", - " builder.add_edge(\"fin\", END)\n", - " graph = builder.compile()\n", - " return graph\n", + "def node_1(state: ParentState):\n", + " return {\"foo\": \"hi! \" + state[\"foo\"]}\n", "\n", "\n", - "graph = make_graph(ParentState, ChildState)" + "def node_2(state: ParentState):\n", + " # transform the state to the subgraph state\n", + " response = subgraph.invoke({\"bar\": state[\"foo\"]})\n", + " # transform response back to the parent state\n", + " return {\"foo\": response[\"bar\"]}\n", + "\n", + "\n", + "builder = StateGraph(ParentState)\n", + "builder.add_node(\"node_1\", node_1)\n", + "# note that instead of using the compiled subgraph we are using `node_2` function that is calling the subgraph\n", + "builder.add_node(\"node_2\", node_2)\n", + "builder.add_edge(START, \"node_1\")\n", + "builder.add_edge(\"node_1\", \"node_2\")\n", + "graph = builder.compile()" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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qGMrKWrlllkcWO5vd3f00jd1zSN06HU8V0Wz291ptVHRPrKm4Op4WxGrrHB0026NN95AALj0kgDj4FrO3Om8WX77Eq/wl6blL6w81QWW8VFS7gxk9vmpY9fjdJK1oA+M8T8QJ4JwNcarGbLa7Le8eg/4k/8APkUrWpxSyOx3HqG3vkE0sLP0kjRoHPJLnED4t4lbZefj1RXi11RyTM+aTyiIi4EEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBV35WnfTsG/vAofMkViFXflad9Owb+8Ch8yRBYhERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBV35WnfTsG/vAofMkViFXflad9Owb+8Ch8yRBYhERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEXh80cZ0e9rT8ROi89lQ/7aP+IK2kfVflrtx/pAKzM8qw+C4bNn2GvwrJo7rUUkl6510skG+x0B/0du4dSe67rTToK/UXsqH/AG0f8QX5nf0hXJprZ9tePZJilKKlubVMdvmij9zHcODQXEcGiRmjtfjjkcUtIuJyUuUhcuUzi94yCfDDilrpKltJSym5dl9lv3S6XQc1HuhmsfHjqXEcN0ruShOx/Z7aNj2zTH8PtcsRprXStifKNGmeU91LKR8b3lzv3qY9lQ/7aP8AiCWkfVF8uyof9tH/ABBfVLAiIoCIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAtBnV1qLNjNRPSSCGpkkhpo5SAebdLMyIPAIIJG/qNQRqOI0W/UT2od6Q/tG3emwLsZPEVY1ETyXjzWOWGpGA445v6eyUNbKTq6esgbPLI7hq5z3guc46DUkklPa+xb5tWfyCL1VsL/AH2hxexXK83Ofsa226mkq6qfcc/m4o2l73brQSdGgnQAk+AKJ4ftxwnO7zBarNeXS3CopzVU0NTR1FL2TCNCXwmWNolaAQSWE6L0OHxI/nO8vO1vPa+xb5tWfyCL1U9r7Fvm1Z/IIvVWhxXbtg2a5L2v2i+c/dnNkfFBNSTwCcM92YnyMa2Xd8O4XaDilg264PlFbV01svRqTR9kdlzmjqGU9KYHObKJZnRiOMt3SdHOBI0cNQQTOHxOnO8vO1vva+xb5tWf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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from IPython.display import Image, display\n", - "\n", - "# Setting xray to 1 will show the internal structure of the nested graph\n", - "display(Image(graph.get_graph(xray=1).draw_mermaid_png()))" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[36;1m\u001b[1;3m[-2:checkpoint]\u001b[0m \u001b[1mState at the end of step -2:\n", - "\u001b[0m{'path': []}\n", - "\u001b[36;1m\u001b[1;3m[0:tasks]\u001b[0m \u001b[1mStarting step 0 with 1 task:\n", - "\u001b[0m- \u001b[32;1m\u001b[1;3m__start__\u001b[0m -> {'name': 'test'}\n", - "\u001b[36;1m\u001b[1;3m[0:writes]\u001b[0m \u001b[1mFinished step 0 with writes to 1 channel:\n", - "\u001b[0m- \u001b[33;1m\u001b[1;3mname\u001b[0m -> 'test'\n", - "\u001b[36;1m\u001b[1;3m[-2:checkpoint]\u001b[0m \u001b[1mState at the end of step -2:\n", - "\u001b[0m{'name': 'test', 'path': []}\n", - "\u001b[36;1m\u001b[1;3m[1:tasks]\u001b[0m \u001b[1mStarting step 1 with 1 task:\n", - "\u001b[0m- \u001b[32;1m\u001b[1;3mgrandparent\u001b[0m -> {'name': 'test', 'path': []}\n", - "\u001b[36;1m\u001b[1;3m[1:writes]\u001b[0m \u001b[1mFinished step 1 with writes to 1 channel:\n", - "\u001b[0m- \u001b[33;1m\u001b[1;3mpath\u001b[0m -> ['grandparent']\n", - "\u001b[36;1m\u001b[1;3m[-2:checkpoint]\u001b[0m \u001b[1mState at the end of step -2:\n", - "\u001b[0m{'name': 'test', 'path': ['grandparent']}\n", - "\u001b[36;1m\u001b[1;3m[2:tasks]\u001b[0m \u001b[1mStarting step 2 with 1 task:\n", - "\u001b[0m- \u001b[32;1m\u001b[1;3mparent\u001b[0m -> {'name': 'test', 'path': ['grandparent']}\n", - "\u001b[36;1m\u001b[1;3m[2:writes]\u001b[0m \u001b[1mFinished step 2 with writes to 1 channel:\n", - "\u001b[0m- \u001b[33;1m\u001b[1;3mpath\u001b[0m -> ['parent']\n", - "\u001b[36;1m\u001b[1;3m[-2:checkpoint]\u001b[0m \u001b[1mState at the end of step -2:\n", - "\u001b[0m{'name': 'test', 'path': ['grandparent', 'parent']}\n", - "\u001b[36;1m\u001b[1;3m[3:tasks]\u001b[0m \u001b[1mStarting step 3 with 2 tasks:\n", - "\u001b[0m- \u001b[32;1m\u001b[1;3mchild\u001b[0m -> {'name': 'test', 'path': ['grandparent', 'parent']}\n", - "- \u001b[32;1m\u001b[1;3msibling\u001b[0m -> {'name': 'test', 'path': ['grandparent', 'parent']}\n", - "\u001b[36;1m\u001b[1;3m[3:writes]\u001b[0m \u001b[1mFinished step 3 with writes to 2 channels:\n", - "\u001b[0m- \u001b[33;1m\u001b[1;3mname\u001b[0m -> 'test'\n", - "- \u001b[33;1m\u001b[1;3mpath\u001b[0m -> ['grandparent', 'parent', 'child_start', 'child_middle', 'child_end'], ['sibling']\n", - "\u001b[36;1m\u001b[1;3m[-2:checkpoint]\u001b[0m \u001b[1mState at the end of step -2:\n", - "\u001b[0m{'name': 'test',\n", - " 'path': ['grandparent',\n", - " 'parent',\n", - " 'grandparent',\n", - " 'parent',\n", - " 'child_start',\n", - " 'child_middle',\n", - " 'child_end',\n", - " 'sibling']}\n", - "\u001b[36;1m\u001b[1;3m[4:tasks]\u001b[0m \u001b[1mStarting step 4 with 1 task:\n", - "\u001b[0m- \u001b[32;1m\u001b[1;3mfin\u001b[0m -> {'name': 'test',\n", - " 'path': ['grandparent',\n", - " 'parent',\n", - " 'grandparent',\n", - " 'parent',\n", - " 'child_start',\n", - " 'child_middle',\n", - " 'child_end',\n", - " 'sibling']}\n", - "\u001b[36;1m\u001b[1;3m[4:writes]\u001b[0m \u001b[1mFinished step 4 with writes to 1 channel:\n", - "\u001b[0m- \u001b[33;1m\u001b[1;3mpath\u001b[0m -> ['fin']\n" + "((), {'node_1': {'foo': 'hi! foo'}})\n", + "(('node_2:c47d7ea3-7798-87c4-adf4-2543a91d6891',), {'subgraph_node_1': {'baz': 'baz'}})\n", + "(('node_2:c47d7ea3-7798-87c4-adf4-2543a91d6891',), {'subgraph_node_2': {'bar': 'hi! foobaz'}})\n", + "((), {'node_2': {'foo': 'hi! foobaz'}})\n" ] - }, - { - "data": { - "text/plain": [ - "{'name': 'test',\n", - " 'path': ['grandparent',\n", - " 'parent',\n", - " 'grandparent',\n", - " 'parent',\n", - " 'child_start',\n", - " 'child_middle',\n", - " 'child_end',\n", - " 'sibling',\n", - " 'fin']}" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ - "graph.invoke({\"name\": \"test\"}, debug=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice here that the `[\"grandparent\", \"parent\"]` sequence is duplicated! \n", - "\n", - "This is because our child state has received the full parent state and returns the full parent state once it terminates. \n", - "\n", - "To avoid duplication or conflicts in state, you typically would do one or more of the following:\n", - "\n", - "1. Handle duplicates in your `reducer` function.\n", - "2. Call the child graph from within a python function. In that function, handle the state as needed. \n", - "3. Update the child graph keys to avoid conflicts. You would still need to ensure the output can be interpreted by the parent, however.\n", - "\n", - "Let's re-implement the graph using technique (1) and add unique IDs for every value in the list. This is what is done in [`MessageGraph`](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.MessageGraph)." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "import uuid\n", - "\n", - "\n", - "def reduce_list(left: list | None, right: list | None) -> list:\n", - " \"\"\"Append the right-hand list, replacing any elements with the same id in the left-hand list.\"\"\"\n", - " if not left:\n", - " left = []\n", - " if not right:\n", - " right = []\n", - " left_, right_ = [], []\n", - " for orig, new in [(left, left_), (right, right_)]:\n", - " for val in orig:\n", - " if not isinstance(val, dict):\n", - " val = {\"val\": val}\n", - " if \"id\" not in val:\n", - " val[\"id\"] = str(uuid.uuid4())\n", - " new.append(val)\n", - " # Merge the two lists\n", - " left_idx_by_id = {val[\"id\"]: i for i, val in enumerate(left_)}\n", - " merged = left_.copy()\n", - " for val in right_:\n", - " if (existing_idx := left_idx_by_id.get(val[\"id\"])) is not None:\n", - " merged[existing_idx] = val\n", - " else:\n", - " merged.append(val)\n", - " return merged\n", - "\n", - "\n", - "class ChildState(TypedDict):\n", - " name: str\n", - " # note the updated reducer here\n", - " path: Annotated[list[str], reduce_list]\n", - "\n", - "\n", - "class ParentState(TypedDict):\n", - " name: str\n", - " # note the updated reducer here\n", - " path: Annotated[list[str], reduce_list]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Since our graph topology hasn't changed, we can just reuse the same `make_graph` helper function we defined previously and pass new schema for the parent and child graphs." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[36;1m\u001b[1;3m[-2:checkpoint]\u001b[0m \u001b[1mState at the end of step -2:\n", - "\u001b[0m{'path': []}\n", - "\u001b[36;1m\u001b[1;3m[0:tasks]\u001b[0m \u001b[1mStarting step 0 with 1 task:\n", - "\u001b[0m- \u001b[32;1m\u001b[1;3m__start__\u001b[0m -> {'name': 'test'}\n", - "\u001b[36;1m\u001b[1;3m[0:writes]\u001b[0m \u001b[1mFinished step 0 with writes to 1 channel:\n", - "\u001b[0m- \u001b[33;1m\u001b[1;3mname\u001b[0m -> 'test'\n", - "\u001b[36;1m\u001b[1;3m[-2:checkpoint]\u001b[0m \u001b[1mState at the end of step -2:\n", - "\u001b[0m{'name': 'test', 'path': []}\n", - "\u001b[36;1m\u001b[1;3m[1:tasks]\u001b[0m \u001b[1mStarting step 1 with 1 task:\n", - "\u001b[0m- \u001b[32;1m\u001b[1;3mgrandparent\u001b[0m -> {'name': 'test', 'path': []}\n", - "\u001b[36;1m\u001b[1;3m[1:writes]\u001b[0m \u001b[1mFinished step 1 with writes to 1 channel:\n", - "\u001b[0m- \u001b[33;1m\u001b[1;3mpath\u001b[0m -> ['grandparent']\n", - "\u001b[36;1m\u001b[1;3m[-2:checkpoint]\u001b[0m \u001b[1mState at the end of step -2:\n", - "\u001b[0m{'name': 'test',\n", - " 'path': [{'id': 'a3b7abbe-1083-40af-aa6f-23b39d6b5ab7', 'val': 'grandparent'}]}\n", - "\u001b[36;1m\u001b[1;3m[2:tasks]\u001b[0m \u001b[1mStarting step 2 with 1 task:\n", - "\u001b[0m- \u001b[32;1m\u001b[1;3mparent\u001b[0m -> {'name': 'test',\n", - " 'path': [{'id': 'a3b7abbe-1083-40af-aa6f-23b39d6b5ab7', 'val': 'grandparent'}]}\n", - "\u001b[36;1m\u001b[1;3m[2:writes]\u001b[0m \u001b[1mFinished step 2 with writes to 1 channel:\n", - "\u001b[0m- \u001b[33;1m\u001b[1;3mpath\u001b[0m -> ['parent']\n", - "\u001b[36;1m\u001b[1;3m[-2:checkpoint]\u001b[0m \u001b[1mState at the end of step -2:\n", - "\u001b[0m{'name': 'test',\n", - " 'path': [{'id': 'a3b7abbe-1083-40af-aa6f-23b39d6b5ab7', 'val': 'grandparent'},\n", - " {'id': 'ce8522d8-5c45-4d0c-8e9f-42b11e9e5c6e', 'val': 'parent'}]}\n", - "\u001b[36;1m\u001b[1;3m[3:tasks]\u001b[0m \u001b[1mStarting step 3 with 2 tasks:\n", - "\u001b[0m- \u001b[32;1m\u001b[1;3mchild\u001b[0m -> {'name': 'test',\n", - " 'path': [{'id': 'a3b7abbe-1083-40af-aa6f-23b39d6b5ab7', 'val': 'grandparent'},\n", - " {'id': 'ce8522d8-5c45-4d0c-8e9f-42b11e9e5c6e', 'val': 'parent'}]}\n", - "- \u001b[32;1m\u001b[1;3msibling\u001b[0m -> {'name': 'test',\n", - " 'path': [{'id': 'a3b7abbe-1083-40af-aa6f-23b39d6b5ab7', 'val': 'grandparent'},\n", - " {'id': 'ce8522d8-5c45-4d0c-8e9f-42b11e9e5c6e', 'val': 'parent'}]}\n", - "\u001b[36;1m\u001b[1;3m[3:writes]\u001b[0m \u001b[1mFinished step 3 with writes to 2 channels:\n", - "\u001b[0m- \u001b[33;1m\u001b[1;3mname\u001b[0m -> 'test'\n", - "- \u001b[33;1m\u001b[1;3mpath\u001b[0m -> [{'id': 'a3b7abbe-1083-40af-aa6f-23b39d6b5ab7', 'val': 'grandparent'},\n", - " {'id': 'ce8522d8-5c45-4d0c-8e9f-42b11e9e5c6e', 'val': 'parent'},\n", - " {'id': '2c3d0366-9744-4ece-b3d5-95fa9727e5bf', 'val': 'child_start'},\n", - " {'id': 'b5920f7a-d722-43f2-86fa-cb9cb0dfdcc3', 'val': 'child_middle'},\n", - " {'id': '052b5578-6939-4dc0-8e24-0a13548a937e', 'val': 'child_end'}], ['sibling']\n", - "\u001b[36;1m\u001b[1;3m[-2:checkpoint]\u001b[0m \u001b[1mState at the end of step -2:\n", - "\u001b[0m{'name': 'test',\n", - " 'path': [{'id': 'a3b7abbe-1083-40af-aa6f-23b39d6b5ab7', 'val': 'grandparent'},\n", - " {'id': 'ce8522d8-5c45-4d0c-8e9f-42b11e9e5c6e', 'val': 'parent'},\n", - " {'id': '2c3d0366-9744-4ece-b3d5-95fa9727e5bf', 'val': 'child_start'},\n", - " {'id': 'b5920f7a-d722-43f2-86fa-cb9cb0dfdcc3', 'val': 'child_middle'},\n", - " {'id': '052b5578-6939-4dc0-8e24-0a13548a937e', 'val': 'child_end'},\n", - " {'id': 'ff5e852c-3c71-4133-87a1-ec2e0b3a5b29', 'val': 'sibling'}]}\n", - "\u001b[36;1m\u001b[1;3m[4:tasks]\u001b[0m \u001b[1mStarting step 4 with 1 task:\n", - "\u001b[0m- \u001b[32;1m\u001b[1;3mfin\u001b[0m -> {'name': 'test',\n", - " 'path': [{'id': 'a3b7abbe-1083-40af-aa6f-23b39d6b5ab7', 'val': 'grandparent'},\n", - " {'id': 'ce8522d8-5c45-4d0c-8e9f-42b11e9e5c6e', 'val': 'parent'},\n", - " {'id': '2c3d0366-9744-4ece-b3d5-95fa9727e5bf', 'val': 'child_start'},\n", - " {'id': 'b5920f7a-d722-43f2-86fa-cb9cb0dfdcc3', 'val': 'child_middle'},\n", - " {'id': '052b5578-6939-4dc0-8e24-0a13548a937e', 'val': 'child_end'},\n", - " {'id': 'ff5e852c-3c71-4133-87a1-ec2e0b3a5b29', 'val': 'sibling'}]}\n", - "\u001b[36;1m\u001b[1;3m[4:writes]\u001b[0m \u001b[1mFinished step 4 with writes to 1 channel:\n", - "\u001b[0m- \u001b[33;1m\u001b[1;3mpath\u001b[0m -> ['fin']\n" - ] - }, - { - "data": { - "text/plain": [ - "{'name': 'test',\n", - " 'path': [{'val': 'grandparent', 'id': 'a3b7abbe-1083-40af-aa6f-23b39d6b5ab7'},\n", - " {'val': 'parent', 'id': 'ce8522d8-5c45-4d0c-8e9f-42b11e9e5c6e'},\n", - " {'val': 'child_start', 'id': '2c3d0366-9744-4ece-b3d5-95fa9727e5bf'},\n", - " {'val': 'child_middle', 'id': 'b5920f7a-d722-43f2-86fa-cb9cb0dfdcc3'},\n", - " {'val': 'child_end', 'id': '052b5578-6939-4dc0-8e24-0a13548a937e'},\n", - " {'val': 'sibling', 'id': 'ff5e852c-3c71-4133-87a1-ec2e0b3a5b29'},\n", - " {'val': 'fin', 'id': '82dc42d5-799b-4fad-8fbd-b12f32c179d2'}]}" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "graph = make_graph(ParentState, ChildState)\n", - "graph.invoke({\"name\": \"test\"}, debug=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can see that that now the path values are no longer duplicated thanks to the updated reducer we introduced above." + "for chunk in graph.stream({\"foo\": \"foo\"}, subgraphs=True):\n", + " print(chunk)" ] } ], @@ -693,7 +301,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/docs/docs/how-tos/subgraphs-manage-state.ipynb b/docs/docs/how-tos/subgraphs-manage-state.ipynb index 0bf07e0e7..910b64fa7 100644 --- a/docs/docs/how-tos/subgraphs-manage-state.ipynb +++ b/docs/docs/how-tos/subgraphs-manage-state.ipynb @@ -5,15 +5,48 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# How to manage state in subgraphs\n", + "# How to view and update state in subgraphs\n", "\n", - "For more complex systems, sub-graphs are a useful design principle. Sub-graphs allow you to create and manage different states in different parts of your graph. This allows you build things like [multi-agent teams](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/hierarchical_agent_teams/), where each team can track its own separate state.\n", + "
\n", + "

Prerequisites

\n", + "

\n", + " This guide assumes familiarity with the following:\n", + "

\n", + "

\n", + "
\n", "\n", - "In this how-to guide we will cover how to manage the persisted state in subgraphs. This will enable a lot of the human-in-the-loop interaction patterns.\n", + "Once you add [persistence](../subgraph-persistence), you can easily view and update the state of the subgraph at any point in time. This enables a lot of the human-in-the-loop interaction patterns:\n", "\n", + "* You can surface a state during an interrupt to a user to let them accept an action.\n", + "* You can rewind the subgraph to reproduce or avoid issues.\n", + "* You can modify the state to let the user better control its actions.\n", + "\n", + "This guide shows how you can do this." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "## Setup\n", "\n", - "First we need to install the packages required" + "First, let's install the required packages" ] }, { @@ -68,7 +101,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Define SubGraph\n", + "## Define subgraph\n", "\n", "First, let's set up our subgraph. For this, we will create a simple graph that can get the weather for a specific city. We will compile this graph with a [breakpoint](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/breakpoints/) before the `weather_node`:" ] @@ -121,7 +154,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Define Parent Graph\n", + "## Define parent graph\n", "\n", "We can now setup the overall graph. This graph will first route to the subgraph if it needs to get the weather, otherwise it will route to a normal LLM." ] @@ -444,7 +477,7 @@ " if h.next == (\"model_node\",)\n", ")\n", "\n", - "# This pattern can be extended no matter how many levels deep - image model node was another subgraph in this case\n", + "# This pattern can be extended no matter how many levels deep\n", "# subsubgraph_stat_history = next(h for h in graph.get_state_history(subgraph_state_before_model_node.tasks[0].state) if h.next == ('my_subsubgraph_node',))" ] }, @@ -660,7 +693,9 @@ " print(update)\n", "# Graph execution should stop before the weather node\n", "print(\"interrupted!\")\n", + "\n", "state = graph.get_state(config, subgraphs=True)\n", + "\n", "# We update the state by passing in the message we want returned from the weather node, and make sure to use as_node\n", "graph.update_state(\n", " state.tasks[0].state.config,\n", @@ -669,6 +704,7 @@ ")\n", "for update in graph.stream(None, config=config, stream_mode=\"updates\", subgraphs=True):\n", " print(update)\n", + "\n", "print(graph.get_state(config).values[\"messages\"])" ] }, @@ -708,6 +744,7 @@ " print(update)\n", "# Graph execution should stop before the weather node\n", "print(\"interrupted!\")\n", + "\n", "# We update the state by passing in the message we want returned from the weather graph, making sure to use as_node\n", "# Note that we don't need to pass in the subgraph config, since we aren't updating the state inside the subgraph\n", "graph.update_state(\n", @@ -717,6 +754,7 @@ ")\n", "for update in graph.stream(None, config=config, stream_mode=\"updates\"):\n", " print(update)\n", + "\n", "print(graph.get_state(config).values[\"messages\"])" ] }, @@ -947,6 +985,7 @@ " None, config=config, stream_mode=\"updates\", subgraphs=True\n", "):\n", " print(update)\n", + "\n", "print(grandparent_graph.get_state(config).values[\"messages\"])" ] }, @@ -1002,7 +1041,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/docs/docs/how-tos/use-remote-graph.md b/docs/docs/how-tos/use-remote-graph.md new file mode 100644 index 000000000..819c46f4b --- /dev/null +++ b/docs/docs/how-tos/use-remote-graph.md @@ -0,0 +1,256 @@ +# How to interact with the deployment using RemoteGraph + +!!! info "Prerequisites" + - [LangGraph Platform](../concepts/langgraph_platform.md) + - [LangGraph Server](../concepts/langgraph_server.md) + +`RemoteGraph` is an interface that allows you to interact with your LangGraph Platform deployment as if it were a regular, locally-defined LangGraph graph (e.g. a `CompiledGraph`). This guide shows you how you can initialize a `RemoteGraph` and interact with it. + +## Initializing the graph + +When initializing a `RemoteGraph`, you must always specify: + +- `name`: the name of the graph you want to interact with. This is the same graph name you use in `langgraph.json` configuration file for your deployment. +- `api_key`: a valid LangSmith API key. Can be set as an environment variable (`LANGSMITH_API_KEY`) or passed directly via the `api_key` argument. The API key could also be provided via the `client` / `sync_client` arguments, if `LangGraphClient` / `SyncLangGraphClient` were initialized with `api_key` argument. + +Additionally, you have to provide one of the following: + +- `url`: URL of the deployment you want to interact with. If you pass `url` argument, both sync and async clients will be created using the provided URL, headers (if provided) and default configuration values (e.g. timeout, etc). +- `client`: a `LangGraphClient` instance for interacting with the deployment asynchronously (e.g. using `.astream()`, `.ainvoke()`, `.aget_state()`, `.aupdate_state()`, etc.) +- `sync_client`: a `SyncLangGraphClient` instance for interacting with the deployment synchronously (e.g. using `.stream()`, `.invoke()`, `.get_state()`, `.update_state()`, etc.) + +!!! Note + + If you pass both `client` or `sync_client` as well as `url` argument, they will take precedence over the `url` argument. If none of the `client` / `sync_client` / `url` arguments are provided, `RemoteGraph` will raise a `ValueError` at runtime. + + +### Using URL + +=== "Python" + + ```python + from langgraph.pregel.remote import RemoteGraph + + url = + graph_name = "agent" + remote_graph = RemoteGraph(graph_name, url=url) + ``` + +=== "JavaScript" + + ```ts + import { RemoteGraph } from "@langchain/langgraph/remote"; + + const url = ``; + const graphName = "agent"; + const remoteGraph = new RemoteGraph({ graphId: graphName, url }); + ``` + +### Using clients + +=== "Python" + + ```python + from langgraph_sdk import get_client, get_sync_client + from langgraph.pregel.remote import RemoteGraph + + url = + graph_name = "agent" + client = get_client(url=url) + sync_client = get_sync_client(url=url) + remote_graph = RemoteGraph(graph_name, client=client, sync_client=sync_client) + ``` + +=== "JavaScript" + + ```ts + import { Client } from "@langchain/langgraph-sdk"; + import { RemoteGraph } from "@langchain/langgraph/remote"; + + const client = new Client({ apiUrl: `` }); + const graphName = "agent"; + const remoteGraph = new RemoteGraph({ graphId: graphName, client }); + ``` + +## Invoking the graph + +Since `RemoteGraph` is a `Runnable` that implements the same methods as `CompiledGraph`, you can interact with it the same way you normally would with a compiled graph, i.e. by calling `.invoke()`, `.stream()`, `.get_state()`, `.update_state()`, etc (as well as their async counterparts). + +### Asynchronously + +!!! Note + + To use the graph asynchronously, you must provide either the `url` or `client` when initializing the `RemoteGraph`. + +=== "Python" + + ```python + # invoke the graph + result = await remote_graph.ainvoke({ + "messages": [{"role": "user", "content": "what's the weather in sf"}] + }) + + # stream outputs from the graph + async for chunk in remote_graph.astream({ + "messages": [{"role": "user", "content": "what's the weather in la"}] + }): + print(chunk) + ``` + +=== "JavaScript" + + ```ts + // invoke the graph + const result = await remoteGraph.invoke({ + messages: [{role: "user", content: "what's the weather in sf"}] + }) + + // stream outputs from the graph + for await (const chunk of await remoteGraph.stream({ + messages: [{role: "user", content: "what's the weather in la"}] + })): + console.log(chunk) + ``` + +### Synchronously + +!!! Note + + To use the graph synchronously, you must provide either the `url` or `sync_client` when initializing the `RemoteGraph`. + +=== "Python" + + ```python + # invoke the graph + result = remote_graph.invoke({ + "messages": [{"role": "user", "content": "what's the weather in sf"}] + }) + + # stream outputs from the graph + for chunk in remote_graph.stream({ + "messages": [{"role": "user", "content": "what's the weather in la"}] + }): + print(chunk) + ``` + +## Thread-level persistence + +By default, the graph runs (i.e. `.invoke()` or `.stream()` invocations) are stateless - the checkpoints and the final state of the graph are not persisted. If you would like to persist the outputs of the graph run (for example, to enable human-in-the-loop features), you can create a thread and provide the thread ID via the `config` argument, same as you would with a regular compiled graph: + +=== "Python" + + ```python + from langgraph_sdk import get_sync_client + url = + graph_name = "agent" + sync_client = get_sync_client(url=url) + remote_graph = RemoteGraph(graph_name, url=url) + + # create a thread (or use an existing thread instead) + thread = sync_client.threads.create() + + # invoke the graph with the thread config + config = {"configurable": {"thread_id": thread["thread_id"]}} + result = remote_graph.invoke({ + "messages": [{"role": "user", "content": "what's the weather in sf"}] + }, config=config) + + # verify that the state was persisted to the thread + thread_state = remote_graph.get_state(config) + print(thread_state) + ``` + +=== "JavaScript" + + ```ts + import { Client } from "@langchain/langgraph-sdk"; + import { RemoteGraph } from "@langchain/langgraph/remote"; + + const url = ``; + const graphName = "agent"; + const client = new Client({ apiUrl: url }); + const remoteGraph = new RemoteGraph({ graphId: graphName, url }); + + // create a thread (or use an existing thread instead) + const thread = await client.threads.create(); + + // invoke the graph with the thread config + const config = { configurable: { thread_id: thread.thread_id }}; + const result = await remoteGraph.invoke({ + messages: [{ role: "user", content: "what's the weather in sf" }], + }, config); + + // verify that the state was persisted to the thread + const threadState = await remoteGraph.getState(config); + console.log(threadState); + ``` + +## Using as a subgraph + +!!! Note + + If you need to use a `checkpointer` with a graph that has a `RemoteGraph` subgraph node, make sure to use UUIDs as thread IDs. + + +Since the `RemoteGraph` behaves the same way as a regular `CompiledGraph`, it can be also used as a subgraph in another graph. For example: + +=== "Python" + + ```python + from langgraph_sdk import get_sync_client + from langgraph.graph import StateGraph, MessagesState, START + from typing import TypedDict + + url = + graph_name = "agent" + remote_graph = RemoteGraph(graph_name, url=url) + + # define parent graph + builder = StateGraph(MessagesState) + # add remote graph directly as a node + builder.add_node("child", remote_graph) + builder.add_edge(START, "child") + graph = builder.compile() + + # invoke the parent graph + result = graph.invoke({ + "messages": [{"role": "user", "content": "what's the weather in sf"}] + }) + print(result) + + # stream outputs from both the parent graph and subgraph + for chunk in graph.stream({ + "messages": [{"role": "user", "content": "what's the weather in sf"}] + }, subgraphs=True): + print(chunk) + ``` + +=== "JavaScript" + + ```ts + import { MessagesAnnotation, StateGraph, START } from "@langchain/langgraph"; + import { RemoteGraph } from "@langchain/langgraph/remote"; + + const url = ``; + const graphName = "agent"; + const remoteGraph = new RemoteGraph({ graphId: graphName, url }); + + // define parent graph and add remote graph directly as a node + const graph = new StateGraph(MessagesAnnotation) + .addNode("child", remoteGraph) + .addEdge(START, "child") + .compile() + + // invoke the parent graph + const result = await graph.invoke({ + messages: [{ role: "user", content: "what's the weather in sf" }] + }); + console.log(result); + + // stream outputs from both the parent graph and subgraph + for await (const chunk of await graph.stream({ + messages: [{ role: "user", content: "what's the weather in la" }] + }, { subgraphs: true })) { + console.log(chunk); + } + ``` \ No newline at end of file diff --git a/docs/docs/index.md b/docs/docs/index.md index 072f2aada..24ccf17e5 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -3,7 +3,6 @@ hide_comments: true hide: - navigation title: Home - --- {!README.md!} diff --git a/docs/docs/reference/index.md b/docs/docs/reference/index.md new file mode 100644 index 000000000..dd78c1e8e --- /dev/null +++ b/docs/docs/reference/index.md @@ -0,0 +1,17 @@ +--- +title: Reference +description: API reference for LangGraph +--- + + + + +# Reference + +Welcome to the LangGraph API reference! This reference provides detailed information about the LangGraph API, including classes, methods, and other components. + +If you are new to LangGraph, we recommend starting with the [Quick Start](../tutorials/introduction.ipynb) in the Tutorials section. \ No newline at end of file diff --git a/docs/docs/reference/remote_graph.md b/docs/docs/reference/remote_graph.md new file mode 100644 index 000000000..1a1d23832 --- /dev/null +++ b/docs/docs/reference/remote_graph.md @@ -0,0 +1,6 @@ +# RemoteGraph + +::: langgraph.pregel.remote + options: + members: + - RemoteGraph diff --git a/docs/docs/troubleshooting/errors/GRAPH_RECURSION_LIMIT.md b/docs/docs/troubleshooting/errors/GRAPH_RECURSION_LIMIT.md new file mode 100644 index 000000000..332294d16 --- /dev/null +++ b/docs/docs/troubleshooting/errors/GRAPH_RECURSION_LIMIT.md @@ -0,0 +1,29 @@ +# GRAPH_RECURSION_LIMIT + +Your LangGraph [`StateGraph`](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.state.StateGraph) reached the maximum number of steps before hitting a stop condition. +This is often due to an infinite loop caused by code like the example below: + +```python +class State(TypedDict): + some_key: str + +builder = StateGraph(State) +builder.add_node("a", ...) +builder.add_node("b", ...) +builder.add_edge("a", "b") +builder.add_edge("b", "a") +... + +graph = builder.compile() +``` + +However, complex graphs may hit the default limit naturally. + +## Troubleshooting + +- If you are not expecting your graph to go through many iterations, you likely have a cycle. Check your logic for infinite loops. +- If you have a complex graph, you can pass in a higher `recursion_limit` value into your `config` object when invoking your graph like this: + +```python +graph.invoke({...}, {"recursion_limit": 100}) +``` \ No newline at end of file diff --git a/docs/docs/troubleshooting/errors/INVALID_CHAT_HISTORY.md b/docs/docs/troubleshooting/errors/INVALID_CHAT_HISTORY.md new file mode 100644 index 000000000..121152ba0 --- /dev/null +++ b/docs/docs/troubleshooting/errors/INVALID_CHAT_HISTORY.md @@ -0,0 +1,30 @@ +# INVALID_CHAT_HISTORY + +This error is raised in the prebuilt [create_react_agent][langgraph.prebuilt.chat_agent_executor.create_react_agent] when the `call_model` graph node receives a malformed list of messages. Specifically, it is malformed when there are `AIMessages` with `tool_calls` (LLM requesting to call a tool) that do not have a corresponding `ToolMessage` (result of a tool invocation to return to the LLM). + +There could be a few reasons you're seeing this error: + +1. You manually passed a malformed list of messages when invoking the graph, e.g. `graph.invoke({'messages': [AIMessage(..., tool_calls=[...])]})` +2. The graph was interrupted before receiving updates from the `tools` node (i.e. a list of ToolMessages) +and you invoked it with a an input that is not None or a ToolMessage, +e.g. `graph.invoke({'messages': [HumanMessage(...)]}, config)`. + This interrupt could have been triggered in one of the following ways: + - You manually set `interrupt_before = ['tools']` in `create_react_agent` + - One of the tools raised an error that wasn't handled by the [ToolNode][langgraph.prebuilt.tool_node.ToolNode] (`"tools"`) + +## Troubleshooting + +To resolve this, you can do one of the following: + +1. Don't invoke the graph with a malformed list of messages +2. In case of an interrupt (manual or due to an error) you can: + + - provide ToolMessages that match existing tool calls and call `graph.invoke({'messages': [ToolMessage(...)]})`. + **NOTE**: this will append the messages to the history and run the graph from the START node. + - manually update the state and resume the graph from the interrupt: + + 1. get the list of most recent messages from the graph state with `graph.get_state(config)` + 2. modify the list of messages to either remove unanswered tool calls from AIMessages +or add ToolMessages with tool_call_ids that match unanswered tool calls + 3. call `graph.update_state(config, {'messages': ...})` with the modified list of messages + 4. resume the graph, e.g. call `graph.invoke(None, config)` diff --git a/docs/docs/troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE.md b/docs/docs/troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE.md new file mode 100644 index 000000000..a02094578 --- /dev/null +++ b/docs/docs/troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE.md @@ -0,0 +1,49 @@ +# INVALID_CONCURRENT_GRAPH_UPDATE + +A LangGraph [`StateGraph`](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.state.StateGraph) received concurrent updates to its state from multiple nodes to a state property that doesn't +support it. + +One way this can occur is if you are using a [fanout](https://langchain-ai.github.io/langgraph/how-tos/map-reduce/) +or other parallel execution in your graph and you have defined a graph like this: + +```python +class State(TypedDict): + some_key: str + +def node(state: State): + return {"some_key": "some_string_value"} + +def other_node(state: State): + return {"some_key": "some_string_value"} + + +builder = StateGraph(State) +builder.add_node(node) +builder.add_node(other_node) +builder.add_edge(START, "node") +builder.add_edge(START, "other_node") +graph = builder.compile() +``` + +If a node in the above graph returns `{ "some_key": "some_string_value" }`, this will overwrite the state value for `"some_key"` with `"some_string_value"`. +However, if multiple nodes in e.g. a fanout within a single step return values for `"some_key"`, the graph will throw this error because +there is uncertainty around how to update the internal state. + +To get around this, you can define a reducer that combines multiple values: + +```python +import operator +from typing import Annotated + +class State(TypedDict): + # The operator.add reducer fn makes this append-only + some_key: Annotated[list, operator.add] +``` + +This will allow you to define logic that handles the same key returned from multiple nodes executed in parallel. + +## Troubleshooting + +The following may help resolve this error: + +- If your graph executes nodes in parallel, make sure you have defined relevant state keys with a reducer. \ No newline at end of file diff --git a/docs/docs/troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE.md b/docs/docs/troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE.md new file mode 100644 index 000000000..d318c1a6f --- /dev/null +++ b/docs/docs/troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE.md @@ -0,0 +1,38 @@ +# INVALID_GRAPH_NODE_RETURN_VALUE + +A LangGraph [`StateGraph`](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.state.StateGraph) +received a non-dict return type from a node. Here's an example: + +```python +class State(TypedDict): + some_key: str + +def bad_node(state: State): + # Should return an dict with a value for "some_key", not a list + return ["whoops"] + +builder = StateGraph(State) +builder.add_node(bad_node) +... + +graph = builder.compile() +``` + +Invoking the above graph will result in an error like this: + +```python +graph.invoke({ "some_key": "someval" }); +``` + +``` +InvalidUpdateError: Expected dict, got ['whoops'] +For troubleshooting, visit: https://python.langchain.com/docs/troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE +``` + +Nodes in your graph must return an dict containing one or more keys defined in your state. + +## Troubleshooting + +The following may help resolve this error: + +- If you have complex logic in your node, make sure all code paths return an appropriate dict for your defined state. \ No newline at end of file diff --git a/docs/docs/troubleshooting/errors/MULTIPLE_SUBGRAPHS.md b/docs/docs/troubleshooting/errors/MULTIPLE_SUBGRAPHS.md new file mode 100644 index 000000000..a0b41110d --- /dev/null +++ b/docs/docs/troubleshooting/errors/MULTIPLE_SUBGRAPHS.md @@ -0,0 +1,12 @@ +# MULTIPLE_SUBGRAPHS + +You are calling the same subgraph multiple times within a single LangGraph node with checkpointing enabled for each subgraph. + +This is currently not allowed due to internal restrictions on how checkpoint namespacing for subgraphs works. + +## Troubleshooting + +The following may help resolve this error: + +- If you don't need to interrupt/resume from a subgraph, pass `checkpointer=False` when compiling it like this: `.compile(checkpointer=False)` +- Don't imperatively call graphs multiple times in the same node, and instead use the [`Send`](https://langchain-ai.github.io/langgraph/concepts/low_level/#send) API. \ No newline at end of file diff --git a/docs/docs/troubleshooting/errors/index.md b/docs/docs/troubleshooting/errors/index.md new file mode 100644 index 000000000..c8a21d5d5 --- /dev/null +++ b/docs/docs/troubleshooting/errors/index.md @@ -0,0 +1,10 @@ +# Error reference + +This page contains guides around resolving common errors you may find while building with LangChain. +Errors referenced below will have an `lc_error_code` property corresponding to one of the below codes when they are thrown in code. + +- [GRAPH_RECURSION_LIMIT](./GRAPH_RECURSION_LIMIT.md) +- [INVALID_CONCURRENT_GRAPH_UPDATE](./INVALID_CONCURRENT_GRAPH_UPDATE.md) +- [INVALID_GRAPH_NODE_RETURN_VALUE](./INVALID_GRAPH_NODE_RETURN_VALUE.md) +- [MULTIPLE_SUBGRAPHS](./MULTIPLE_SUBGRAPHS.md) +- [INVALID_CHAT_HISTORY](./INVALID_CHAT_HISTORY.md) diff --git a/docs/docs/tutorials/code_assistant/langgraph_code_assistant.ipynb b/docs/docs/tutorials/code_assistant/langgraph_code_assistant.ipynb index 8ce428054..2e774a9b7 100644 --- a/docs/docs/tutorials/code_assistant/langgraph_code_assistant.ipynb +++ b/docs/docs/tutorials/code_assistant/langgraph_code_assistant.ipynb @@ -102,7 +102,7 @@ "from langchain_community.document_loaders.recursive_url_loader import RecursiveUrlLoader\n", "\n", "# LCEL docs\n", - "url = \"https://python.langchain.com/docs/concepts/#langchain-expression-language-lcel\"\n", + "url = \"https://python.langchain.com/docs/concepts/lcel/\"\n", "loader = RecursiveUrlLoader(\n", " url=url, max_depth=20, extractor=lambda x: Soup(x, \"html.parser\").text\n", ")\n", diff --git a/docs/docs/tutorials/customer-support/customer-support.ipynb b/docs/docs/tutorials/customer-support/customer-support.ipynb index 9cf3ac479..07fff6f97 100644 --- a/docs/docs/tutorials/customer-support/customer-support.ipynb +++ b/docs/docs/tutorials/customer-support/customer-support.ipynb @@ -35,7 +35,7 @@ "outputs": [], "source": [ "%%capture --no-stderr\n", - "% pip install -U langgraph langchain-community langchain-anthropic tavily-python pandas" + "%pip install -U langgraph langchain-community langchain-anthropic tavily-python pandas openai" ] }, { @@ -55,6 +55,7 @@ "\n", "\n", "_set_env(\"ANTHROPIC_API_KEY\")\n", + "_set_env(\"OPENAI_API_KEY\")\n", "_set_env(\"TAVILY_API_KEY\")" ] }, @@ -85,7 +86,9 @@ "cell_type": "code", "execution_count": 21, "id": "71638c2a-5038-439e-907a-de2bb548db34", - "metadata": {"hide_from_vcr": true}, + "metadata": { + "hide_from_vcr": true + }, "outputs": [], "source": [ "import os\n", @@ -176,7 +179,9 @@ "cell_type": "code", "execution_count": 22, "id": "654e2f81", - "metadata": {"hide_from_vcr": true}, + "metadata": { + "hide_from_vcr": true + }, "outputs": [], "source": [ "import re\n", @@ -1077,7 +1082,7 @@ " ),\n", " (\"placeholder\", \"{messages}\"),\n", " ]\n", - ").partial(time=datetime.now())\n", + ").partial(time=datetime.now)\n", "\n", "part_1_tools = [\n", " TavilySearchResults(max_results=1),\n", @@ -1893,7 +1898,7 @@ " ),\n", " (\"placeholder\", \"{messages}\"),\n", " ]\n", - ").partial(time=datetime.now())\n", + ").partial(time=datetime.now)\n", "\n", "part_2_tools = [\n", " TavilySearchResults(max_results=1),\n", @@ -2472,7 +2477,7 @@ " ),\n", " (\"placeholder\", \"{messages}\"),\n", " ]\n", - ").partial(time=datetime.now())\n", + ").partial(time=datetime.now)\n", "\n", "\n", "# \"Read\"-only tools (such as retrievers) don't need a user confirmation to use\n", @@ -3183,7 +3188,7 @@ " ),\n", " (\"placeholder\", \"{messages}\"),\n", " ]\n", - ").partial(time=datetime.now())\n", + ").partial(time=datetime.now)\n", "\n", "update_flight_safe_tools = [search_flights]\n", "update_flight_sensitive_tools = [update_ticket_to_new_flight, cancel_ticket]\n", @@ -3215,7 +3220,7 @@ " ),\n", " (\"placeholder\", \"{messages}\"),\n", " ]\n", - ").partial(time=datetime.now())\n", + ").partial(time=datetime.now)\n", "\n", "book_hotel_safe_tools = [search_hotels]\n", "book_hotel_sensitive_tools = [book_hotel, update_hotel, cancel_hotel]\n", @@ -3247,7 +3252,7 @@ " ),\n", " (\"placeholder\", \"{messages}\"),\n", " ]\n", - ").partial(time=datetime.now())\n", + ").partial(time=datetime.now)\n", "\n", "book_car_rental_safe_tools = [search_car_rentals]\n", "book_car_rental_sensitive_tools = [\n", @@ -3282,7 +3287,7 @@ " ),\n", " (\"placeholder\", \"{messages}\"),\n", " ]\n", - ").partial(time=datetime.now())\n", + ").partial(time=datetime.now)\n", "\n", "book_excursion_safe_tools = [search_trip_recommendations]\n", "book_excursion_sensitive_tools = [book_excursion, update_excursion, cancel_excursion]\n", @@ -3389,7 +3394,7 @@ " ),\n", " (\"placeholder\", \"{messages}\"),\n", " ]\n", - ").partial(time=datetime.now())\n", + ").partial(time=datetime.now)\n", "primary_assistant_tools = [\n", " TavilySearchResults(max_results=1),\n", " search_flights,\n", diff --git a/docs/docs/tutorials/index.md b/docs/docs/tutorials/index.md index 5d17ac234..887740e6a 100644 --- a/docs/docs/tutorials/index.md +++ b/docs/docs/tutorials/index.md @@ -1,6 +1,7 @@ --- hide: - - toc + - navigation +title: Tutorials --- # Tutorials @@ -11,35 +12,40 @@ Welcome to the LangGraph Tutorials! These notebooks introduce LangGraph through Learn the basics of LangGraph through a comprehensive quick start in which you will build an agent from scratch. -- [Quick Start](introduction.ipynb) +- [Quick Start](introduction.ipynb): In this tutorial, you will build a support chatbot using LangGraph. +- [LangGraph Cloud Quick Start](../cloud/quick_start.md): In this tutorial, you will build and deploy an agent to LangGraph Cloud. ## Use cases Learn from example implementations of graphs designed for specific scenarios and that implement common design patterns. -#### Chatbots +### Chatbots - [Customer Support](customer-support/customer-support.ipynb): Build a customer support chatbot to manage flights, hotel reservations, car rentals, and other tasks - [Prompt Generation from User Requirements](chatbots/information-gather-prompting.ipynb): Build an information gathering chatbot - [Code Assistant](code_assistant/langgraph_code_assistant.ipynb): Build a code analysis and generation assistant + +### RAG + +- [Agentic RAG](rag/langgraph_agentic_rag.ipynb): Use an agent to figure out how to retrieve the most relevant information before using the retrieved information to answer the user's question. +- [Adaptive RAG](rag/langgraph_adaptive_rag.ipynb): Adaptive RAG is a strategy for RAG that unites (1) query analysis with (2) active / self-corrective RAG. Implementation of: https://arxiv.org/abs/2403.14403 + - For a version that uses a local LLM: [Adaptive RAG using local LLMs](rag/langgraph_adaptive_rag_local.ipynb) +- [Corrective RAG](rag/langgraph_crag.ipynb): Uses an LLM to grade the quality of the retrieved information from the given source, and if the quality is low, it will try to retrieve the information from another source. Implementation of: https://arxiv.org/pdf/2401.15884.pdf + - For a version that uses a local LLM: [Corrective RAG using local LLMs](rag/langgraph_crag_local.ipynb) +- [Self-RAG](rag/langgraph_self_rag.ipynb): Self-RAG is a strategy for RAG that incorporates self-reflection / self-grading on retrieved documents and generations. Implementation of https://arxiv.org/abs/2310.11511. + - For a version that uses a local LLM: [Self-RAG using local LLMs](rag/langgraph_self_rag_local.ipynb) +- [SQL Agent](sql-agent.ipynb): Build a SQL agent that can answer questions about a SQL database. + + +### Agent Architectures + #### Multi-Agent Systems -- [Collaboration](multi_agent/multi-agent-collaboration.ipynb): Enable two agents to collaborate on a task -- [Supervision](multi_agent/agent_supervisor.ipynb): Use an LLM to orchestrate and delegate to individual agents +- [Network](multi_agent/multi-agent-collaboration.ipynb): Enable two or more agents to collaborate on a task +- [Supervisor](multi_agent/agent_supervisor.ipynb): Use an LLM to orchestrate and delegate to individual agents - [Hierarchical Teams](multi_agent/hierarchical_agent_teams.ipynb): Orchestrate nested teams of agents to solve problems - -#### RAG - -- [Adaptive RAG](rag/langgraph_adaptive_rag.ipynb) - - [Adaptive RAG using local LLMs](rag/langgraph_adaptive_rag_local.ipynb) -- [Agentic RAG](rag/langgraph_agentic_rag.ipynb) -- [Corrective RAG](rag/langgraph_crag.ipynb) - - [Corrective RAG using local LLMs](rag/langgraph_crag_local.ipynb) -- [Self-RAG](rag/langgraph_self_rag.ipynb) - - [Self-RAG using local LLMs](rag/langgraph_self_rag_local.ipynb) -- [SQL Agent](sql-agent.ipynb) - + #### Planning Agents - [Plan-and-Execute](plan-and-execute/plan-and-execute.ipynb): Implement a basic planning and execution agent @@ -50,15 +56,16 @@ Learn from example implementations of graphs designed for specific scenarios and - [Basic Reflection](reflection/reflection.ipynb): Prompt the agent to reflect on and revise its outputs - [Reflexion](reflexion/reflexion.ipynb): Critique missing and superfluous details to guide next steps -- [Language Agent Tree Search](lats/lats.ipynb): Use reflection and rewards to drive a tree search over agents +- [Tree of Thoughts](tot/tot.ipynb): Search over candidate solutions to a problem using a scored tree +- [Language Agent Tree Search](lats/lats.ipynb): Use reflection and rewards to drive a monte-carlo tree search over agents - [Self-Discover Agent](self-discover/self-discover.ipynb): Analyze an agent that learns about its own capabilities -#### Evaluation +### Evaluation - [Agent-based](chatbot-simulation-evaluation/agent-simulation-evaluation.ipynb): Evaluate chatbots via simulated user interactions - [In LangSmith](chatbot-simulation-evaluation/langsmith-agent-simulation-evaluation.ipynb): Evaluate chatbots in LangSmith over a dialog dataset -#### Experimental +### Experimental - [Web Research (STORM)](storm/storm.ipynb): Generate Wikipedia-like articles via research and multi-perspective QA - [TNT-LLM](tnt-llm/tnt-llm.ipynb): Build rich, interpretable taxonomies of user intentand using the classification system developed by Microsoft for their Bing Copilot application. diff --git a/docs/docs/tutorials/introduction.ipynb b/docs/docs/tutorials/introduction.ipynb index 7c95ec299..f37fcf138 100644 --- a/docs/docs/tutorials/introduction.ipynb +++ b/docs/docs/tutorials/introduction.ipynb @@ -5,7 +5,7 @@ "id": "4a1aae78-88a6-4133-b905-7e46c8e3772f", "metadata": {}, "source": [ - "# Quick Start\n", + "# LangGraph Quick Start\n", "\n", "In this comprehensive quick start, we will build a support chatbot in LangGraph that can:\n", "\n", @@ -127,7 +127,7 @@ "
\n", "

Note

\n", "

\n", - " The first thing you do when you define a graph is define the State of the graph. The State consists of the schema of the graph as well as reducer functions which specify how to apply updates to the state. In our example State is a TypedDict with a single key: messages. The messages key is annotated with the add_messages reducer function, which tells LangGraph to append new messages to the existing list, rather than overwriting it. State keys without an annotation will be overwritten by each update, storing the most recent value. Check out this conceptual guide to learn more about state, reducers and other low-level concepts.\n", + " The first thing you do when you define a graph is define the State of the graph. The State consists of the schema of the graph as well as reducer functions which specify how to apply updates to the state. In our example State is a TypedDict with a single key: messages. The messages key is annotated with the add_messages reducer function, which tells LangGraph to append new messages to the existing list, rather than overwriting it. State keys without an annotation will be overwritten by each update, storing the most recent value. Check out this conceptual guide to learn more about state, reducers and other low-level concepts.\n", "

\n", "
" ] diff --git a/docs/docs/tutorials/multi_agent/agent_supervisor.ipynb b/docs/docs/tutorials/multi_agent/agent_supervisor.ipynb index b1f94190f..d7952e990 100644 --- a/docs/docs/tutorials/multi_agent/agent_supervisor.ipynb +++ b/docs/docs/tutorials/multi_agent/agent_supervisor.ipynb @@ -10,11 +10,11 @@ "id": "a3e3ebc4-57af-4fe4-bdd3-36aff67bf276", "metadata": {}, "source": [ - "# Agent Supervisor\n", + "# Multi-agent supervisor\n", "\n", "The [previous example](../multi-agent-collaboration) routed messages automatically based on the output of the initial researcher agent.\n", "\n", - "We can also choose to use an LLM to orchestrate the different agents.\n", + "We can also choose to use an [LLM to orchestrate](https://langchain-ai.github.io/langgraph/concepts/multi_agent/#supervisor) the different agents.\n", "\n", "Below, we will create an agent group, with an agent supervisor to help delegate tasks.\n", "\n", @@ -35,12 +35,12 @@ "outputs": [], "source": [ "%%capture --no-stderr\n", - "%pip install -U langgraph langchain langchain_openai langchain_experimental langsmith pandas" + "%pip install -U langgraph langchain_community langchain_anthropic langchain_experimental" ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "30c2f3de-c730-4aec-85a6-af2c2f058803", "metadata": {}, "outputs": [], @@ -54,7 +54,7 @@ " os.environ[var] = getpass.getpass(f\"Please provide your {var}\")\n", "\n", "\n", - "_set_if_undefined(\"OPENAI_API_KEY\")\n", + "_set_if_undefined(\"ANTHROPIC_API_KEY\")\n", "_set_if_undefined(\"TAVILY_API_KEY\")" ] }, @@ -83,7 +83,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 3, "id": "f04c6778-403b-4b49-9b93-678e910d5cec", "metadata": {}, "outputs": [], @@ -91,45 +91,27 @@ "from typing import Annotated\n", "\n", "from langchain_community.tools.tavily_search import TavilySearchResults\n", - "from langchain_experimental.tools import PythonREPLTool\n", + "from langchain_core.tools import tool\n", + "from langchain_experimental.utilities import PythonREPL\n", "\n", "tavily_tool = TavilySearchResults(max_results=5)\n", "\n", "# This executes code locally, which can be unsafe\n", - "python_repl_tool = PythonREPLTool()" - ] - }, - { - "cell_type": "markdown", - "id": "d58d1e85-22d4-4c22-9062-72a346a0d709", - "metadata": {}, - "source": [ - "## Helper Utilities" - ] - }, - { - "cell_type": "markdown", - "id": "b7c302b0-cd57-4913-986f-5dc7d6d77386", - "metadata": {}, - "source": [ - "Define a helper function that we will use to create the nodes in the graph - it takes care of converting the agent response to a human message. This is important because that is how we will add it the global state of the graph" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "80862241-a1a7-4726-bce5-f867b233832e", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_core.messages import HumanMessage\n", + "repl = PythonREPL()\n", "\n", "\n", - "def agent_node(state, agent, name):\n", - " result = agent.invoke(state)\n", - " return {\n", - " \"messages\": [HumanMessage(content=result[\"messages\"][-1].content, name=name)]\n", - " }" + "@tool\n", + "def python_repl_tool(\n", + " code: Annotated[str, \"The python code to execute to generate your chart.\"],\n", + "):\n", + " \"\"\"Use this to execute python code and do math. If you want to see the output of a value,\n", + " you should print it out with `print(...)`. This is visible to the user.\"\"\"\n", + " try:\n", + " result = repl.run(code)\n", + " except BaseException as e:\n", + " return f\"Failed to execute. Error: {repr(e)}\"\n", + " result_str = f\"Successfully executed:\\n```python\\n{code}\\n```\\nStdout: {result}\"\n", + " return result_str" ] }, { @@ -139,57 +121,78 @@ "source": [ "### Create Agent Supervisor\n", "\n", - "It will use function calling to choose the next worker node OR finish processing." + "It will use LLM with structured output to choose the next worker node OR finish processing." ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, + "id": "f16c289b-10b0-47a8-a675-0bb54d299236", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.graph import MessagesState" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "4a3baabf-18cb-415c-9473-d0546cf58b8b", + "metadata": {}, + "outputs": [], + "source": [ + "# The agent state is the input to each node in the graph\n", + "class AgentState(MessagesState):\n", + " # The 'next' field indicates where to route to next\n", + " next: str" + ] + }, + { + "cell_type": "code", + "execution_count": 6, "id": "311f0a58-b425-4496-adac-dc4cd8ffb912", "metadata": {}, "outputs": [], "source": [ - "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", - "from langchain_openai import ChatOpenAI\n", - "from pydantic import BaseModel\n", "from typing import Literal\n", + "from typing_extensions import TypedDict\n", + "\n", + "from langchain_anthropic import ChatAnthropic\n", + "from langgraph.graph import MessagesState\n", + "\n", + "members = [\"researcher\", \"coder\"]\n", + "# Our team supervisor is an LLM node. It just picks the next agent to process\n", + "# and decides when the work is completed\n", + "options = members + [\"FINISH\"]\n", "\n", - "members = [\"Researcher\", \"Coder\"]\n", "system_prompt = (\n", " \"You are a supervisor tasked with managing a conversation between the\"\n", - " \" following workers: {members}. Given the following user request,\"\n", + " f\" following workers: {members}. Given the following user request,\"\n", " \" respond with the worker to act next. Each worker will perform a\"\n", " \" task and respond with their results and status. When finished,\"\n", " \" respond with FINISH.\"\n", ")\n", - "# Our team supervisor is an LLM node. It just picks the next agent to process\n", - "# and decides when the work is completed\n", - "options = [\"FINISH\"] + members\n", "\n", "\n", - "class routeResponse(BaseModel):\n", + "class Router(TypedDict):\n", + " \"\"\"Worker to route to next. If no workers needed, route to FINISH.\"\"\"\n", + "\n", " next: Literal[*options]\n", "\n", "\n", - "prompt = ChatPromptTemplate.from_messages(\n", - " [\n", - " (\"system\", system_prompt),\n", - " MessagesPlaceholder(variable_name=\"messages\"),\n", - " (\n", - " \"system\",\n", - " \"Given the conversation above, who should act next?\"\n", - " \" Or should we FINISH? Select one of: {options}\",\n", - " ),\n", - " ]\n", - ").partial(options=str(options), members=\", \".join(members))\n", + "llm = ChatAnthropic(model=\"claude-3-5-sonnet-latest\")\n", "\n", "\n", - "llm = ChatOpenAI(model=\"gpt-4o\")\n", + "def supervisor_node(state: AgentState) -> AgentState:\n", + " messages = [\n", + " {\"role\": \"system\", \"content\": system_prompt},\n", + " ] + state[\"messages\"]\n", + " response = llm.with_structured_output(Router).invoke(messages)\n", + " next_ = response[\"next\"]\n", + " if next_ == \"FINISH\":\n", + " next_ = END\n", "\n", - "\n", - "def supervisor_agent(state):\n", - " supervisor_chain = prompt | llm.with_structured_output(routeResponse)\n", - " return supervisor_chain.invoke(state)" + " return {\"next\": next_}" ] }, { @@ -204,42 +207,57 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 7, "id": "6a430af7-8fce-4e66-ba9e-d940c1bc48e8", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "import functools\n", - "import operator\n", - "from typing import Sequence\n", - "from typing_extensions import TypedDict\n", - "\n", - "from langchain_core.messages import BaseMessage\n", - "\n", - "from langgraph.graph import END, StateGraph, START\n", + "from langchain_core.messages import HumanMessage\n", + "from langgraph.graph import StateGraph, START, END\n", "from langgraph.prebuilt import create_react_agent\n", "\n", "\n", - "# The agent state is the input to each node in the graph\n", - "class AgentState(TypedDict):\n", - " # The annotation tells the graph that new messages will always\n", - " # be added to the current states\n", - " messages: Annotated[Sequence[BaseMessage], operator.add]\n", - " # The 'next' field indicates where to route to next\n", - " next: str\n", + "research_agent = create_react_agent(\n", + " llm, tools=[tavily_tool], state_modifier=\"You are a researcher. DO NOT do any math.\"\n", + ")\n", "\n", "\n", - "research_agent = create_react_agent(llm, tools=[tavily_tool])\n", - "research_node = functools.partial(agent_node, agent=research_agent, name=\"Researcher\")\n", + "def research_node(state: AgentState) -> AgentState:\n", + " result = research_agent.invoke(state)\n", + " return {\n", + " \"messages\": [\n", + " HumanMessage(content=result[\"messages\"][-1].content, name=\"researcher\")\n", + " ]\n", + " }\n", "\n", - "# NOTE: THIS PERFORMS ARBITRARY CODE EXECUTION. PROCEED WITH CAUTION\n", + "\n", + "# NOTE: THIS PERFORMS ARBITRARY CODE EXECUTION, WHICH CAN BE UNSAFE WHEN NOT SANDBOXED\n", "code_agent = create_react_agent(llm, tools=[python_repl_tool])\n", - "code_node = functools.partial(agent_node, agent=code_agent, name=\"Coder\")\n", "\n", - "workflow = StateGraph(AgentState)\n", - "workflow.add_node(\"Researcher\", research_node)\n", - "workflow.add_node(\"Coder\", code_node)\n", - "workflow.add_node(\"supervisor\", supervisor_agent)" + "\n", + "def code_node(state: AgentState) -> AgentState:\n", + " result = code_agent.invoke(state)\n", + " return {\n", + " \"messages\": [HumanMessage(content=result[\"messages\"][-1].content, name=\"coder\")]\n", + " }\n", + "\n", + "\n", + "builder = StateGraph(MessagesState)\n", + "builder.add_edge(START, \"supervisor\")\n", + "builder.add_node(\"supervisor\", supervisor_node)\n", + "builder.add_node(\"researcher\", research_node)\n", + "builder.add_node(\"coder\", code_node)" ] }, { @@ -252,23 +270,53 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 8, "id": "14778e86-077b-4e6a-893c-400e59b0cdbf", "metadata": {}, "outputs": [], "source": [ "for member in members:\n", " # We want our workers to ALWAYS \"report back\" to the supervisor when done\n", - " workflow.add_edge(member, \"supervisor\")\n", + " builder.add_edge(member, \"supervisor\")\n", + "\n", "# The supervisor populates the \"next\" field in the graph state\n", "# which routes to a node or finishes\n", - "conditional_map = {k: k for k in members}\n", - "conditional_map[\"FINISH\"] = END\n", - "workflow.add_conditional_edges(\"supervisor\", lambda x: x[\"next\"], conditional_map)\n", + "builder.add_conditional_edges(\"supervisor\", lambda state: state[\"next\"])\n", "# Finally, add entrypoint\n", - "workflow.add_edge(START, \"supervisor\")\n", + "builder.add_edge(START, \"supervisor\")\n", "\n", - "graph = workflow.compile()" + "graph = builder.compile()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "fb2cf698-c42b-49ba-8ade-585d207a3daa", + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import display, Image" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "f4992dcb-33c5-4fef-b4e3-09c303737342", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display(Image(graph.get_graph().draw_mermaid_png()))" ] }, { @@ -283,7 +331,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 11, "id": "56ba78e9-d9c1-457c-a073-d606d5d3e013", "metadata": {}, "outputs": [ @@ -291,7 +339,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'supervisor': {'next': 'Coder'}}\n", + "((), {'supervisor': {'next': 'coder'}})\n", "----\n" ] }, @@ -306,9 +354,62 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'Coder': {'messages': [HumanMessage(content='The code to print \"Hello, World!\" in the terminal has been executed successfully. Here is the output:\\n\\n```\\nHello, World!\\n```', additional_kwargs={}, response_metadata={}, name='Coder')]}}\n", + "(('coder:a0c2a6de-4a2d-3573-4049-cba490183bc1',), {'agent': {'messages': [AIMessage(content=[{'text': \"I'll help you calculate the square root of 42 using Python.\", 'type': 'text'}, {'id': 'toolu_011Nsa2En2Qk1SsYBdG6zveY', 'input': {'code': 'import math\\nprint(math.sqrt(42))'}, 'name': 'python_repl_tool', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_016CdBcK9JKm39tsuGH6skhN', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 435, 'output_tokens': 82}}, id='run-f9be84c7-1569-4f53-9063-b1244339755b-0', tool_calls=[{'name': 'python_repl_tool', 'args': {'code': 'import math\\nprint(math.sqrt(42))'}, 'id': 'toolu_011Nsa2En2Qk1SsYBdG6zveY', 'type': 'tool_call'}], usage_metadata={'input_tokens': 435, 'output_tokens': 82, 'total_tokens': 517, 'input_token_details': {}})]}})\n", "----\n", - "{'supervisor': {'next': 'FINISH'}}\n", + "(('coder:a0c2a6de-4a2d-3573-4049-cba490183bc1',), {'tools': {'messages': [ToolMessage(content='Successfully executed:\\n```python\\nimport math\\nprint(math.sqrt(42))\\n```\\nStdout: 6.48074069840786\\n', name='python_repl_tool', id='8b6bd229-5c63-43a4-9d63-e3b4a8468e21', tool_call_id='toolu_011Nsa2En2Qk1SsYBdG6zveY')]}})\n", + "----\n", + "(('coder:a0c2a6de-4a2d-3573-4049-cba490183bc1',), {'agent': {'messages': [AIMessage(content='The square root of 42 is approximately 6.4807 (rounded to 4 decimal places).', additional_kwargs={}, response_metadata={'id': 'msg_01QYQtz84F1Mgqyp2ecw4TEu', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 561, 'output_tokens': 28}}, id='run-b9dfff5d-f1c4-44d6-98d7-80f0e8548bcd-0', usage_metadata={'input_tokens': 561, 'output_tokens': 28, 'total_tokens': 589, 'input_token_details': {}})]}})\n", + "----\n", + "((), {'coder': {'messages': [HumanMessage(content='The square root of 42 is approximately 6.4807 (rounded to 4 decimal places).', additional_kwargs={}, response_metadata={}, name='coder')]}})\n", + "----\n", + "((), {'supervisor': {'next': '__end__'}})\n", + "----\n" + ] + } + ], + "source": [ + "for s in graph.stream(\n", + " {\"messages\": [(\"user\", \"What's the square root of 42?\")]}, subgraphs=True\n", + "):\n", + " print(s)\n", + " print(\"----\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "45a92dfd-0e11-47f5-aad4-b68d24990e34", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "((), {'supervisor': {'next': 'researcher'}})\n", + "----\n", + "(('researcher:7daea379-a5b6-6d3d-ef85-fffc96d7472e',), {'agent': {'messages': [AIMessage(content=[{'text': \"I'll help you search for the GDP data of New York and California using the search tool. Then I'll note the values, but as instructed, I won't perform the mathematical calculation myself.\", 'type': 'text'}, {'id': 'toolu_01S9hPD5nFsW1A2nE4fwCvRc', 'input': {'query': 'latest GDP of New York state 2023'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01RetKetMGpP2Q51w4R8N81e', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 442, 'output_tokens': 107}}, id='run-6e738192-18ae-4c1a-bbc0-f8b8509fe656-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'latest GDP of New York state 2023'}, 'id': 'toolu_01S9hPD5nFsW1A2nE4fwCvRc', 'type': 'tool_call'}], usage_metadata={'input_tokens': 442, 'output_tokens': 107, 'total_tokens': 549, 'input_token_details': {}})]}})\n", + "----\n", + "(('researcher:7daea379-a5b6-6d3d-ef85-fffc96d7472e',), {'tools': {'messages': [ToolMessage(content='[{\"url\": \"https://usafacts.org/metrics/gross-domestic-product-gdp-by-state-new-york/\", \"content\": \"Gross domestic product (GDP) state — New York (dollars) Adjustment. None. Adjustment. Frequency. Yearly. Frequency. In 2022 (most recent), Gross domestic product (GDP) was $2,053,179,700,000 in the United States for New York (state). ... August 25, 2023. Suggested citation: Explore in... Less detail\"}, {\"url\": \"https://www.osc.ny.gov/reports/finance/2023-fcr/economic-and-demographic-trends\", \"content\": \"These include, but are not limited to:\\\\nBecause Google Translate™ is intellectual property owned by Google Inc., you must use Google Translate™ in accord with the Google license agreement, which includes potential liability for misuse: Google Terms of Service.\\\\nOffice of the NEW YORK\\\\nSTATE COMPTROLLER\\\\nNYS Comptroller Thomas P. DiNapoli\\\\nMain navigation\\\\nGET to KnowNew York State ComptrollerThomas P. DiNapoli\\\\nRead BIO\\\\nGET to KnowNew York State ComptrollerThomas P. DiNapoli\\\\nMenu\\\\nEconomic and Demographic Trends\\\\n2023 Financial Condition Report For Fiscal Year Ended March 31, 2023\\\\nEmployment Still Below Pre- Pandemic Levels in 2022\\\\nNew York Ranked 45th Nationwide for Personal Income Growth in 2022\\\\nNYS GDP Nearly $1.6 Trillion in 2022\\\\nA state’s Gross Domestic Product (GDP) is the value of production originating from all industries in the state, as defined by the U.S. Bureau of Economic Analysis.\\\\n The State of New York, its officers, employees, and/or agents are not liable to you, or to third parties, for damages or losses of any kind arising out of, or in connection with, the use or performance of such information. New York’s Population Continued to Decline in 2022\\\\nBook traversal links for Economic and Demographic Trends\\\\nTell us more about you to receive content related to your area or interests.\\\\n The Office of the State Comptroller does not warrant, promise, assure or guarantee the accuracy of the translations provided.\"}, {\"url\": \"https://www.statista.com/statistics/188087/gdp-of-the-us-federal-state-of-new-york-since-1997/\", \"content\": \"Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\xa0Mio. facts.\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nU.S. real GDP of New York 2000-2022\\\\nReal gross domestic product of New York in the United States\\\\nfrom 2000 to 2022\\\\n(in billion U.S. dollars)\\\\nAdditional Information\\\\nShow sources information\\\\nShow publisher information\\\\nUse Ask Statista Research Service\\\\nMarch 2023\\\\nUnited States\\\\n2000 to 2022\\\\nData presented here is in 2012 chained U.S. dollars.\\\\n Statistics on\\\\n\\\\\"\\\\nNew York\\\\n\\\\\"\\\\nOther statistics that may interest you New York\\\\nPopulation\\\\nEconomy\\\\nEmployment & Earnings\\\\nState & Local Government\\\\nNew York City\\\\nFurther related statistics\\\\nFurther Content: You might find this interesting as well\\\\nStatistics\\\\nTopics Other statistics on the topic\\\\nEconomy\\\\nU.S. real gross domestic product 2022, by state\\\\nPolitics & Government\\\\nU.S. state and local government outstanding debt 2021, by state\\\\nDemographics\\\\nResident population in New York 1960-2022\\\\nEconomy\\\\nU.S. New York metro area GDP 2001-2022\\\\nYou only have access to basic statistics.\\\\n Customized Research & Analysis projects:\\\\nGet quick analyses with our professional research service\\\\nThe best of the best: the portal for top lists & rankings:\\\\n\"}, {\"url\": \"https://www.statista.com/statistics/306777/new-york-gdp-growth/\", \"content\": \"Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\xa0Mio. facts.\\\\n Other statistics on the topic\\\\nEconomy\\\\nU.S. real gross domestic product 2022, by state\\\\nPolitics & Government\\\\nU.S. state and local government outstanding debt 2021, by state\\\\nDemographics\\\\nResident population in New York 1960-2022\\\\nEconomy\\\\nU.S. New York metro area GDP 2001-2022\\\\nTo download this statistic in XLS format you need a Statista Account\\\\nTo download this statistic in PNG format you need a Statista Account\\\\nTo download this statistic in PDF format you need a Statista Account\\\\nTo download this statistic in PPT format you need a Statista Account\\\\nAs a Premium user you get access to the detailed source references and background information about this statistic.\\\\n Statistics on\\\\n\\\\\"\\\\nNew York\\\\n\\\\\"\\\\nOther statistics that may interest you New York\\\\nPopulation\\\\nEconomy\\\\nEmployment & Earnings\\\\nState & Local Government\\\\nNew York City\\\\nFurther related statistics\\\\nFurther Content: You might find this interesting as well\\\\nStatistics\\\\nTopics U.S. annual GDP growth in New York 2000-2022\\\\nAnnual percent change in the real gross domestic product of New York in the United States from 2000 to 2022\\\\nAdditional Information\\\\nShow sources information\\\\nShow publisher information\\\\nUse Ask Statista Research Service\\\\nOctober 2023\\\\nUnited States (New York)\\\\n2000 to 2022\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nIndustry-specific and extensively researched technical data (partially from exclusive partnerships).\"}, {\"url\": \"https://www.bea.gov/news/2024/gross-domestic-product-state-and-personal-income-state-4th-quarter-2023-and-preliminary\", \"content\": \"Real gross domestic product (GDP) increased in all 50 states and the District of Columbia in the fourth quarter of 2023, with the percent change ranging from 6.7 percent in Nevada to 0.2 percent in Nebraska (table 1), according to statistics released today by the U.S. Bureau of Economic Analysis (BEA). Current-dollar GDP increased in 49 states and the District of Columbia.\"}]', name='tavily_search_results_json', id='a7ba20fa-57d5-43e9-9d15-29e4e6476edf', tool_call_id='toolu_01S9hPD5nFsW1A2nE4fwCvRc', artifact={'query': 'latest GDP of New York state 2023', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'title': 'Gross domestic product (GDP) - USAFacts', 'url': 'https://usafacts.org/metrics/gross-domestic-product-gdp-by-state-new-york/', 'content': 'Gross domestic product (GDP) state — New York (dollars) Adjustment. None. Adjustment. Frequency. Yearly. Frequency. In 2022 (most recent), Gross domestic product (GDP) was $2,053,179,700,000 in the United States for New York (state). ... August 25, 2023. Suggested citation: Explore in... Less detail', 'score': 0.99883956, 'raw_content': None}, {'title': 'Economic and Demographic Trends - Office of the New York State Comptroller', 'url': 'https://www.osc.ny.gov/reports/finance/2023-fcr/economic-and-demographic-trends', 'content': 'These include, but are not limited to:\\nBecause Google Translate™ is intellectual property owned by Google Inc., you must use Google Translate™ in accord with the Google license agreement, which includes potential liability for misuse: Google Terms of Service.\\nOffice of the NEW YORK\\nSTATE COMPTROLLER\\nNYS Comptroller Thomas P. DiNapoli\\nMain navigation\\nGET to KnowNew York State ComptrollerThomas P. DiNapoli\\nRead BIO\\nGET to KnowNew York State ComptrollerThomas P. DiNapoli\\nMenu\\nEconomic and Demographic Trends\\n2023 Financial Condition Report For Fiscal Year Ended March 31, 2023\\nEmployment Still Below Pre- Pandemic Levels in 2022\\nNew York Ranked 45th Nationwide for Personal Income Growth in 2022\\nNYS GDP Nearly $1.6 Trillion in 2022\\nA state’s Gross Domestic Product (GDP) is the value of production originating from all industries in the state, as defined by the U.S. Bureau of Economic Analysis.\\n The State of New York, its officers, employees, and/or agents are not liable to you, or to third parties, for damages or losses of any kind arising out of, or in connection with, the use or performance of such information. New York’s Population Continued to Decline in 2022\\nBook traversal links for Economic and Demographic Trends\\nTell us more about you to receive content related to your area or interests.\\n The Office of the State Comptroller does not warrant, promise, assure or guarantee the accuracy of the translations provided.', 'score': 0.97339284, 'raw_content': None}, {'title': 'Real GDP New York U.S. 2023 | Statista', 'url': 'https://www.statista.com/statistics/188087/gdp-of-the-us-federal-state-of-new-york-since-1997/', 'content': 'Industry Overview\\nDigital & Trend reports\\nOverview and forecasts on trending topics\\nIndustry & Market reports\\nIndustry and market insights and forecasts\\nCompanies & Products reports\\nKey figures and rankings about companies and products\\nConsumer & Brand reports\\nConsumer and brand insights and preferences in various industries\\nPolitics & Society reports\\nDetailed information about political and social topics\\nCountry & Region reports\\nAll key figures about countries and regions\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\nInsights on consumer attitudes and behavior worldwide\\nBusiness information on 100m+ public and private companies\\nExplore Company Insights\\nDetailed information for 39,000+ online stores and marketplaces\\nDirectly accessible data for 170 industries from 150+ countries\\nand over 1\\xa0Mio. facts.\\n Transforming data into design:\\nStatista Content & Design\\nStrategy and business building for the data-driven economy:\\nU.S. real GDP of New York 2000-2022\\nReal gross domestic product of New York in the United States\\nfrom 2000 to 2022\\n(in billion U.S. dollars)\\nAdditional Information\\nShow sources information\\nShow publisher information\\nUse Ask Statista Research Service\\nMarch 2023\\nUnited States\\n2000 to 2022\\nData presented here is in 2012 chained U.S. dollars.\\n Statistics on\\n\"\\nNew York\\n\"\\nOther statistics that may interest you New York\\nPopulation\\nEconomy\\nEmployment & Earnings\\nState & Local Government\\nNew York City\\nFurther related statistics\\nFurther Content: You might find this interesting as well\\nStatistics\\nTopics Other statistics on the topic\\nEconomy\\nU.S. real gross domestic product 2022, by state\\nPolitics & Government\\nU.S. state and local government outstanding debt 2021, by state\\nDemographics\\nResident population in New York 1960-2022\\nEconomy\\nU.S. New York metro area GDP 2001-2022\\nYou only have access to basic statistics.\\n Customized Research & Analysis projects:\\nGet quick analyses with our professional research service\\nThe best of the best: the portal for top lists & rankings:\\n', 'score': 0.76454014, 'raw_content': None}, {'title': 'Annual GDP growth New York U.S. 2023 | Statista', 'url': 'https://www.statista.com/statistics/306777/new-york-gdp-growth/', 'content': 'Industry Overview\\nDigital & Trend reports\\nOverview and forecasts on trending topics\\nIndustry & Market reports\\nIndustry and market insights and forecasts\\nCompanies & Products reports\\nKey figures and rankings about companies and products\\nConsumer & Brand reports\\nConsumer and brand insights and preferences in various industries\\nPolitics & Society reports\\nDetailed information about political and social topics\\nCountry & Region reports\\nAll key figures about countries and regions\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\nInsights on consumer attitudes and behavior worldwide\\nBusiness information on 100m+ public and private companies\\nExplore Company Insights\\nDetailed information for 39,000+ online stores and marketplaces\\nDirectly accessible data for 170 industries from 150+ countries\\nand over 1\\xa0Mio. facts.\\n Other statistics on the topic\\nEconomy\\nU.S. real gross domestic product 2022, by state\\nPolitics & Government\\nU.S. state and local government outstanding debt 2021, by state\\nDemographics\\nResident population in New York 1960-2022\\nEconomy\\nU.S. New York metro area GDP 2001-2022\\nTo download this statistic in XLS format you need a Statista Account\\nTo download this statistic in PNG format you need a Statista Account\\nTo download this statistic in PDF format you need a Statista Account\\nTo download this statistic in PPT format you need a Statista Account\\nAs a Premium user you get access to the detailed source references and background information about this statistic.\\n Statistics on\\n\"\\nNew York\\n\"\\nOther statistics that may interest you New York\\nPopulation\\nEconomy\\nEmployment & Earnings\\nState & Local Government\\nNew York City\\nFurther related statistics\\nFurther Content: You might find this interesting as well\\nStatistics\\nTopics U.S. annual GDP growth in New York 2000-2022\\nAnnual percent change in the real gross domestic product of New York in the United States from 2000 to 2022\\nAdditional Information\\nShow sources information\\nShow publisher information\\nUse Ask Statista Research Service\\nOctober 2023\\nUnited States (New York)\\n2000 to 2022\\n Transforming data into design:\\nStatista Content & Design\\nStrategy and business building for the data-driven economy:\\nIndustry-specific and extensively researched technical data (partially from exclusive partnerships).', 'score': 0.7212526, 'raw_content': None}, {'title': 'Gross Domestic Product by State and Personal Income by State, 4th ...', 'url': 'https://www.bea.gov/news/2024/gross-domestic-product-state-and-personal-income-state-4th-quarter-2023-and-preliminary', 'content': 'Real gross domestic product (GDP) increased in all 50 states and the District of Columbia in the fourth quarter of 2023, with the percent change ranging from 6.7 percent in Nevada to 0.2 percent in Nebraska (table 1), according to statistics released today by the U.S. Bureau of Economic Analysis (BEA). Current-dollar GDP increased in 49 states and the District of Columbia.', 'score': 0.36139008, 'raw_content': None}], 'response_time': 2.31})]}})\n", + "----\n", + "(('researcher:7daea379-a5b6-6d3d-ef85-fffc96d7472e',), {'agent': {'messages': [AIMessage(content=[{'id': 'toolu_015fdnpWUiuEshsEwn2nBJ1g', 'input': {'query': 'latest GDP of California state 2023'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01Cksgb2aaqcD2bPtam5HDPF', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 2534, 'output_tokens': 66}}, id='run-11d924ba-a494-49d6-b649-ac961e31c79c-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'latest GDP of California state 2023'}, 'id': 'toolu_015fdnpWUiuEshsEwn2nBJ1g', 'type': 'tool_call'}], usage_metadata={'input_tokens': 2534, 'output_tokens': 66, 'total_tokens': 2600, 'input_token_details': {}})]}})\n", + "----\n", + "(('researcher:7daea379-a5b6-6d3d-ef85-fffc96d7472e',), {'tools': {'messages': [ToolMessage(content='[{\"url\": \"https://www.gov.ca.gov/2024/04/16/california-remains-the-worlds-5th-largest-economy/\", \"content\": \"California remains the 5th largest economy in the world since 2017. California is the 5th largest economy in the world for the seventh consecutive year, with a nominal GDP of nearly $3.9 trillion in 2023 and a growth rate of 6.1% since the year prior, according to the U.S. Bureau of Economic Analysis (BEA). On a per capita basis, California is\"}, {\"url\": \"https://usafacts.org/metrics/gross-domestic-product-gdp-by-state-california/\", \"content\": \"USAFacts -- In 2022 (most recent), Gross domestic product (GDP) was 3598102700000.0 in the United States for California (state). This increased by 224,862,000,000 or 6.67% from 2021. Highest: 3,598,102,700,000 in 2022.\"}, {\"url\": \"https://www.statista.com/statistics/187834/gdp-of-the-us-federal-state-of-california-since-1997/\", \"content\": \"Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\xa0Mio. facts.\\\\n Statistics on\\\\n\\\\\"\\\\nCalifornia\\\\n\\\\\"\\\\nOther statistics that may interest you California\\\\nPopulation\\\\nEconomy\\\\nEmployment & Earnings\\\\nState & Local Government\\\\nMetro Areas\\\\nFurther related statistics\\\\nFurther Content: You might find this interesting as well\\\\nStatistics\\\\nTopics Other statistics on the topicCalifornia\\\\nEconomy\\\\nU.S. leading companies headquartered in California 2023, by number of employees\\\\nEconomy\\\\nU.S. average annual wages in California 2018-2026\\\\nEconomy\\\\nU.S. California fastest growing private companies 2023, by three year growth rate\\\\nResidential Real Estate\\\\nHourly wages needed to afford a two-bedroom apartment in California 2021-23, by metro\\\\nYou only have access to basic statistics.\\\\n Additional Information\\\\nShow sources information\\\\nShow publisher information\\\\nUse Ask Statista Research Service\\\\nMarch 2023\\\\nUnited States\\\\n2000 to 2022\\\\nData presented here is in 2012 chained U.S. dollars.\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nU.S. real GDP of California 2000-2022\\\\nReal gross domestic product of California in the United States from 2000 to 2022\\\\n(in billion U.S. dollars)\\\\n\"}, {\"url\": \"https://www.bea.gov/news/2024/gross-domestic-product-state-and-personal-income-state-4th-quarter-2023-and-preliminary\", \"content\": \"Real gross domestic product (GDP) increased in all 50 states and the District of Columbia in the fourth quarter of 2023, with the percent change ranging from 6.7 percent in Nevada to 0.2 percent in Nebraska (table 1), according to statistics released today by the U.S. Bureau of Economic Analysis (BEA). Current-dollar GDP increased in 49 states and the District of Columbia.\"}, {\"url\": \"https://www.bea.gov/news/2023/gross-domestic-product-state-and-personal-income-state-1st-quarter-2023\", \"content\": \"Real gross domestic product (GDP) increased in all 50 states and the District of Columbia in the first quarter of 2023, with the percent change ranging from 12.4 percent in North Dakota to 0.1 percent in Rhode Island and Alabama (table 1), according to statistics released today by the U.S. Bureau of Economic Analysis (BEA). Current-dollar GDP increased in 47 states and the District of Columbia\"}]', name='tavily_search_results_json', id='77de7955-ba39-4db3-9460-1a2bd7f602fb', tool_call_id='toolu_015fdnpWUiuEshsEwn2nBJ1g', artifact={'query': 'latest GDP of California state 2023', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'title': \"California Remains the World's 5th Largest Economy\", 'url': 'https://www.gov.ca.gov/2024/04/16/california-remains-the-worlds-5th-largest-economy/', 'content': 'California remains the 5th largest economy in the world since 2017. California is the 5th largest economy in the world for the seventh consecutive year, with a nominal GDP of nearly $3.9 trillion in 2023 and a growth rate of 6.1% since the year prior, according to the U.S. Bureau of Economic Analysis (BEA). On a per capita basis, California is', 'score': 0.99338466, 'raw_content': None}, {'title': 'Gross domestic product (GDP) - USAFacts', 'url': 'https://usafacts.org/metrics/gross-domestic-product-gdp-by-state-california/', 'content': 'USAFacts -- In 2022 (most recent), Gross domestic product (GDP) was 3598102700000.0 in the United States for California (state). This increased by 224,862,000,000 or 6.67% from 2021. Highest: 3,598,102,700,000 in 2022.', 'score': 0.99128854, 'raw_content': None}, {'title': 'Real GDP California U.S. 2023 | Statista', 'url': 'https://www.statista.com/statistics/187834/gdp-of-the-us-federal-state-of-california-since-1997/', 'content': 'Industry Overview\\nDigital & Trend reports\\nOverview and forecasts on trending topics\\nIndustry & Market reports\\nIndustry and market insights and forecasts\\nCompanies & Products reports\\nKey figures and rankings about companies and products\\nConsumer & Brand reports\\nConsumer and brand insights and preferences in various industries\\nPolitics & Society reports\\nDetailed information about political and social topics\\nCountry & Region reports\\nAll key figures about countries and regions\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\nInsights on consumer attitudes and behavior worldwide\\nBusiness information on 100m+ public and private companies\\nExplore Company Insights\\nDetailed information for 39,000+ online stores and marketplaces\\nDirectly accessible data for 170 industries from 150+ countries\\nand over 1\\xa0Mio. facts.\\n Statistics on\\n\"\\nCalifornia\\n\"\\nOther statistics that may interest you California\\nPopulation\\nEconomy\\nEmployment & Earnings\\nState & Local Government\\nMetro Areas\\nFurther related statistics\\nFurther Content: You might find this interesting as well\\nStatistics\\nTopics Other statistics on the topicCalifornia\\nEconomy\\nU.S. leading companies headquartered in California 2023, by number of employees\\nEconomy\\nU.S. average annual wages in California 2018-2026\\nEconomy\\nU.S. California fastest growing private companies 2023, by three year growth rate\\nResidential Real Estate\\nHourly wages needed to afford a two-bedroom apartment in California 2021-23, by metro\\nYou only have access to basic statistics.\\n Additional Information\\nShow sources information\\nShow publisher information\\nUse Ask Statista Research Service\\nMarch 2023\\nUnited States\\n2000 to 2022\\nData presented here is in 2012 chained U.S. dollars.\\n Transforming data into design:\\nStatista Content & Design\\nStrategy and business building for the data-driven economy:\\nU.S. real GDP of California 2000-2022\\nReal gross domestic product of California in the United States from 2000 to 2022\\n(in billion U.S. dollars)\\n', 'score': 0.58112484, 'raw_content': None}, {'title': 'Gross Domestic Product by State and Personal Income by State, 4th ...', 'url': 'https://www.bea.gov/news/2024/gross-domestic-product-state-and-personal-income-state-4th-quarter-2023-and-preliminary', 'content': 'Real gross domestic product (GDP) increased in all 50 states and the District of Columbia in the fourth quarter of 2023, with the percent change ranging from 6.7 percent in Nevada to 0.2 percent in Nebraska (table 1), according to statistics released today by the U.S. Bureau of Economic Analysis (BEA). Current-dollar GDP increased in 49 states and the District of Columbia.', 'score': 0.5455884, 'raw_content': None}, {'title': 'Gross Domestic Product by State and Personal Income by State, 1st ...', 'url': 'https://www.bea.gov/news/2023/gross-domestic-product-state-and-personal-income-state-1st-quarter-2023', 'content': 'Real gross domestic product (GDP) increased in all 50 states and the District of Columbia in the first quarter of 2023, with the percent change ranging from 12.4 percent in North Dakota to 0.1 percent in Rhode Island and Alabama (table 1), according to statistics released today by the U.S. Bureau of Economic Analysis (BEA). Current-dollar GDP increased in 47 states and the District of Columbia', 'score': 0.40857172, 'raw_content': None}], 'response_time': 3.04})]}})\n", + "----\n", + "(('researcher:7daea379-a5b6-6d3d-ef85-fffc96d7472e',), {'agent': {'messages': [AIMessage(content=\"Based on the search results, I can provide you with the latest GDP figures for both states:\\n\\nNew York:\\n- GDP: $2.053 trillion (2022 figures)\\n\\nCalifornia:\\n- GDP: $3.9 trillion (2023 figures)\\n\\nAs instructed, I won't calculate the average, but I've provided you with the most recent GDP figures for both states. Note that the figures are from different years (2022 for NY and 2023 for CA), which should be considered when calculating the average.\", additional_kwargs={}, response_metadata={'id': 'msg_013yuy2PoBUNSGNDCYXvUL27', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 3748, 'output_tokens': 120}}, id='run-d775159b-c114-4db5-b425-416f0f2f957c-0', usage_metadata={'input_tokens': 3748, 'output_tokens': 120, 'total_tokens': 3868, 'input_token_details': {}})]}})\n", + "----\n", + "((), {'researcher': {'messages': [HumanMessage(content=\"Based on the search results, I can provide you with the latest GDP figures for both states:\\n\\nNew York:\\n- GDP: $2.053 trillion (2022 figures)\\n\\nCalifornia:\\n- GDP: $3.9 trillion (2023 figures)\\n\\nAs instructed, I won't calculate the average, but I've provided you with the most recent GDP figures for both states. Note that the figures are from different years (2022 for NY and 2023 for CA), which should be considered when calculating the average.\", additional_kwargs={}, response_metadata={}, name='researcher')]}})\n", + "----\n", + "((), {'supervisor': {'next': 'coder'}})\n", + "----\n", + "(('coder:2c47a596-d75b-143e-9b4a-a99f78779aec',), {'agent': {'messages': [AIMessage(content=[{'text': \"I'll help calculate the average GDP between New York ($2.053 trillion) and California ($3.9 trillion).\", 'type': 'text'}, {'id': 'toolu_019yGU4aBc9H73jfRWr1iKtf', 'input': {'code': 'ny_gdp = 2.053\\nca_gdp = 3.9\\n\\naverage_gdp = (ny_gdp + ca_gdp) / 2\\n\\nprint(f\"New York GDP: ${ny_gdp} trillion\")\\nprint(f\"California GDP: ${ca_gdp} trillion\")\\nprint(f\"Average GDP: ${average_gdp:.3f} trillion\")'}, 'name': 'python_repl_tool', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01PGbexsWGaf4LQbKDH8toJs', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 558, 'output_tokens': 175}}, id='run-355dc875-adcc-436c-b0f3-675dc1c099bd-0', tool_calls=[{'name': 'python_repl_tool', 'args': {'code': 'ny_gdp = 2.053\\nca_gdp = 3.9\\n\\naverage_gdp = (ny_gdp + ca_gdp) / 2\\n\\nprint(f\"New York GDP: ${ny_gdp} trillion\")\\nprint(f\"California GDP: ${ca_gdp} trillion\")\\nprint(f\"Average GDP: ${average_gdp:.3f} trillion\")'}, 'id': 'toolu_019yGU4aBc9H73jfRWr1iKtf', 'type': 'tool_call'}], usage_metadata={'input_tokens': 558, 'output_tokens': 175, 'total_tokens': 733, 'input_token_details': {}})]}})\n", + "----\n", + "(('coder:2c47a596-d75b-143e-9b4a-a99f78779aec',), {'tools': {'messages': [ToolMessage(content='Successfully executed:\\n```python\\nny_gdp = 2.053\\nca_gdp = 3.9\\n\\naverage_gdp = (ny_gdp + ca_gdp) / 2\\n\\nprint(f\"New York GDP: ${ny_gdp} trillion\")\\nprint(f\"California GDP: ${ca_gdp} trillion\")\\nprint(f\"Average GDP: ${average_gdp:.3f} trillion\")\\n```\\nStdout: New York GDP: $2.053 trillion\\nCalifornia GDP: $3.9 trillion\\nAverage GDP: $2.976 trillion\\n', name='python_repl_tool', id='39106042-eb3e-485c-8d62-bb2f572fbf8b', tool_call_id='toolu_019yGU4aBc9H73jfRWr1iKtf')]}})\n", + "----\n", + "(('coder:2c47a596-d75b-143e-9b4a-a99f78779aec',), {'agent': {'messages': [AIMessage(content=\"Based on the calculations:\\n- New York's GDP: $2.053 trillion (2022)\\n- California's GDP: $3.9 trillion (2023)\\n- The average GDP between the two states is $2.976 trillion\\n\\nNote: As mentioned earlier, these GDP figures are from different years (2022 for NY and 2023 for CA), which should be taken into consideration when interpreting the average.\", additional_kwargs={}, response_metadata={'id': 'msg_016GGgrPRH3psSoWUw7TzTQu', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 877, 'output_tokens': 98}}, id='run-55436cea-0fab-4fa3-a691-3a80a0afb431-0', usage_metadata={'input_tokens': 877, 'output_tokens': 98, 'total_tokens': 975, 'input_token_details': {}})]}})\n", + "----\n", + "((), {'coder': {'messages': [HumanMessage(content=\"Based on the calculations:\\n- New York's GDP: $2.053 trillion (2022)\\n- California's GDP: $3.9 trillion (2023)\\n- The average GDP between the two states is $2.976 trillion\\n\\nNote: As mentioned earlier, these GDP figures are from different years (2022 for NY and 2023 for CA), which should be taken into consideration when interpreting the average.\", additional_kwargs={}, response_metadata={}, name='coder')]}})\n", + "----\n", + "((), {'supervisor': {'next': '__end__'}})\n", "----\n" ] } @@ -317,46 +418,16 @@ "for s in graph.stream(\n", " {\n", " \"messages\": [\n", - " HumanMessage(content=\"Code hello world and print it to the terminal\")\n", + " (\n", + " \"user\",\n", + " \"Find the latest GDP of New York and California, then calculate the average\",\n", + " )\n", " ]\n", - " }\n", + " },\n", + " subgraphs=True,\n", "):\n", - " if \"__end__\" not in s:\n", - " print(s)\n", - " print(\"----\")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "45a92dfd-0e11-47f5-aad4-b68d24990e34", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'supervisor': {'next': 'Researcher'}}\n", - "----\n", - "{'Researcher': {'messages': [HumanMessage(content='### Research Report on Pikas\\n\\n#### Introduction\\nPikas are small, herbivorous mammals belonging to the family Ochotonidae, closely related to rabbits and hares. These animals are known for their distinctive high-pitched calls and are often found in cold, mountainous regions across Asia, North America, and parts of Europe.\\n\\n#### Habitat and Behavior\\nPikas primarily inhabit talus slopes and alpine meadows, often at elevations ranging from 2,500 to over 13,000 feet. These environments provide the necessary rock crevices and vegetation required for their survival. Pikas are diurnal and exhibit two main foraging behaviors: direct consumption of plants and the collection of vegetation into \"haypiles\" for winter storage. Unlike many small mammals, pikas do not hibernate and remain active throughout the winter, relying on these haypiles for sustenance.\\n\\n#### Diet and Feeding Habits\\nPikas are generalist herbivores, feeding on a variety of grasses, forbs, and small shrubs. They have a highly developed behavior known as \"haying,\" where they collect and store plant material during the summer months to ensure a food supply during the harsh winter. This behavior is crucial for their survival, as the stored hay provides the necessary nutrients when fresh vegetation is scarce.\\n\\n#### Reproduction and Lifecycle\\nPikas have a relatively short lifespan, averaging around three years. They typically breed once or twice a year, with a gestation period of roughly 30 days. Females usually give birth to litters of two to six young. The young are weaned and become independent within a month, reaching sexual maturity by the following spring.\\n\\n#### Conservation Status\\nThe conservation status of pikas varies by region and species. The American pika (Ochotona princeps), found in the mountains of western North America, is particularly vulnerable to climate change. Rising temperatures and reduced snowpack threaten their habitat, forcing pikas to move to higher elevations or face local extirpation. Despite these challenges, the American pika is not currently listed under the US Endangered Species Act, although several studies indicate localized population declines.\\n\\n#### Conclusion\\nPikas are fascinating creatures that play a vital role in their alpine ecosystems. Their unique behaviors, such as haying, and their sensitivity to climate change make them important indicators of environmental health. Continued research and conservation efforts are essential to ensure the survival of these small but significant mammals in the face of global climatic shifts.\\n\\n#### References\\n1. Wikipedia - Pika: [Link](https://en.wikipedia.org/wiki/Pika)\\n2. Wikipedia - American Pika: [Link](https://en.wikipedia.org/wiki/American_pika)\\n3. Animal Spot - American Pika: [Link](https://www.animalspot.net/american-pika.html)\\n4. Animalia - American Pika: [Link](https://animalia.bio/index.php/american-pika)\\n5. National Park Service - Pikas Resource Brief: [Link](https://www.nps.gov/articles/pikas-brief.htm)\\n6. Alaska Department of Fish and Game - Pikas: [Link](https://www.adfg.alaska.gov/static/education/wns/pikas.pdf)\\n7. NatureMapping Foundation - American Pika: [Link](http://naturemappingfoundation.org/natmap/facts/american_pika_712.html)\\n8. USDA Forest Service - Conservation Status of Pikas: [Link](https://www.fs.usda.gov/psw/publications/millar/psw_2022_millar002.pdf)', additional_kwargs={}, response_metadata={}, name='Researcher')]}}\n", - "----\n", - "{'supervisor': {'next': 'Coder'}}\n", - "----\n", - "{'Coder': {'messages': [HumanMessage(content='### Research Report on Pikas\\n\\n#### Introduction\\nPikas are small, herbivorous mammals belonging to the family Ochotonidae, closely related to rabbits and hares. These animals are known for their distinctive high-pitched calls and are often found in cold, mountainous regions across Asia, North America, and parts of Europe.\\n\\n#### Habitat and Behavior\\nPikas primarily inhabit talus slopes and alpine meadows, often at elevations ranging from 2,500 to over 13,000 feet. These environments provide the necessary rock crevices and vegetation required for their survival. Pikas are diurnal and exhibit two main foraging behaviors: direct consumption of plants and the collection of vegetation into \"haypiles\" for winter storage. Unlike many small mammals, pikas do not hibernate and remain active throughout the winter, relying on these haypiles for sustenance.\\n\\n#### Diet and Feeding Habits\\nPikas are generalist herbivores, feeding on a variety of grasses, forbs, and small shrubs. They have a highly developed behavior known as \"haying,\" where they collect and store plant material during the summer months to ensure a food supply during the harsh winter. This behavior is crucial for their survival, as the stored hay provides the necessary nutrients when fresh vegetation is scarce.\\n\\n#### Reproduction and Lifecycle\\nPikas have a relatively short lifespan, averaging around three years. They typically breed once or twice a year, with a gestation period of roughly 30 days. Females usually give birth to litters of two to six young. The young are weaned and become independent within a month, reaching sexual maturity by the following spring.\\n\\n#### Conservation Status\\nThe conservation status of pikas varies by region and species. The American pika (Ochotona princeps), found in the mountains of western North America, is particularly vulnerable to climate change. Rising temperatures and reduced snowpack threaten their habitat, forcing pikas to move to higher elevations or face local extirpation. Despite these challenges, the American pika is not currently listed under the US Endangered Species Act, although several studies indicate localized population declines.\\n\\n#### Conclusion\\nPikas are fascinating creatures that play a vital role in their alpine ecosystems. Their unique behaviors, such as haying, and their sensitivity to climate change make them important indicators of environmental health. Continued research and conservation efforts are essential to ensure the survival of these small but significant mammals in the face of global climatic shifts.\\n\\n#### References\\n1. Wikipedia - Pika: [Link](https://en.wikipedia.org/wiki/Pika)\\n2. Wikipedia - American Pika: [Link](https://en.wikipedia.org/wiki/American_pika)\\n3. Animal Spot - American Pika: [Link](https://www.animalspot.net/american-pika.html)\\n4. Animalia - American Pika: [Link](https://animalia.bio/index.php/american-pika)\\n5. National Park Service - Pikas Resource Brief: [Link](https://www.nps.gov/articles/pikas-brief.htm)\\n6. Alaska Department of Fish and Game - Pikas: [Link](https://www.adfg.alaska.gov/static/education/wns/pikas.pdf)\\n7. NatureMapping Foundation - American Pika: [Link](http://naturemappingfoundation.org/natmap/facts/american_pika_712.html)\\n8. USDA Forest Service - Conservation Status of Pikas: [Link](https://www.fs.usda.gov/psw/publications/millar/psw_2022_millar002.pdf)', additional_kwargs={}, response_metadata={}, name='Coder')]}}\n", - "----\n", - "{'supervisor': {'next': 'FINISH'}}\n", - "----\n" - ] - } - ], - "source": [ - "for s in graph.stream(\n", - " {\"messages\": [HumanMessage(content=\"Write a brief research report on pikas.\")]},\n", - " {\"recursion_limit\": 100},\n", - "):\n", - " if \"__end__\" not in s:\n", - " print(s)\n", - " print(\"----\")" + " print(s)\n", + " print(\"----\")" ] } ], @@ -376,7 +447,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/docs/docs/tutorials/multi_agent/hierarchical_agent_teams.ipynb b/docs/docs/tutorials/multi_agent/hierarchical_agent_teams.ipynb index c923976f0..f96e55c39 100644 --- a/docs/docs/tutorials/multi_agent/hierarchical_agent_teams.ipynb +++ b/docs/docs/tutorials/multi_agent/hierarchical_agent_teams.ipynb @@ -2,8 +2,8 @@ "cells": [ { "attachments": { - "50a6ed47-ace3-428e-8dcf-a13ec56c11d6.png": { - "image/png": "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" + "d98ed25c-51cb-441f-a6f4-016921d59fc3.png": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABaIAAAP8CAIAAAA7q14UAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAFoqADAAQAAAABAAAD/AAAAADsXbjNAABAAElEQVR4AezdecAkRX03cFfwDhgVFRBY1JgoEI1HfPbxgnjiAV7AridiNB4R3UVNBPGViEcC6q6Jxmg8Iom6C14QQRAUJcrugxfegKKugopX5BDEa9+vlGmGeY6dZ545uns+zx9rT091ddWn5nmkf/OrqmVbtmy5nh8CBAgQIECAAAECBAgQIECAQPMFrt/8LugBAQIECBAgQIAAAQIECBAgQOD3AsIcPgcECBAgQIAAAQIECBAgQIBASwSEOVoykLpBgAABAgQIECBAgAABAgQICHP4DBAgQIAAAQIECBAgQIAAAQItERDmaMlA6gYBAgQIECBAgAABAgQIECAgzOEzQIAAAQIECBAgQIAAAQIECLREQJijJQOpGwQIECBAgAABAgQIECBAgIAwh88AAQIECBAgQIAAAQIECBAg0BIBYY6WDKRuECBAgAABAgQIECBAgAABAsIcPgMECBAgQIAAAQIECBAgQIBASwSEOVoykLpBgAABAgQIECBAgAABAgQICHP4DBAgQIAAAQIECBAgQIAAAQItERDmaMlA6gYBAgQIECBAgAABAgQIECAgzOEzQIAAAQIECBAgQIAAAQIECLREQJijJQOpGwQIECBAgAABAgQIECBAgIAwh88AAQIECBAgQIAAAQIECBAg0BIBYY6WDKRuECBAgAABAgQIECBAgAABAsIcPgMECBAgQIAAAQIECBAgQIBASwSEOVoykLpBgAABAgQIECBAgAABAgQICHP4DBAgQIAAAQIECBAgQIAAAQItERDmaMlA6gYBAgQIECBAgAABAgQIECAgzOEzQIAAAQIECBAgQIAAAQIECLREQJijJQOpGwQIECBAgAABAgQIECBAgIAwh88AAQIECBAgQIAAAQIECBAg0BIBYY6WDKRuECBAgAABAgQIECBAgAABAsIcPgMECBAgQIAAAQIECBAgQIBASwSEOVoykLpBgAABAgQIECBAgAABAgQICHP4DBAgQIAAAQIECBAgQIAAAQItERDmaMlA6gYBAgQIECBAgAABAgQIECAgzOEzQIAAAQIECBAgQIAAAQIECLREQJijJQOpGwQIECBAgAABAgQIECBAgIAwh88AAQIECBAgQIAAAQIECBAg0BIBYY6WDKRuECBAgAABAgQIECBAgAABAsIcPgMECBAgQIAAAQIECBAgQIBASwSEOVoykLpBgAABAgQIECBAgAABAgQICHP4DBAgQIAAAQIECBAgQIAAAQItERDmaMlA6gYBAgQIECBAgAABAgQIECAgzOEzQIAAAQIECBAgQIAAAQIECLREQJijJQOpGwQIECBAgAABAgQIECBAgIAwh88AAQIECBAgQIAAAQIECBAg0BIBYY6WDKRuECBAgAABAgQIECBAgAABAsIcPgMECBAgQIAAAQIECBAgQIBASwSEOVoykLpBgAABAgQIECBAgAABAgQICHP4DBAgQIAAAQIECBAgQIAAAQItERDmaMlA6gYBAgQIECBAgAABAgQIECAgzOEzQIAAAQIECBAgQIAAAQIECLREQJijJQOpGwQIECBAgAABAgQIECBAgIAwh88AAQIECBAgQIAAAQIECBAg0BIBYY6WDKRuECBAgAABAgQIECBAgAABAsIcPgMECBAgQIAAAQIECBAgQIBASwSEOVoykLpBgAABAgQIECBAgAABAgQICHP4DBAgQIAAAQIECBAgQIAAAQItERDmaMlA6gYBAgQIECBAgAABAgQIECAgzOEzQIAAAQIECBAgQIAAAQIECLREQJijJQOpGwQIECBAgAABAgQIECBAgMC2CAgQIECAwCQIbNq0qXRz48aNLevvzMxM3z2amprq+1oXEhiXwPT0dG69YsWKcTXAfQkQIECgzgLLtmzZUuf2aRsBAgQIEOhPIHGNdevWlWsT2ijPRXmqLwf91Tn7quYGTZYSHJnt4EyjBcYS7Vrsb2L1u1Y+utXL1atXBz+1iXo0+kOo8QQIEBiggDDHADFVRYAAAQK1EOgMcJRHIM8/tRgYjSAwUIH8ppdgRxXQzO/7mjVrBnoTlREgQIBA8wSEOZo3ZlpMgAABAnMKlOhGHnvyvW6edoQ25lRykkArBdauXZt+lXiHYEcrh1inCBAg0LuAtTl6t1KSAAECBOorkIecPOHk8SY/Ahz1HSctIzAcgc4kjhLsMI1lONJqJUCAQAMEZHM0YJA0kQABAgQWFli1alUKrF+/fuFi3iVAYBIEStAzPd2wYYOg5ySMuD4SIECgS8CGsl0gXhIgQIBAkwQyUWX58uVZQFGMo0nDpq0EhimQzI7Nmzcnm2PlypX5EzHMW6mbAAECBOooIJujjqOiTQQIECDQi0C+s82eC2ap9GKlDIEJFEieVxbrkdMxgUOvywQITLiAbI4J/wDoPgECBJoqkC9pE+NIEoek9KYOoXYTGLJAYqC5Q7UPy5DvpnoCBAgQqIuAMEddRkI7CBAgQGBRAklHL88wi7pKYQIEJkcgMdCkciShw9SVyRl0PSVAgEAETFrxMSBAgACB5gmUzSM791ZoXh+0mACBkQhYongkzG5CgACBGgkIc9RoMDSFAAECBHoREOPoRUkZAgQqgSxUnOQvgdEKxAEBAgTaLSDM0e7x1TsCBAi0TSDJ55mukm0U2tYx/SFAYGgCZYtZfzeGBqxiAgQI1EvA2hz1Gg+tIUCAAIGFBTLN3pIcCxN5lwCBLoGSx2GFji4WLwkQINBWAdkcbR1Z/SJAgEA7BZJ87ivZdg6tXhEYpoAVOoapq24CBAjUS0A2R73GQ2sIECBAYAGBZJ5L5VjAx1sECMwn4E/HfDLOEyBAoH0CwhztG1M9IkCAQGsF1q1b19q+6RgBAkMWyJS3Id9B9QQIECBQCwFhjloMg0YQIECAwFYFyrx6eyVsFUoBAgRmC6xYsWJ6etryHLNlnCFAgED7BIQ52jemekSAAIF2Clh8tJ3jqlcERiggI2yE2G5FgACBsQkIc4yN3o0JECBAYFECMzMz+TJ2UZcoTIAAgUrA8hwVhQMCBAi0W0CYo93jq3cECBBoj4B59e0ZSz0hQIAAAQIECAxNQJhjaLQqJkCAAIFBC2R2/aCrVB8BApMi4A/IpIy0fhIgMPECwhwT/xEAQIAAgSYIZOFAM1aaMFDaSKDWApLCaj08GkeAAIEBCQhzDAhSNQQIECAwZIGpqakh30H1BAgQIECAAAECjRcQ5mj8EOoAAQIEJkHAd7CTMMr6SGDYApLChi2sfgIECNRBQJijDqOgDQQIECBAgAABAgQIECBAgMAABIQ5BoCoCgIECBAYgYCvYUeA7BYEWi+QhX5a30cdJECAwIQLCHNM+AdA9wkQIECAAAECBAgQIECAQHsEhDnaM5Z6QoAAAQIECBAgQIAAAQIEJlxAmGPCPwC6T4AAAQIECBAgQIAAAQIE2iMgzNGesdQTAgQIECBAgAABAgQIECAw4QLCHBP+AdB9AgQIECBAgAABAgQIECDQHgFhjvaMpZ4QIECAAAECBAgQIECAAIEJFxDmmPAPgO4TIECAAAECBAgQIECAAIH2CAhztGcs9YQAAQIECBAgQIAAAQIECEy4gDDHhH8AdJ8AAQIECBAgQIAAAQIECLRHQJijPWOpJwQIEGi3wMaNG9vdQb0jQIAAAQIECBBYuoAwx9IN1UCAAAECBAgQIECAAAECBAjUQkCYoxbDoBEECBAgQIAAAQIECBAgQIDA0gWEOZZuqAYCBAgQIECAAAECBAgQIECgFgLCHLUYBo0gQIAAAQIECBAgQIAAAQIEli4gzLF0QzUQIECAAAECBAgQIECAAAECtRAQ5qjFMGgEAQIECBAgQIAAAQIECBAgsHQBYY6lG6qBAAECBAgQIECAAAECBAgQqIWAMEcthkEjCBAgQIAAAQIECBAgQIAAgaULCHMs3VANBAgQIECAAAECBAgQIECAQC0EhDlqMQwaQYAAAQIECBAgQIAAAQIECCxdQJhj6YZqIECAAAECBAgQIECAAAECBGohIMxRi2HQCAIECBAgQIAAAQIECBAgQGDpAsIcSzdUAwECBAgQIECAAAECBAgQIFALAWGOWgyDRhAgQIAAAQIECBAgQIAAAQJLFxDmWLqhGggQIECAAAECBAgQIECAAIFaCAhz1GIYNIIAAQIEtiowMzOz1TIKECBAgAABAgQITLiAMMeEfwB0nwABAgQIECBAgAABAgQItEdAmKM9Y6knBAgQIECAAAECBAgQIEBgwgWEOSb8A6D7BAgQIECAAAECBAgQIECgPQLCHO0ZSz0hQIAAAQIECBBYWGDFihULF/AuAQIECDRdQJij6SOo/QQIECBAYGACW7ZsOf7441/96lf/8pe/XKDSX/3qV695zWvWrVv3wx/+cIFi3iJAgAABAgQIjF5g29Hf0h0JECBAgACBegqcfvrpL37xi9O2HXfc8elPf/p8jTzzzDP/7d/+Le9m+5v3vve98xVzngABAgQIECAwegHZHKM3d0cCBAgQIFBTga985SulZT/60Y8WaOIuu+xS3j377LOT2bFASW8RIECAAAECBEYsIMwxYnC3I0CAAAEC9RU45ZRTSuP222+/BVq55557Vu9effXV1bEDAgQIECBAgMDYBYQ5xj4EGkCAAAECBGoh8M1vfvMb3/hGmrLXXnt1BjJmN64zg+Oyyy6bXSBnsszH97///Ysuuug3v/nNnAWcJECAAAECBAgMQ8DaHMNQVScBAgQIEGiewKmnnloafdBBBy3c+iuuuKIqcIMb3KA6zkECJR/96Ec//vGPf/azny3nd9tttw9+8IM77LBDZ7E+jn/yk598+tOfTugkAZQsHfJnf/ZnC8di+riFSwgQIECAAIEWCAhztGAQdYEAAQIECAxAIEGEUsv97ne/hau79NJLqwK3utWtcnzJJZcktPFf//Vf1eoeVYHvfve7n//85x/60IdWZ7oOPve5z73xjW/84he/mO1dHvzgB++9996ZMnPDG96ws9gXvvCFxzzmMZ1ncpysk2OOOUawo4vFSwIECBAgMOECwhwT/gHQfQIECBAg8AeB888/vxwtX758YZSsPFoK7LTTTttss01mpiSK8Ytf/KLrqpvd7GYJguy88873v//9u94qL3/6058efvjhp512WvXuidf8nHTSSdnJ5SY3uUk5n4Y96UlPqspUBwmpPOIRjzjiiCOe+cxnXv/65uFWMA4IECBAgMBECwhzTPTw6zwBAgQIEKgEEnTIcSIX2267lf88OOOMM8pVJZPida97XWeM4173utcBBxzwV3/1V5laUlU+++DnP/95ghdf//rXZ7/1iU984oQTTnjqU59a3krKRlX/fe5znzve8Y5ZHCRtKA1+9atffcEFF6QNs+txhgABAgQIEJhAAV99TOCg6zIBAgQIEOgWqBYK3WoqRyIOmZ9Srl+xYkUOdt99987qEp5ImGPhGMdVV13VGeP4f//v/5133nmZ2/LEJz6xVPWRj3ykHGTOSxVVOfLII9/73ve+8pWvTOAjU13e/va3Z+GPFDvzzDM7G+CYAAECBAgQmGQBYY5JHn19J0CAAAECfxD43e9+V45udKMbLYySVIuqwD777JPjZz/72Z3TUp7//Oc/8IEPPPnkk6s6q/LVwbve9a5qFY8NGzb89V//daaoZIbLbW9721Km2szlk5/8ZDlzpzvd6ZBDDqlqWLZsWRbySIAj7Xn3u99dnXdAgAABAgQITLjAVrJSJ1xH9wkQIECAwIQIVEt+ZiuTBbp89dVXr127thS4y13uktBDjhMZSdgieRYvfelLy1tJwXjuc5+bJUJf+MIXZvZKQhKddWbPlNe85jXVmac//ekPe9jDbn3rW5911lnVHJZqGdSLL764lDz44INnz6bJmdvf/vZVVQ4IECBAgAABArI5fAYIECBAgACB3wvc7W53y7/ZEfbcc8+dTyQLYaRAeffQQw+timUh0ic/+cnZRPawww4re6/kreRrJP/iUY96VBYZ7czseMc73lFdmIPMgvnABz7wlre8pYpxZCrK3/zN35QyZQGOHG81zaSzTscECBAgQIDAxAoIc0zs0Os4AQIECBC4jkB2LSmvX/SiF/3617++znvXu95vf/vbY4899j/+4z/K+eRfVOWrksnIeMELXjAzM/PWt741qRzlfIIdiVlkO9gqYLF+/fryVia2JCySDVmqGnKQTVs+9KEPVScvv/zyzncdEyBAgAABAgQWFhDmWNjHuwQIECBAYFIE9t9//9LV5Gs8/vGPr7I2sjpp1r846KCD3vjGN5YCma6SnU26pqJ873vfKykbN7jBDRIE+fCHP/zOd76zZIjkqi9+8YuJdGRGzKWXXlriHXkroZCERT7zmc8cf/zxiaFk5ss555zz7//+71U+SC7MNJly0x//+MflwL8ECBAgQIAAgQUEhDkWwPEWAQIECBCYIIGdd9758MMPLx1OVCILfN7jHvdI7OOud71r1sXIhJTyVsITScfYbrvtOmmygkZW08hWsqeccko5nyBIFiI98cQTM0WlLOGRBTuyeEe1New3v/nN7LeSwkncmJqaShglC5pWS5BWlVd5JZs3b65OOiBAgAABAgQIzCcgzDGfjPMECBAgQGDiBJ7xjGcceOCBVbeTdpF4RxWYyPnsFJuNUf74j/+4KlMOksqRg5R/znOe84QnPOFjH/tYzlx22WW5NkuEZnmOUiw70d7ylrcsx3mra5GOcn6+fy+44IL53nKeAAECBAgQIFAJ2GmlonBAgAABAgQmXSAbl2TyyL3vfe83v/nN3/rWtyqOO9zhDknNSMLFn/3Zn1UnOw+Sr7HTTjv94Ac/yMmzr/npfLc6TpncYs2aNWW7lmOOOSbLeaTaqkB1kA1lk77xv//7v1mq43/+539yvtp7pSrjgAABAgQIECAwW2DZli1bZp91hgABAgQI1EogT8VZ2LJaurJWbWtlY/KfB4lZXHnllendbW5zm+23336r3bzkkksSHMl6HAuUzAhOT09nrkp2mS0xkRTee++9s6dsAiiZCJPFO7785S8n6SNLe5R6XvnKV2beSjZ/ecUrXjE7i2SBe3mLwGyBVatW+TMym8UZAgQItExANkfLBlR3CBAgQIDAAASyskaW6lhURVlW46ijjnrWs571vve977zzzjv//PMTs8jJzFLJv/e85z0f/ehH77DDDqnzJje5ScpkgkzZQfaT1/zMd6+ENvbbb7/53nWeAAECBAgQINAlIMzRBeIlAQIECBAg0L9ApqUceuihW71+l112+cAHPpDtWt72trfNVzgrkj75yU9+yEMeMl8B5wkQIECAAAECswWEOWabOEOAAAECBAgMXeCmN73py172suc973mZn5LNa7MSR2bK7Lrrrrvvvnt2eMl+LlnFY+iNcAMCBAgQIECgdQL+A6J1Q6pDBAgQIECgOQK3uMUtnvKUpzSnvVpKgAABAgQI1F3AhrJ1HyHtI0CAAAECBAgQIECAAAECBHoUEOboEUoxAgQIECBAgAABAgQIECBAoO4Cwhx1HyHtI0CAAAECBAgQIECAAAECBHoUEOboEUoxAgQIECBAgAABAgQIECBAoO4Cwhx1HyHtI0CAAAECBAgQIECAAAECBHoUEOboEUoxAgQIECBAgAABAgQIECBAoO4Cwhx1HyHtI0CAAAECBAgQIECAAAECBHoUEOboEUoxAgQIECBAgAABAgQIECBAoO4Cwhx1HyHtI0CAAAECBAgQIECAAAECBHoUEOboEUoxAgQIECBAgAABAgQIECBAoO4Cwhx1HyHtI0CAAIEiMDU1hYIAAQIECBAgQIDAwgLCHAv7eJcAAQIECBAgQIAAAQIECBBojIAwR2OGSkMJECBAgAABAgQIECBAgACBhQWEORb28S4BAgQIECBAgAABAgQIECDQGAFhjsYMlYYSIECAAAECBAgQIECAAAECCwsIcyzs410CBAgQIECAAAECBAgQIECgMQLCHI0ZKg0lQIAAAQIECBAgQIAAAQIEFhYQ5ljYx7sECBAgUBeB6enpujRFOwgQIECAAAECBOoqIMxR15HRLgIECBAgQIAAgUELbNq0adBVqo8AAQIE6iUgzFGv8dAaAgQIECBAgAABAgQIECBAoG8BYY6+6VxIgAABAgQIECBAgAABAgQI1EtAmKNe46E1BAgQIDCfwMaNG+d7y3kCBAj0KLBixYoeSypG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4alPfWoru6lTBAgQIECAQJ0FhDnqPDraRoAAgVELNHfL2EyuGTVWx/2yAMff/M3f/OIXv+g4d7299trrGc94xiMe8Ygb3ehGnedbfHzb29626t3LXvayu9zlLn/5l39ZnXFAgAABAgQIEBiBgDDHCJDdggABAg0QsNroUgbp/e9/f2eM4+CDD86yJnvuuedS6qzPtenapz/96XTndre73cKtuuMd73jGGWf8+7//+4c//OFclcyO+cIcV1555Q9/+MNkfExCksvCaN4lQIAAAQIEBisgzDFYT7URIECgkQIlicNqo30P3k1vetPOax/4wAc2LsZx4YUXfuYzn/nxj3984xvfeMcdd7znPe+58847p1PZLCaLiWRF1cy++eQnP7nddtt19rQ6Pvroo/Pu3/3d3z30oQ895phjXv3qV3//+99PFKMqkIOENv7nf/4nu+pmds8PfvCD8tbLX/7ypz/96Z3F+juer/391eYqAgQIECBAoLkCwhzNHTstJ0CAwGAEmjtRZTD9H0Qtf/3Xf/3BD36wSuhINkee9p/73Ofe/e53H0T116kjKRIJKPz2t7+9613v+ld/9VfzxR3KNSn23//93+94xzu++c1vJk6R8gnB7LPPPtep8XrXO+644zLHpOvk/vvvnxhE1hxJjCNvZfHUL37xi/e73/26iuXlF77whSxKkoO3vOUt6XgOtt122912262UTKDkc5/7XPI7cpdypvPf97znPfOFOS666KKPf/zjCZek/K1vfet73eted7vb3TqvrY4XaH9XqKW6xAEBAgQIECDQVgFhjraOrH4RIEBg6wJWG926UW8l7nCHO5x88slHHHFEWYAzF330mp88mT/72c9OZGGbbbbpqumrX/1qgiMJPWT9jsc+9rFd7+bl5Zdf/pOf/OT2t7999VY2anne856XhIjqTA7+/u///mlPe1pXOkkp8OUvf/nFL37x17/+9fIyUZh3XfOzZs2a1atXV5UkQDM7xpF3TzrppI997GOvetWrqpIl4lC9rA5e97rXleO/+Iu/qE5WB//4j/+YaSzVy+ogfb/lLW+Z9VmrM9VB5rPkqjSsOlMO4vzkJz/5iU984k1ucpPqrYXb/6//+q+zwzrVtQ4IECBAgACB9gks27JlS/t6pUcECBAgsFWBliVxZAfc9evXb7XXwy6Q3WTf/OY3V8GOcrs8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QkAICAEhIASEQGIIiOZIrEEHro7mDANDpgeEgBAQAkJACAgBISAEhIAQEAJCIFYERHPE2jIVyiXHHDnBFlA5gVIyISAEhIAQEAJFIXDdddeddNJJ3//+9++9996i8lQ+MSDw3e9+95hjjtF6WwxtIRmEQHoIrJ1elVSjgRDQabIDwaXEQkAICAEhIASEQJUIvPe9773tttvOOOMMCn3Na16z55577rzzzuuvv36VMqRd1kMPPXTppZc+5SlPmTZtWmU1/dGPfnTEEUdQ3Ne+9rVf/epXa6890pTkr3/963//93+/8IUv/Od//ufKqqCChIAQiBkBWXPE3DqSTQgUgMDvfve7hQsXXn/99QXkpSyEgBAQAkJACPRFoPBzGfbee28v8MILL5wzZ87kyZOPPfbYW2+91eMVGAWBc889961vfesBBxzwpz/9aZR8Bnr2yiuvHCh9/8Tvf//7586d+653vat/Mt0VAkKgPQiI5mhPW3evqWwFu+OSSuwf/vCHf/3Xf2Xs32uvvQ4//PD58+f/4he/uOuuu1Kpn+rROgQKn0G1DkFVWAg0DYF3vOMdkBrYGoSCf/WrX919993f9KY3XXPNNWG8wkMg8OCDD9pTV1xxxRCPD/EIJZ5zzjn2IC07oikH+fztb3/j74033rhixYpe8pBGSz69wFG8EEgPAdEc6bXpwDWSy4n8kDVuirVs2TKv3UUXXfSRj3xk+vTp22233b/927+dfvrpv/3tb/2uAkJACAgBISAEIkRgrbXWestb3sL4+4lPfALiPpQQhx1veMMb3vjGN7KrJYxXeCAEnOa45557Bnpw6MRXXXWVW448/elPHzoff/Dhhx+2cJ8qYArEks9//dd/+VNVBo4//vitt96ansz+mirLVVlCoLUIjLQRrrWoJVNxnSabTFP2qogP/JkEaISnPPrbdddd0RF32223ddZZJ5NGlyECv/71rzfYYIPNN988jCwv/Pe///2+++574hOfiMb250d/q1evJobFqHHjxj3wwAObbbYZu9Mzy5vlyaOchYAQEAL1IrDhhhu+/vWvf8UrXrH99tsjyT/90z+xJdNE+uGjPywW8fWgr+IQzeSqwsqVK6E8ABZT0LvvvhsHKMD+3Oc+l8FoiGz7PPL1r3/d744fP97DQwecqUHybbbZhlWcO+64g7GS/rDppps+61nP8pwX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TJ2FU4wAAcQg2oIWQZvXShwjAHjkWWpYeTSNHOi8X6CBbCOmSitZ46kCSNMum8xx0NYodKDT1LRGd31HhEDmqhzU2tocI2p6Z2sEjEAfCGhJZvWBPiAbdlJt8iAAsdUDwYEShxVch43xWPPLf5tlrHA07GW0Pp9Tq2J1anaPjk7I1Du+4RyHGpfOzxTv+b3eXX0MtSviK2qaYww9wa8wAkagVwQknZPaAnqvkE07HZJfSsmb4Jg2orlkUIQUkgtYNSoH2lj2GTxle3p0TAlRzRJIurBoQbPyiWi4s55Offull1669dZbb7/99qeeemruuedeYIEFUIibc845O6VvcvzCCy+c/2gyzdHkLuq6G4FMEZAAqt1Iihi2LZkWt9hiBcHBRI4eh/Xbi23JjgW3ZN8Rmjre0IiG48DczLu1U7aw9vbzl9SnrIgTdEfA46KCDzSH9zMqmHD597//fbfddrv44osrt3bffffPfvazs846ayW+yZel0MT2zdHkXuq6G4FMEfin047/839wjE/5WH7DGeuTmmlxSysWMh9SDn8UHFlHxWdvB8xLq4rLOwUCtClNDGPoETQFUoXf1qCOc5HMcfTSnqCkWcajoxe4Ck0jMovCm/uLFmS6T1U4I77hgc9//vOtHAeYHHvssRtvvPH999/fcHyi+uINi5AYrc0RreaAETACmSLAJ5X9SSt3TLN5gJEcJNxIfYNLa3BME9UiHpdQQlGtqFxEe/VbSFbpGtdWTOgXOqUHQPh0728Phl7OT2loeFy0tpGovSJWqq2FH0XMbbfdtv766yvnN77xjWusscbrXve6++6777rrrlMkl+edd9473vGOUby9rDzZJCtFdDTNUVbXcmmNQKMR0MRsvqPfThCrXGYmfI/Fishbvv0iWXR6S/xFN1/bwmto221wW3D6ihSSpcjufVWtmYljaJjj6NQBWKya2gtw9t57b/R9uITj+PGPf/z6179etx5//HFEprPOOovLo446avPNN49HmhkoiyAzzdHMXupaG4GyEeA7qzNBmKSpiXck2jZnsBtCyexGW5QaFWmmozbNHaPb3kaH2KZlSfBDrHjNstKHzkeGdW9WfUOs4ieUll566T//+c+Ejz/++A022KAC3dNPP42BGwbUc801V+VWoy41sgqy/zLN0aj+6coagbohIKnU+h3RrgguhIPRYAkE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ETACxSJgmqPYpnPBjYARMAJGwAgYASNgBIyAETACRsAIGIEZETDNMSMevjICRsAIGAEjYASMgBEwAkbACBgBI2AEikXANEexTeeCGwEjYASMgBEwAkbACBgBI2AEjIARMAIzImCaY0Y8fGUEjIARMAJGwAgYASNgBIyAETACRsAIFIuAaY5im84FNwJGwAgYASNgBIyAETACRsAIGAEjYARmRMA0x4x4+MoIGAEjYASMgBEwAkbACBgBI2AEjIARKBYB0xzFNp0LbgSMgBEwAkbACBgBI2AEjIARMAJGwAjMiIBpjhnx8JURMAJGwAgYASNgBIyAETACRsAIGAEjUCwCpjmKbToX3AgYASNgBIyAETACRsAIGAEjYASMgBGYEQHTHDPi4SsjYASMgBEwAkbACBgBI2AEjIARMAJGoFgETHMU23QuuBEwAkbACBgBI2AEjIARMAJGwAgYASMwIwKmOWbEw1dGwAgYASNgBIyAETACRsAIGAEjYASMQLEImOYotulccCNgBIyAETACRsAIGAEjYASMgBEwAkZgRgRMc8yIh6+MgBEwAkbACBgBI2AEjIARMAJGwAgYgWIRMM1RbNO54EbACBgBI2AEjIARMAJGwAgYASNgBIzAjAiY5pgRD18ZASNgBIyAETACRsAIGAEjYASMgBEwAsUiYJqj2KZzwY2AETACRsAIGAEjYASMgBEwAkbACBiBGREwzTEjHr4yAkbACBgBI2AEjIARMAJGwAgYASNgBIpFwDRHsU3nghsBI2AEjIARMAJGwAgYASNgBIyAETACMyJgmmNGPHxlBIyAETACRsAIGAEjYASMgBEwAkbACBSLgGmOYpvOBTcCRsAIGAEjYASMgBEwAkbACBgBI2AEZkTANMeMePjKCBgBI2AEjIARMAJGwAgYASNgBIyAESgWAdMcxTadC24EjIARMAJGwAgYASNgBIyAETACRsAIzIiAaY4Z8fCVETACRsAIGAEjYASMgBEwAkbACBgBI1AsAqY5im06F9wIGAEjYASMgBEwAkbACBgBI2AEjIARmBEB0xwz4uErI2AEjIARMAJGwAgYASNgBIyAETACRqBYBExzFNt0/68dO7QBAABAGPb/1+idQFKJI8WhOAECBAgQIECAAAECBAgQIFABN0c9JAIECBAgQIAAAQIECBAgQOBWwM1xO53iBAgQIECAAAECBAgQIECAQAXcHPWQCBAgQIAAAQIECBAgQIAAgVsBN8ftdIoTIECAAAECBAgQIECAAAECFXBz1EMiQIAAAQIECBAgQIAAAQIEbgXcHLfTKU6AAAECBAgQIECAAAECBAhUwM1RD4kAAQIECBAgQIAAAQIECBC4FXBz3E6nOAECBAgQIECAAAECBAgQIFABN0c9JAIECBAgQIAAAQIECBAgQOBWwM1xO53iBAgQIECAAAECBAgQIECAQAXcHPWQCBAgQIAAAQIECBAgQIAAgVsBN8ftdIoTIECAAAECBAgQIECAAAECFXBz1EMiQIAAAQIECBAgQIAAAQIEbgXcHLfTKU6AAAECBAgQIECAAAECBAhUwM1RD4kAAQIECBAgQIAAAQIECBC4FXBz3E6nOAECBAgQIECAAAECBAgQIFABN0c9JAIECBAgQIAAAQIECBAgQOBWwM1xO53iBAgQIECAAAECBAgQIECAQAXcHPWQCBAgQIAAAQIECBAgQIAAgVsBN8ftdIoTIECAAAECBAgQIECAAAECFXBz1EMiQIAAAQIECBAgQIAAAQIEbgXcHLfTKU6AAAECBAgQIECAAAECBAhUwM1RD4kAAQIECBAgQIAAAQIECBC4FXBz3E6nOAECBAgQIECAAAECBAgQIFABN0c9JAIECBAgQIAAAQIECBAgQOBWwM1xO53iBAgQIECAAAECBAgQIECAQAXcHPWQCBAgQIAAAQIECBAgQIAAgVsBN8ftdIoTIECAAAECBAgQIECAAAECFXBz1EMiQIAAAQIECBAgQIAAAQIEbgXcHLfTKU6AAAECBAgQIECAAAECBAhUwM1RD4kAAQIECBAgQIAAAQIECBC4FXBz3E6nOAECBAgQIECAAAECBAgQIFABN0c9JAIECBAgQIAAAQIECBAgQOBWwM1xO53iBAgQIECAAAECBAgQIECAQAXcHPWQCBAgQIAAAQIECBAgQIAAgVsBN8ftdIoTIECAAAECBAgQIECAAAECFXBz1EMiQIAAAQIECBAgQIAAAQIEbgXcHLfTKU6AAAECBAgQIECAAAECBAhUwM1RD4kAAQIECBAgQIAAAQIECBC4FXBz3E6nOAECBAgQIECAAAECBAgQIFABN0c9JAIECBAgQIAAAQIECBAgQOBWwM1xO53iBAgQIECAAAECBAgQIECAQAXcHPWQCBAgQIAAAQIECBAgQIAAgVsBN8ftdIoTIECAAAECBAgQIECAAAECFXBz1EMiQIAAAQIECBAgQIAAAQIEbgXcHLfTKU6AAAECBAgQIECAAAECBAhUwM1RD4kAAQIECBAgQIAAAQIECBC4FXBz3E6nOAECBAgQIECAAAECBAgQIFABN0c9JAIECBAgQIAAAQIECBAgQOBWwM1xO53iBAgQIECAAAECBAgQIECAQAXcHPWQCBAgQIAAAQIECBAgQIAAgVsBN8ftdIoTIECAAAECBAgQIECAAAECFXBz1EMiQIAAAQIECBAgQIAAAQIEbgXcHLfTKU6AAAECBAgQIECAAAECBAhUwM1RD4kAAQIECBAgQIAAAQIECBC4FXBz3E6nOAECBAgQIECAAAECBAgQIFABN0c9JAIECBAgQIAAAQIECBAgQOBWwM1xO53iBAgQIECAAAECBAgQIECAQAXcHPWQCBAgQIAAAQIECBAgQIAAgVsBN8ftdIoTIECAAAECBAgQIECAAAECFXBz1EMiQIAAAQIECBAgQIAAAQIEbgXcHLfTKU6AAAECBAgQIECAAAECBAhUwM1RD4kAAQIECBAgQIAAAQIECBC4FXBz3E6nOAECBAgQIECAAAECBAgQIFABN0c9JAIECBAgQIAAAQIECBAgQOBWwM1xO53iBAgQIECAAAECBAgQIECAQAXcHPWQCBAgQIAAAQIECBAgQIAAgVuBAXdOmz2nfEvMAAAAAElFTkSuQmCC" } }, "cell_type": "markdown", @@ -12,7 +12,7 @@ "source": [ "# Hierarchical Agent Teams\n", "\n", - "In our previous example ([Agent Supervisor](../agent_supervisor)), we introduced the concept of a single supervisor node to route work between different worker nodes.\n", + "In our previous example ([Agent Supervisor](../agent_supervisor)), we introduced the concept of a single [supervisor node](https://langchain-ai.github.io/langgraph/concepts/multi_agent/#supervisor) to route work between different worker nodes.\n", "\n", "But what if the job for a single worker becomes too complex? What if the number of workers becomes too large?\n", "\n", @@ -22,7 +22,7 @@ "\n", "To do this, let's build a simple research assistant! The graph will look something like the following:\n", "\n", - "![diagram](attachment:50a6ed47-ace3-428e-8dcf-a13ec56c11d6.png)\n", + "![diagram](attachment:d98ed25c-51cb-441f-a6f4-016921d59fc3.png)\n", "\n", "This notebook is inspired by the paper [AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation](https://arxiv.org/abs/2308.08155), by Wu, et. al. In the rest of this notebook, you will:\n", "\n", @@ -49,12 +49,12 @@ "outputs": [], "source": [ "%%capture --no-stderr\n", - "%pip install -U langgraph langchain langchain_openai langchain_experimental" + "%pip install -U langgraph langchain_community langchain_anthropic langchain_experimental" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "30c2f3de-c730-4aec-85a6-af2c2f058803", "metadata": { "ExecuteTime": { @@ -108,7 +108,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "id": "4024eb89-843d-4cc3-ab3f-e1eb4d031179", "metadata": { "ExecuteTime": { @@ -155,7 +155,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 4, "id": "f20a18ca-2709-4c12-84f3-88678591a9fa", "metadata": { "ExecuteTime": { @@ -190,7 +190,7 @@ "\n", "@tool\n", "def read_document(\n", - " file_name: Annotated[str, \"File path to save the document.\"],\n", + " file_name: Annotated[str, \"File path to read the document from.\"],\n", " start: Annotated[Optional[int], \"The start line. Default is 0\"] = None,\n", " end: Annotated[Optional[int], \"The end line. Default is None\"] = None,\n", ") -> str:\n", @@ -246,7 +246,7 @@ "\n", "\n", "@tool\n", - "def python_repl(\n", + "def python_repl_tool(\n", " code: Annotated[str, \"The python code to execute to generate your chart.\"],\n", "):\n", " \"\"\"Use this to execute python code. If you want to see the output of a value,\n", @@ -275,7 +275,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 5, "id": "e09fb60f-1aac-455b-b67d-8d2e4ccfd747", "metadata": { "ExecuteTime": { @@ -285,68 +285,47 @@ }, "outputs": [], "source": [ - "from typing import List, Optional\n", - "from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser\n", - "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", - "from langchain_openai import ChatOpenAI\n", + "from typing import List, Optional, Literal\n", + "from langchain_core.language_models.chat_models import BaseChatModel\n", "\n", - "from langgraph.graph import END, StateGraph, START\n", + "from langgraph.graph import StateGraph, MessagesState, START, END\n", "from langchain_core.messages import HumanMessage, trim_messages\n", "\n", - "llm = ChatOpenAI(model=\"gpt-4o-mini\")\n", "\n", - "trimmer = trim_messages(\n", - " max_tokens=100000,\n", - " strategy=\"last\",\n", - " token_counter=llm,\n", - " include_system=True,\n", - ")\n", + "# The agent state is the input to each node in the graph\n", + "class AgentState(MessagesState):\n", + " # The 'next' field indicates where to route to next\n", + " next: str\n", "\n", "\n", - "def agent_node(state, agent, name):\n", - " result = agent.invoke(state)\n", - " return {\n", - " \"messages\": [HumanMessage(content=result[\"messages\"][-1].content, name=name)]\n", - " }\n", - "\n", - "\n", - "def create_team_supervisor(llm: ChatOpenAI, system_prompt, members) -> str:\n", - " \"\"\"An LLM-based router.\"\"\"\n", + "def make_supervisor_node(llm: BaseChatModel, members: list[str]) -> str:\n", " options = [\"FINISH\"] + members\n", - " function_def = {\n", - " \"name\": \"route\",\n", - " \"description\": \"Select the next role.\",\n", - " \"parameters\": {\n", - " \"title\": \"routeSchema\",\n", - " \"type\": \"object\",\n", - " \"properties\": {\n", - " \"next\": {\n", - " \"title\": \"Next\",\n", - " \"anyOf\": [\n", - " {\"enum\": options},\n", - " ],\n", - " },\n", - " },\n", - " \"required\": [\"next\"],\n", - " },\n", - " }\n", - " prompt = ChatPromptTemplate.from_messages(\n", - " [\n", - " (\"system\", system_prompt),\n", - " MessagesPlaceholder(variable_name=\"messages\"),\n", - " (\n", - " \"system\",\n", - " \"Given the conversation above, who should act next?\"\n", - " \" Or should we FINISH? Select one of: {options}\",\n", - " ),\n", - " ]\n", - " ).partial(options=str(options), team_members=\", \".join(members))\n", - " return (\n", - " prompt\n", - " | trimmer\n", - " | llm.bind_functions(functions=[function_def], function_call=\"route\")\n", - " | JsonOutputFunctionsParser()\n", - " )" + " system_prompt = (\n", + " \"You are a supervisor tasked with managing a conversation between the\"\n", + " f\" following workers: {members}. Given the following user request,\"\n", + " \" respond with the worker to act next. Each worker will perform a\"\n", + " \" task and respond with their results and status. When finished,\"\n", + " \" respond with FINISH.\"\n", + " )\n", + "\n", + " class Router(TypedDict):\n", + " \"\"\"Worker to route to next. If no workers needed, route to FINISH.\"\"\"\n", + "\n", + " next: Literal[*options]\n", + "\n", + " def supervisor_node(state: MessagesState) -> MessagesState:\n", + " \"\"\"An LLM-based router.\"\"\"\n", + " messages = [\n", + " {\"role\": \"system\", \"content\": system_prompt},\n", + " ] + state[\"messages\"]\n", + " response = llm.with_structured_output(Router).invoke(messages)\n", + " next_ = response[\"next\"]\n", + " if next_ == \"FINISH\":\n", + " next_ = END\n", + "\n", + " return {\"next\": next_}\n", + "\n", + " return supervisor_node" ] }, { @@ -365,7 +344,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 6, "id": "53db0c78-e357-48ba-ae5f-3fc04735a3b7", "metadata": { "ExecuteTime": { @@ -375,43 +354,37 @@ }, "outputs": [], "source": [ - "import functools\n", - "import operator\n", - "\n", - "from langchain_core.messages import BaseMessage, HumanMessage\n", - "from langchain_openai.chat_models import ChatOpenAI\n", + "from langchain_core.messages import HumanMessage\n", + "from langchain_openai import ChatOpenAI\n", "from langgraph.prebuilt import create_react_agent\n", "\n", - "\n", - "# ResearchTeam graph state\n", - "class ResearchTeamState(TypedDict):\n", - " # A message is added after each team member finishes\n", - " messages: Annotated[List[BaseMessage], operator.add]\n", - " # The team members are tracked so they are aware of\n", - " # the others' skill-sets\n", - " team_members: List[str]\n", - " # Used to route work. The supervisor calls a function\n", - " # that will update this every time it makes a decision\n", - " next: str\n", - "\n", - "\n", "llm = ChatOpenAI(model=\"gpt-4o\")\n", "\n", "search_agent = create_react_agent(llm, tools=[tavily_tool])\n", - "search_node = functools.partial(agent_node, agent=search_agent, name=\"Search\")\n", "\n", - "research_agent = create_react_agent(llm, tools=[scrape_webpages])\n", - "research_node = functools.partial(agent_node, agent=research_agent, name=\"WebScraper\")\n", "\n", - "supervisor_agent = create_team_supervisor(\n", - " llm,\n", - " \"You are a supervisor tasked with managing a conversation between the\"\n", - " \" following workers: Search, WebScraper. Given the following user request,\"\n", - " \" respond with the worker to act next. Each worker will perform a\"\n", - " \" task and respond with their results and status. When finished,\"\n", - " \" respond with FINISH.\",\n", - " [\"Search\", \"WebScraper\"],\n", - ")" + "def search_node(state: AgentState) -> AgentState:\n", + " result = search_agent.invoke(state)\n", + " return {\n", + " \"messages\": [\n", + " HumanMessage(content=result[\"messages\"][-1].content, name=\"search\")\n", + " ]\n", + " }\n", + "\n", + "\n", + "web_scraper_agent = create_react_agent(llm, tools=[scrape_webpages])\n", + "\n", + "\n", + "def web_scraper_node(state: AgentState) -> AgentState:\n", + " result = web_scraper_agent.invoke(state)\n", + " return {\n", + " \"messages\": [\n", + " HumanMessage(content=result[\"messages\"][-1].content, name=\"web_scraper\")\n", + " ]\n", + " }\n", + "\n", + "\n", + "research_supervisor_node = make_supervisor_node(llm, [\"search\", \"web_scraper\"])" ] }, { @@ -424,7 +397,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 7, "id": "1a7a1260-d9f6-4011-b2b1-13fab5126997", "metadata": { "ExecuteTime": { @@ -434,41 +407,25 @@ }, "outputs": [], "source": [ - "research_graph = StateGraph(ResearchTeamState)\n", - "research_graph.add_node(\"Search\", search_node)\n", - "research_graph.add_node(\"WebScraper\", research_node)\n", - "research_graph.add_node(\"supervisor\", supervisor_agent)\n", + "research_builder = StateGraph(MessagesState)\n", + "research_builder.add_node(\"supervisor\", research_supervisor_node)\n", + "research_builder.add_node(\"search\", search_node)\n", + "research_builder.add_node(\"web_scraper\", web_scraper_node)\n", "\n", "# Define the control flow\n", - "research_graph.add_edge(\"Search\", \"supervisor\")\n", - "research_graph.add_edge(\"WebScraper\", \"supervisor\")\n", - "research_graph.add_conditional_edges(\n", - " \"supervisor\",\n", - " lambda x: x[\"next\"],\n", - " {\"Search\": \"Search\", \"WebScraper\": \"WebScraper\", \"FINISH\": END},\n", - ")\n", + "research_builder.add_edge(START, \"supervisor\")\n", + "# We want our workers to ALWAYS \"report back\" to the supervisor when done\n", + "research_builder.add_edge(\"search\", \"supervisor\")\n", + "research_builder.add_edge(\"web_scraper\", \"supervisor\")\n", + "# Add the edges where routing applies\n", + "research_builder.add_conditional_edges(\"supervisor\", lambda state: state[\"next\"])\n", "\n", - "\n", - "research_graph.add_edge(START, \"supervisor\")\n", - "chain = research_graph.compile()\n", - "\n", - "\n", - "# The following functions interoperate between the top level graph state\n", - "# and the state of the research sub-graph\n", - "# this makes it so that the states of each graph don't get intermixed\n", - "def enter_chain(message: str):\n", - " results = {\n", - " \"messages\": [HumanMessage(content=message)],\n", - " }\n", - " return results\n", - "\n", - "\n", - "research_chain = enter_chain | chain" + "research_graph = research_builder.compile()" ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 8, "id": "110f59bed6134685", "metadata": { "ExecuteTime": { @@ -479,7 +436,7 @@ "outputs": [ { "data": { - "image/jpeg": "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", + "image/png": "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", "text/plain": [ "" ] @@ -491,7 +448,7 @@ "source": [ "from IPython.display import Image, display\n", "\n", - "display(Image(chain.get_graph(xray=True).draw_mermaid_png()))" + "display(Image(research_graph.get_graph().draw_mermaid_png()))" ] }, { @@ -504,7 +461,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 9, "id": "912b0604-a178-4246-a36f-2dedae606680", "metadata": { "ExecuteTime": { @@ -517,22 +474,26 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'supervisor': {'next': 'Search'}}\n", + "{'supervisor': {'next': 'search'}}\n", "---\n", - "{'Search': {'messages': [HumanMessage(content='Taylor Swift\\'s next tour is called \"The Eras Tour,\" which is scheduled to hit U.S. stadiums beginning in March 2023 and running into August, with international dates set to be revealed later. The tour has already started with some shows, including the kickoff on March 18, 2023, in Glendale, AZ. The U.S. leg is set to wrap up in Los Angeles at SoFi Stadium on August 9, 2023.\\n\\nFor specific dates and locations, you may want to check Taylor Swift\\'s official website or trusted ticketing platforms, as the tour dates and details are subject to change.', name='Search')]}}\n", + "{'search': {'messages': [HumanMessage(content=\"Taylor Swift's next tour is The Eras Tour, which includes both U.S. and international dates. She announced additional U.S. dates for 2024. You can find more details about the tour and ticket information on platforms like Ticketmaster and official announcements.\", additional_kwargs={}, response_metadata={}, name='search', id='4df8687b-50a8-4342-aad5-680732c4a10f')]}}\n", "---\n", - "{'supervisor': {'next': 'FINISH'}}\n", + "{'supervisor': {'next': 'web_scraper'}}\n", + "---\n", + "{'web_scraper': {'messages': [HumanMessage(content='Taylor Swift\\'s next tour is \"The Eras Tour.\" Here are some of the upcoming international dates for 2024 that were listed on Ticketmaster:\\n\\n1. **Toronto, ON, Canada** at Rogers Centre\\n - November 21, 2024\\n - November 22, 2024\\n - November 23, 2024\\n\\n2. **Vancouver, BC, Canada** at BC Place\\n - December 6, 2024\\n - December 7, 2024\\n - December 8, 2024\\n\\nFor the most current information and additional dates, you can check platforms like Ticketmaster or Taylor Swift\\'s [official website](https://www.taylorswift.com/events).', additional_kwargs={}, response_metadata={}, name='web_scraper', id='27524ebc-d179-4733-831d-ee10a58a2528')]}}\n", + "---\n", + "{'supervisor': {'next': '__end__'}}\n", "---\n" ] } ], "source": [ - "for s in research_chain.stream(\n", - " \"when is Taylor Swift's next tour?\", {\"recursion_limit\": 100}\n", + "for s in research_graph.stream(\n", + " {\"messages\": [(\"user\", \"when is Taylor Swift's next tour?\")]},\n", + " {\"recursion_limit\": 100},\n", "):\n", - " if \"__end__\" not in s:\n", - " print(s)\n", - " print(\"---\")" + " print(s)\n", + " print(\"---\")" ] }, { @@ -549,7 +510,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 10, "id": "1bcdbf44-9481-430c-8429-fa142ed8a626", "metadata": { "ExecuteTime": { @@ -559,75 +520,62 @@ }, "outputs": [], "source": [ - "import operator\n", - "from pathlib import Path\n", - "\n", - "\n", - "# Document writing team graph state\n", - "class DocWritingState(TypedDict):\n", - " # This tracks the team's conversation internally\n", - " messages: Annotated[List[BaseMessage], operator.add]\n", - " # This provides each worker with context on the others' skill sets\n", - " team_members: str\n", - " # This is how the supervisor tells langgraph who to work next\n", - " next: str\n", - " # This tracks the shared directory state\n", - " current_files: str\n", - "\n", - "\n", - "# This will be run before each worker agent begins work\n", - "# It makes it so they are more aware of the current state\n", - "# of the working directory.\n", - "def prelude(state):\n", - " written_files = []\n", - " if not WORKING_DIRECTORY.exists():\n", - " WORKING_DIRECTORY.mkdir()\n", - " try:\n", - " written_files = [\n", - " f.relative_to(WORKING_DIRECTORY) for f in WORKING_DIRECTORY.rglob(\"*\")\n", - " ]\n", - " except Exception:\n", - " pass\n", - " if not written_files:\n", - " return {**state, \"current_files\": \"No files written.\"}\n", - " return {\n", - " **state,\n", - " \"current_files\": \"\\nBelow are files your team has written to the directory:\\n\"\n", - " + \"\\n\".join([f\" - {f}\" for f in written_files]),\n", - " }\n", - "\n", - "\n", "llm = ChatOpenAI(model=\"gpt-4o\")\n", "\n", "doc_writer_agent = create_react_agent(\n", - " llm, tools=[write_document, edit_document, read_document]\n", - ")\n", - "# Injects current directory working state before each call\n", - "context_aware_doc_writer_agent = prelude | doc_writer_agent\n", - "doc_writing_node = functools.partial(\n", - " agent_node, agent=context_aware_doc_writer_agent, name=\"DocWriter\"\n", - ")\n", - "\n", - "note_taking_agent = create_react_agent(llm, tools=[create_outline, read_document])\n", - "context_aware_note_taking_agent = prelude | note_taking_agent\n", - "note_taking_node = functools.partial(\n", - " agent_node, agent=context_aware_note_taking_agent, name=\"NoteTaker\"\n", - ")\n", - "\n", - "chart_generating_agent = create_react_agent(llm, tools=[read_document, python_repl])\n", - "context_aware_chart_generating_agent = prelude | chart_generating_agent\n", - "chart_generating_node = functools.partial(\n", - " agent_node, agent=context_aware_note_taking_agent, name=\"ChartGenerator\"\n", - ")\n", - "\n", - "doc_writing_supervisor = create_team_supervisor(\n", " llm,\n", - " \"You are a supervisor tasked with managing a conversation between the\"\n", - " \" following workers: {team_members}. Given the following user request,\"\n", - " \" respond with the worker to act next. Each worker will perform a\"\n", - " \" task and respond with their results and status. When finished,\"\n", - " \" respond with FINISH.\",\n", - " [\"DocWriter\", \"NoteTaker\", \"ChartGenerator\"],\n", + " tools=[write_document, edit_document, read_document],\n", + " state_modifier=(\n", + " \"You can read, write and edit documents based on note-taker's outlines. \"\n", + " \"Don't ask follow-up questions.\"\n", + " ),\n", + ")\n", + "\n", + "\n", + "def doc_writing_node(state: AgentState) -> AgentState:\n", + " result = doc_writer_agent.invoke(state)\n", + " return {\n", + " \"messages\": [\n", + " HumanMessage(content=result[\"messages\"][-1].content, name=\"doc_writer\")\n", + " ]\n", + " }\n", + "\n", + "\n", + "note_taking_agent = create_react_agent(\n", + " llm,\n", + " tools=[create_outline, read_document],\n", + " state_modifier=(\n", + " \"You can read documents and create outlines for the document writer. \"\n", + " \"Don't ask follow-up questions.\"\n", + " ),\n", + ")\n", + "\n", + "\n", + "def note_taking_node(state: AgentState) -> AgentState:\n", + " result = note_taking_agent.invoke(state)\n", + " return {\n", + " \"messages\": [\n", + " HumanMessage(content=result[\"messages\"][-1].content, name=\"note_taker\")\n", + " ]\n", + " }\n", + "\n", + "\n", + "chart_generating_agent = create_react_agent(\n", + " llm, tools=[read_document, python_repl_tool]\n", + ")\n", + "\n", + "\n", + "def chart_generating_node(state: AgentState) -> AgentState:\n", + " result = chart_generating_agent.invoke(state)\n", + " return {\n", + " \"messages\": [\n", + " HumanMessage(content=result[\"messages\"][-1].content, name=\"chart_generator\")\n", + " ]\n", + " }\n", + "\n", + "\n", + "doc_writing_supervisor_node = make_supervisor_node(\n", + " llm, [\"doc_writer\", \"note_taker\", \"chart_generator\"]\n", ")" ] }, @@ -641,7 +589,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 11, "id": "9c5c644f-8966-4d2e-98d2-80d73520e9fe", "metadata": { "ExecuteTime": { @@ -651,56 +599,28 @@ }, "outputs": [], "source": [ - "# Create the graph here:\n", - "# Note that we have unrolled the loop for the sake of this doc\n", - "authoring_graph = StateGraph(DocWritingState)\n", - "authoring_graph.add_node(\"DocWriter\", doc_writing_node)\n", - "authoring_graph.add_node(\"NoteTaker\", note_taking_node)\n", - "authoring_graph.add_node(\"ChartGenerator\", chart_generating_node)\n", - "authoring_graph.add_node(\"supervisor\", doc_writing_supervisor)\n", - "\n", - "# Add the edges that always occur\n", - "authoring_graph.add_edge(\"DocWriter\", \"supervisor\")\n", - "authoring_graph.add_edge(\"NoteTaker\", \"supervisor\")\n", - "authoring_graph.add_edge(\"ChartGenerator\", \"supervisor\")\n", + "# Create the graph here\n", + "paper_writing_builder = StateGraph(AgentState)\n", + "paper_writing_builder.add_node(\"supervisor\", doc_writing_supervisor_node)\n", + "paper_writing_builder.add_node(\"doc_writer\", doc_writing_node)\n", + "paper_writing_builder.add_node(\"note_taker\", note_taking_node)\n", + "paper_writing_builder.add_node(\"chart_generator\", chart_generating_node)\n", "\n", + "# Define the control flow\n", + "paper_writing_builder.add_edge(START, \"supervisor\")\n", + "# We want our workers to ALWAYS \"report back\" to the supervisor when done\n", + "paper_writing_builder.add_edge(\"doc_writer\", \"supervisor\")\n", + "paper_writing_builder.add_edge(\"note_taker\", \"supervisor\")\n", + "paper_writing_builder.add_edge(\"chart_generator\", \"supervisor\")\n", "# Add the edges where routing applies\n", - "authoring_graph.add_conditional_edges(\n", - " \"supervisor\",\n", - " lambda x: x[\"next\"],\n", - " {\n", - " \"DocWriter\": \"DocWriter\",\n", - " \"NoteTaker\": \"NoteTaker\",\n", - " \"ChartGenerator\": \"ChartGenerator\",\n", - " \"FINISH\": END,\n", - " },\n", - ")\n", + "paper_writing_builder.add_conditional_edges(\"supervisor\", lambda state: state[\"next\"])\n", "\n", - "authoring_graph.add_edge(START, \"supervisor\")\n", - "chain = authoring_graph.compile()\n", - "\n", - "\n", - "# The following functions interoperate between the top level graph state\n", - "# and the state of the research sub-graph\n", - "# this makes it so that the states of each graph don't get intermixed\n", - "def enter_chain(message: str, members: List[str]):\n", - " results = {\n", - " \"messages\": [HumanMessage(content=message)],\n", - " \"team_members\": \", \".join(members),\n", - " }\n", - " return results\n", - "\n", - "\n", - "# We reuse the enter/exit functions to wrap the graph\n", - "authoring_chain = (\n", - " functools.partial(enter_chain, members=authoring_graph.nodes)\n", - " | authoring_graph.compile()\n", - ")" + "paper_writing_graph = paper_writing_builder.compile()" ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 12, "id": "58e7d1e48a9c39a5", "metadata": { "ExecuteTime": { @@ -711,7 +631,7 @@ "outputs": [ { "data": { - "image/jpeg": "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ERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFzzjyAeHY3c1v+esN1cN/wDnOr0XQ1zzjzt5uxuSB5aw3cN/+dKqDoaIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiKA1BqWbH22Y/HVGX8m+PtiyWUxRRR7kBz3hriNyCAACTse4Akd0UVWk8tIn0VJOc1hudqGEI9W9ub+Gvzy7rD8Qwfyub+GtPotesbwty7oqR5d1h+IYP5XN/DTy7rD8Qwfyub+GnotesbwXLuvI/hy+E/PwUkw2m59HS5ahkzUyUWWF0RMEla4yV8HIYnddomelv07Tfbp19A+XdYfiGD+Vzfw1yvwieCmS8I/R1TA5uviaLqlxluC7WsSOlj26PYN4u57SQfz7HrtsnotesbwXOieD9xXv8beGWP1jd027S8WRe91Sm+34y6SAHlEpPIzl5nB2w2PQA7+l06Oue4ifUuBxNLGY/EYGrQpQMrV4I7c3LHGxoa1o+x9wAAW35d1h+IYP5XN/DT0WvWN4Ll3RUjy7rD8Qwfyub+Gnl3WH4hg/lc38NPRa9Y3guXdFSPLusPxDB/K5v4a/RqDV0R534rDTtHUxx3pWOI/MTERv+n+sd6ei16xvBcuyLQwmZr57HR3Kwe1ri5jo5W8r43tJDmOHqIIIPq6dCRsVvrJVE0zMTmgiIoCIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgKik78R8/+ahRG/wDtWf8A1/Sr0qIf5R9QfqFH96wtvDff9nzh1GUphERezkREQEREBEUS3VWLfqqTTYsk5qOk3IOrdk/YQOe6MP59uX4zXDbffpvtsoJZERUEREGnw4P2DPj1DLz7D+hh/wDNW9VDhx9p1B+15/3WK3rNxXrqnVWYiIsrkREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQER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", + "image/png": "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", "text/plain": [ "" ] @@ -723,12 +643,12 @@ "source": [ "from IPython.display import Image, display\n", "\n", - "display(Image(chain.get_graph().draw_mermaid_png()))" + "display(Image(paper_writing_graph.get_graph().draw_mermaid_png()))" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 14, "id": "9860fd46-c24d-40a5-a6ba-e8fddcd43369", "metadata": { "ExecuteTime": { @@ -741,23 +661,33 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'supervisor': {'next': 'NoteTaker'}}\n", + "{'supervisor': {'next': 'note_taker'}}\n", "---\n", - "{'NoteTaker': {'messages': [HumanMessage(content='The poem has been written and saved to \"poem.txt\".', name='NoteTaker')]}}\n", + "{'note_taker': {'messages': [HumanMessage(content='The outline for the poem about cats has been created and saved as \"cats_poem_outline.txt\".', additional_kwargs={}, response_metadata={}, name='note_taker', id='14a5d8ca-9092-416f-96ee-ba16686e8658')]}}\n", "---\n", - "{'supervisor': {'next': 'FINISH'}}\n", + "{'supervisor': {'next': 'doc_writer'}}\n", + "---\n", + "{'doc_writer': {'messages': [HumanMessage(content='The poem about cats has been written and saved as \"cats_poem.txt\".', additional_kwargs={}, response_metadata={}, name='doc_writer', id='c4e31a94-63ae-4632-9e80-1166f3f138b2')]}}\n", + "---\n", + "{'supervisor': {'next': '__end__'}}\n", "---\n" ] } ], "source": [ - "for s in authoring_chain.stream(\n", - " \"Write an outline for poem and then write the poem to disk.\",\n", + "for s in paper_writing_graph.stream(\n", + " {\n", + " \"messages\": [\n", + " (\n", + " \"user\",\n", + " \"Write an outline for poem about cats and then write the poem to disk.\",\n", + " )\n", + " ]\n", + " },\n", " {\"recursion_limit\": 100},\n", "):\n", - " if \"__end__\" not in s:\n", - " print(s)\n", - " print(\"---\")" + " print(s)\n", + " print(\"---\")" ] }, { @@ -774,35 +704,21 @@ }, { "cell_type": "code", - "execution_count": 13, - "id": "95ae7e52-92ed-41a3-88c4-21b6d7c8b041", - "metadata": { - "ExecuteTime": { - "end_time": "2024-05-15T08:19:55.454047Z", - "start_time": "2024-05-15T08:19:53.725466Z" - } - }, + "execution_count": 15, + "id": "8cbfbe34-43f5-4a3d-8e9b-6a1d9b339aec", + "metadata": {}, "outputs": [], "source": [ "from langchain_core.messages import BaseMessage\n", - "from langchain_openai.chat_models import ChatOpenAI\n", "\n", "llm = ChatOpenAI(model=\"gpt-4o\")\n", "\n", - "supervisor_node = create_team_supervisor(\n", - " llm,\n", - " \"You are a supervisor tasked with managing a conversation between the\"\n", - " \" following teams: {team_members}. Given the following user request,\"\n", - " \" respond with the worker to act next. Each worker will perform a\"\n", - " \" task and respond with their results and status. When finished,\"\n", - " \" respond with FINISH.\",\n", - " [\"ResearchTeam\", \"PaperWritingTeam\"],\n", - ")" + "teams_supervisor_node = make_supervisor_node(llm, [\"research_team\", \"writing_team\"])" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 16, "id": "4880e573-612f-4d24-97c1-2079382a4a2f", "metadata": { "ExecuteTime": { @@ -812,49 +728,43 @@ }, "outputs": [], "source": [ - "# Top-level graph state\n", - "class State(TypedDict):\n", - " messages: Annotated[List[BaseMessage], operator.add]\n", - " next: str\n", + "def call_research_team(state: AgentState) -> AgentState:\n", + " response = research_graph.invoke({\"messages\": state[\"messages\"][-1]})\n", + " return {\n", + " \"messages\": [\n", + " HumanMessage(content=response[\"messages\"][-1].content, name=\"research_team\")\n", + " ]\n", + " }\n", "\n", "\n", - "def get_last_message(state: State) -> str:\n", - " return state[\"messages\"][-1].content\n", - "\n", - "\n", - "def join_graph(response: dict):\n", - " return {\"messages\": [response[\"messages\"][-1]]}\n", + "def call_paper_writing_team(state: AgentState) -> AgentState:\n", + " response = paper_writing_graph.invoke({\"messages\": state[\"messages\"][-1]})\n", + " return {\n", + " \"messages\": [\n", + " HumanMessage(content=response[\"messages\"][-1].content, name=\"writing_team\")\n", + " ]\n", + " }\n", "\n", "\n", "# Define the graph.\n", - "super_graph = StateGraph(State)\n", - "# First add the nodes, which will do the work\n", - "super_graph.add_node(\"ResearchTeam\", get_last_message | research_chain | join_graph)\n", - "super_graph.add_node(\n", - " \"PaperWritingTeam\", get_last_message | authoring_chain | join_graph\n", - ")\n", - "super_graph.add_node(\"supervisor\", supervisor_node)\n", + "super_builder = StateGraph(AgentState)\n", + "super_builder.add_node(\"supervisor\", teams_supervisor_node)\n", + "super_builder.add_node(\"research_team\", call_research_team)\n", + "super_builder.add_node(\"writing_team\", call_paper_writing_team)\n", "\n", - "# Define the graph connections, which controls how the logic\n", - "# propagates through the program\n", - "super_graph.add_edge(\"ResearchTeam\", \"supervisor\")\n", - "super_graph.add_edge(\"PaperWritingTeam\", \"supervisor\")\n", - "super_graph.add_conditional_edges(\n", - " \"supervisor\",\n", - " lambda x: x[\"next\"],\n", - " {\n", - " \"PaperWritingTeam\": \"PaperWritingTeam\",\n", - " \"ResearchTeam\": \"ResearchTeam\",\n", - " \"FINISH\": END,\n", - " },\n", - ")\n", - "super_graph.add_edge(START, \"supervisor\")\n", - "super_graph = super_graph.compile()" + "# Define the control flow\n", + "super_builder.add_edge(START, \"supervisor\")\n", + "# We want our teams to ALWAYS \"report back\" to the top-level supervisor when done\n", + "super_builder.add_edge(\"research_team\", \"supervisor\")\n", + "super_builder.add_edge(\"writing_team\", \"supervisor\")\n", + "# Add the edges where routing applies\n", + "super_builder.add_conditional_edges(\"supervisor\", lambda state: state[\"next\"])\n", + "super_graph = super_builder.compile()" ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 17, "id": "270ff3ae26cd42ff", "metadata": { "ExecuteTime": { @@ -865,7 +775,7 @@ "outputs": [ { "data": { - "image/jpeg": "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NZLT2b0/ltNYqnjLkeXoPrskljj7NxhefRlbvHvzMJGzm/OtvICjdR4dufwluiXBkkjeaGQ7/wAlK0h0cg29bXta4fSApJee/dixtGzbndywV43SyOHqa0En/wBgtUTMVRNPNYeLSuYOodM4rJloY+3Vjmc0fkuc0Ej+47hSqgNAUJcZonCV52lk7akZkaRsWvI3cNvoJKn1u7FMXKop5YyTzERFyQREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREGK1Wiu1pa88bZoJWGOSN43a5pGxBHzEKuYrJHSzocNl5S2Fu0VDIyn0LDO5sb3HumHdsfjjZzdzztZaFht1IL9aSvahjs15W8r4pWBzHj5iD0IXWiuIjdq5Sqnai4HcPNXZmxls3ojAZfKWeXtrl3HRSyycrQ1vM5zSTs1oH6gFHHwbeFDg0HhvpYho2AOJg6Dv/o/SVYToCpAf+j8llsUzffsqt57ox+pknM1o+gAD6F+fAmx7VZ7/Gh/CW9y3PKvrH/phGr36V0bgdDYvybp3D0cHj+0MviuPrthj5ztu7laANzsOv0KZVX+BNj2qz3+ND+EnwJse1We/wAaH8JOHb/H8pMI1WhFqzGY7LW+Keo9PyapzHk6hh8ZehLZYe17Wea8yTm/k/i7Votug683U+q1/Amx7VZ7/Gh/CTh2/wAfykwjVl1jw40rxCZVZqfTmL1CyqXGBuTqMnERdtzFvMDtvyjfb5gq2PBu4UhhYOHGlwwkEt8kwbEjfY/F+k/vU/8AAmx7VZ7/ABofwkGibAPypzx//tD+EnDt/j+UmEasWk+FGieHtye/pvSuF09aliMUtjH0o67nR7hxa5zQOm4B2+hfVmZmvJGVau0mno3tfZtj4lxzSHNiiP5TNx6b/ikDkHNu/kyR8PsbK5rsjYv5rbqI8jbfJF/fECIz/e0qysY2Noa0BrQNgANgAm9RbzonGemH9/Rco5PpERedkREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERBr/Blvn71mATz/AAbwe49W3jOV29f6/UP1n1bAWv8AB7+frWfVu3wbwnQBvN/1nK9/r2/X079vWtgICIiAiIgI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", + "image/png": "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", "text/plain": [ "" ] @@ -882,7 +792,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 22, "id": "6b8badbf-d728-44bd-a2a7-5b4e587c92fe", "metadata": { "ExecuteTime": { @@ -895,191 +805,19 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'supervisor': {'next': 'ResearchTeam'}}\n", + "{'supervisor': {'next': 'research_team'}}\n", "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content=\"Unfortunately, the information obtained from the U.S. Fish & Wildlife Service web pages does not provide additional detailed information on the conservation status, size, or lifespan of the North American sturgeon species beyond what was already included in the initial research report. These pages primarily contain placeholders for the species' profiles without specific information on the topics of interest.\\n\\nBased on the information available, the research report provided earlier remains the most comprehensive summary of the North American sturgeon, including an overview of the species, their conservation status, size, lifespan, conservation efforts, and a chart summarizing key data for each species. Further details would require access to additional sources or in-depth research reports that are not currently available in the provided documents.\", name='WebScraper')]}}\n", + "{'research_team': {'messages': [HumanMessage(content=\"**AI Agents Overview 2023**\\n\\nAI agents are sophisticated technologies that automate and enhance various processes across industries, becoming increasingly integral to business operations. In 2023, these agents are notable for their advanced capabilities in communication, data visualization, and language processing.\\n\\n**Popular AI Agents in 2023:**\\n1. **Auto GPT**: This agent is renowned for its seamless integration abilities, significantly impacting industries by improving communication and operational workflows.\\n2. **ChartGPT**: Specializing in data visualization, ChartGPT enables users to interact with data innovatively, providing deeper insights and comprehension.\\n3. **LLMops**: With advanced language capabilities, LLMops is a versatile tool seeing widespread use across multiple sectors.\\n\\n**Market Trends:**\\nThe AI agents market is experiencing rapid growth, with significant advancements anticipated by 2030. There's a growing demand for AI agents in personalized interactions, particularly within customer service, healthcare, and marketing sectors. This trend is fueled by the need for more efficient and tailored customer experiences.\\n\\n**Key Players:**\\nLeading companies such as Microsoft, IBM, Google, Oracle, and AWS are key players in the AI agents market, highlighting the widespread adoption and investment in these technologies.\\n\\n**Technological Innovations:**\\nAI agents are being developed alongside simulation technologies for robust testing and deployment environments. Innovations in generative AI are accelerating, supported by advancements in large language models and platforms like ChatGPT.\\n\\n**Applications in Healthcare:**\\nIn healthcare, AI agents are automating routine tasks, allowing medical professionals to focus more on patient care. They're poised to significantly enhance healthcare delivery and efficiency.\\n\\n**Future Prospects:**\\nThe future of AI agents is promising, with continued evolution and integration into various platforms and ecosystems, offering more seamless and intelligent interactions. As these technologies advance, they are expected to redefine business operations and customer interactions.\", additional_kwargs={}, response_metadata={}, name='research_team', id='5f6606e0-838c-406c-b50d-9f9f6a076322')]}}\n", "---\n", - "{'supervisor': {'next': 'PaperWritingTeam'}}\n", + "{'supervisor': {'next': 'writing_team'}}\n", "---\n", - "{'PaperWritingTeam': {'messages': [HumanMessage(content=\"It appears that the information you were seeking from the U.S. Fish & Wildlife Service web pages was not as detailed as you needed for the North American sturgeon species. If you're looking for more comprehensive data on their conservation status, size, lifespan, and conservation efforts, you might need to consider exploring scientific journals, research papers, or contacting experts in the field.\\n\\nIf you have any specific questions or require assistance with creating an outline or reading a document related to the North American sturgeon, please let me know how I can assist you further.\", name='NoteTaker')]}}\n", + "{'writing_team': {'messages': [HumanMessage(content=\"Here are the contents of the documents:\\n\\n### AI Agents Overview 2023\\n\\n**AI Agents Overview 2023**\\n\\nAI agents are sophisticated technologies that automate and enhance various processes across industries, becoming increasingly integral to business operations. In 2023, these agents are notable for their advanced capabilities in communication, data visualization, and language processing.\\n\\n**Popular AI Agents in 2023:**\\n1. **Auto GPT**: This agent is renowned for its seamless integration abilities, significantly impacting industries by improving communication and operational workflows.\\n2. **ChartGPT**: Specializing in data visualization, ChartGPT enables users to interact with data innovatively, providing deeper insights and comprehension.\\n3. **LLMops**: With advanced language capabilities, LLMops is a versatile tool seeing widespread use across multiple sectors.\\n\\n**Market Trends:**\\nThe AI agents market is experiencing rapid growth, with significant advancements anticipated by 2030. There's a growing demand for AI agents in personalized interactions, particularly within customer service, healthcare, and marketing sectors. This trend is fueled by the need for more efficient and tailored customer experiences.\\n\\n**Key Players:**\\nLeading companies such as Microsoft, IBM, Google, Oracle, and AWS are key players in the AI agents market, highlighting the widespread adoption and investment in these technologies.\\n\\n**Technological Innovations:**\\nAI agents are being developed alongside simulation technologies for robust testing and deployment environments. Innovations in generative AI are accelerating, supported by advancements in large language models and platforms like ChatGPT.\\n\\n**Applications in Healthcare:**\\nIn healthcare, AI agents are automating routine tasks, allowing medical professionals to focus more on patient care. They're poised to significantly enhance healthcare delivery and efficiency.\\n\\n**Future Prospects:**\\nThe future of AI agents is promising, with continued evolution and integration into various platforms and ecosystems, offering more seamless and intelligent interactions. As these technologies advance, they are expected to redefine business operations and customer interactions.\\n\\n### AI_Agents_Overview_2023_Outline\\n\\n1. Introduction to AI Agents in 2023\\n2. Popular AI Agents: Auto GPT, ChartGPT, LLMops\\n3. Market Trends and Growth\\n4. Key Players in the AI Agents Market\\n5. Technological Innovations: Simulation and Generative AI\\n6. Applications of AI Agents in Healthcare\\n7. Future Prospects of AI Agents\", additional_kwargs={}, response_metadata={}, name='writing_team', id='851bd8a6-740e-488c-8928-1f9e05e96ea0')]}}\n", "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", + "{'supervisor': {'next': 'writing_team'}}\n", "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='I\\'ve found several resources that could provide the comprehensive data you\\'re looking for on the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. A paper titled \"Reconnecting Fragmented Sturgeon Populations in North American Rivers\" by Jager et al., which may contain information on distribution, range contraction, and conservation efforts ([Read the paper](https://web.ornl.gov/~zij/mypubs/sturgeon/Jager at al_2016_Reconnecting Fragmented Sturgeon Populations in North American Rivers_Fisheries.pdf)).\\n\\n2. The North American Sturgeon and Paddlefish Society (NASPS) website, which lists experts and provides details on the society\\'s mission to foster the conservation and restoration of sturgeon species in North America ([Visit NASPS](https://nasps-sturgeon.org/about/)).\\n\\n3. A press release from the U.S. Fish and Wildlife Service indicating that lake sturgeon do not require listing under the Endangered Species Act due to successful ongoing management efforts ([Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list)).\\n\\n4. Information on the Conservation Genetics of Atlantic Sturgeon by the USGS, discussing genetic studies and management strategies for Atlantic Sturgeon populations ([Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon)).\\n\\n5. A story on restoring lake sturgeon along the Ontonagon River in Michigan and St. Louis River in Minnesota, detailing efforts by the Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office ([Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon)).\\n\\nThese resources should provide a strong foundation for understanding the current state of North American sturgeon species. If you require more detailed summaries or have any other questions, feel free to ask.', name='Search')]}}\n", + "{'writing_team': {'messages': [HumanMessage(content='The documents have been successfully created and saved:\\n\\n1. **AI_Agents_Overview_2023.txt** - Contains the detailed overview of AI agents in 2023.\\n2. **AI_Agents_Overview_2023_Outline.txt** - Contains the outline of the document.', additional_kwargs={}, response_metadata={}, name='writing_team', id='c87c0778-a085-4a8e-8ee1-9b43b9b0b143')]}}\n", "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.', name='Search')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n1. **Reconnecting Fragmented Sturgeon Populations in North American Rivers (Jager et al.)**:\\n - This paper discusses the fragmentation of large North American rivers by dams that interrupt the migrations of wide-ranging fishes like sturgeons. Efforts to reconnect habitats are viewed as crucial for protecting sturgeon species in U.S. rivers, as these species have lost between 5% and 60% of their historical ranges. [Learn more from the paper](https://www.semanticscholar.org/paper/Reconnecting-Fragmented-Sturgeon-Populations-in-Jager-Parsley/45414d7c86cd2d4f04b9490c7143f36b5158e729/figure/0).\\n\\n2. **North American Sturgeon and Paddlefish Society (NASPS)**:\\n - NASPS is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing and advancing research pertaining to their biology, management, and utilization. [Visit NASPS](https://nasps-sturgeon.org/about/).\\n\\n3. **U.S. Fish and Wildlife Service on Lake Sturgeon and the Endangered Species Act**:\\n - The U.S. Fish and Wildlife Service determined that lake sturgeon do not require listing under the Endangered Species Act, thanks to ongoing management efforts such as fish stocking that have contributed to their population stability. [Read the press release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list).\\n\\n4. **Conservation Genetics of Atlantic Sturgeon (USGS)**:\\n - The USGS is conducting research on the conservation genetics of Atlantic sturgeon, with a focus on genetic assignment testing and population genetic studies. This is in response to the rediscovery of populations that were previously thought to be extirpated, necessitating updated management strategies. [Learn more from USGS](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon).\\n\\n5. **Restoration Efforts Along the Ontonagon River and St. Louis River**:\\n - The Iron River National Fish Hatchery and Ashland Fish and Wildlife Conservation Office are working with partners to restore lake sturgeon in Michigan and Minnesota. Efforts include collecting larval sturgeon and milt for breeding and stocking programs. [Read the story](https://www.fws.gov/story/2024-04/restoring-reverence-along-lake-sturgeon).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n**Conservation Status of North American Sturgeon:**\\n- Lake sturgeon has origins dating back at least 150 million years and is one of the largest freshwater fish in North America. They are not currently listed under the Endangered Species Act, thanks to conservation efforts such as fish stocking ([U.S. Fish and Wildlife Service Press Release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list)).\\n- The North American Sturgeon and Paddlefish Society (NASPS) is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing research on their biology, management, and utilization ([NASPS About](https://nasps-sturgeon.org/about/)).\\n- All 26 remaining sturgeon species are now threatened with extinction according to the IUCN ([WWF News](https://wwf.panda.org/wwf_news/?6080466/sturgeon-slipping-towards-extinction)).\\n- The USGS is conducting research on the conservation genetics of Atlantic sturgeon to ensure appropriate management strategies can be developed ([USGS Conservation Genetics](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon)).\\n\\n**Size and Lifespan of North American Sturgeon Species:**\\n- The white sturgeon (Acipenser transmontanus), also known as the Pacific sturgeon, can grow up to 20 feet long and weigh up to 1,800 pounds. It is the largest freshwater fish in North America ([American Oceans](https://www.americanoceans.org/facts/types-of-sturgeon/)).\\n- The shortnose sturgeon (Acipenser brevirostrum) can grow up to 4 feet long and weigh up to 50 pounds. It inhabits the eastern coast of North America ([American Oceans](https://www.americanoceans.org/facts/types-of-sturgeon/)).\\n- The green sturgeon (Acipenser medirostris) can reach up to 7 feet long and weigh up to 350 pounds ([American Oceans](https://www.americanoceans.org/facts/types-of-sturgeon/)).\\n\\n**Conservation Efforts for North American Sturgeon Species:**\\n- Long-term conservation efforts in North America have helped to stabilize and increase some sturgeon populations, such as the white sturgeon in the Fraser River in the U.S. ([WWF News](https://wwf.panda.org/wwf_news/?6080466/sturgeon-slipping-towards-extinction)).\\n- The NASPS works to foster the conservation of sturgeon species and restoration of sturgeon stocks in North America ([NASPS About](https://nasps-sturgeon.org/about/)).\\n- The collaborative conservation efforts, including fish stocking, have contributed to the conservation and resiliency of lake sturgeon ([U.S. Fish and Wildlife Service Press Release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list)).\\n- Work in the Chesapeake Bay includes identifying and protecting habitat used by Atlantic sturgeon for spawning, seeking to minimize vessel strikes, and educating students about these fish ([NOAA Fisheries](https://www.fisheries.noaa.gov/feature-story/supporting-endangered-atlantic-sturgeon-chesapeake-bay)).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.', name='Search')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n**Conservation Status of North American Sturgeon:**\\n- Lake sturgeon has origins dating back at least 150 million years and is one of the largest freshwater fish in North America. They are not currently listed under the Endangered Species Act, thanks to conservation efforts such as fish stocking ([U.S. Fish and Wildlife Service Press Release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list)).\\n- The North American Sturgeon and Paddlefish Society (NASPS) is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing research on their biology, management, and utilization ([NASPS About](https://nasps-sturgeon.org/about/)).\\n- All 26 remaining sturgeon species are now threatened with extinction according to the IUCN ([WWF News](https://wwf.panda.org/wwf_news/?6080466/sturgeon-slipping-towards-extinction)).\\n- The USGS is conducting research on the conservation genetics of Atlantic sturgeon to ensure appropriate management strategies can be developed ([USGS Conservation Genetics](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon)).\\n\\n**Size and Lifespan of North American Sturgeon Species:**\\n- The white sturgeon (Acipenser transmontanus), also known as the Pacific sturgeon, can grow up to 20 feet long and weigh up to 1,800 pounds. It is the largest freshwater fish in North America ([American Oceans](https://www.americanoceans.org/facts/types-of-sturgeon/)).\\n- The shortnose sturgeon (Acipenser brevirostrum) can grow up to 4 feet long and weigh up to 50 pounds. It inhabits the eastern coast of North America ([American Oceans](https://www.americanoceans.org/facts/types-of-sturgeon/)).\\n- The green sturgeon (Acipenser medirostris) can reach up to 7 feet long and weigh up to 350 pounds ([American Oceans](https://www.americanoceans.org/facts/types-of-sturgeon/)).\\n\\n**Conservation Efforts for North American Sturgeon Species:**\\n- Long-term conservation efforts in North America have helped to stabilize and increase some sturgeon populations, such as the white sturgeon in the Fraser River in the U.S. ([WWF News](https://wwf.panda.org/wwf_news/?6080466/sturgeon-slipping-towards-extinction)).\\n- The NASPS works to foster the conservation of sturgeon species and restoration of sturgeon stocks in North America ([NASPS About](https://nasps-sturgeon.org/about/)).\\n- The collaborative conservation efforts, including fish stocking, have contributed to the conservation and resiliency of lake sturgeon ([U.S. Fish and Wildlife Service Press Release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list)).\\n- Work in the Chesapeake Bay includes identifying and protecting habitat used by Atlantic sturgeon for spawning, seeking to minimize vessel strikes, and educating students about these fish ([NOAA Fisheries](https://www.fisheries.noaa.gov/feature-story/supporting-endangered-atlantic-sturgeon-chesapeake-bay)).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='Based on the search results obtained, here is a summary of the information available regarding the conservation status, size, lifespan, and conservation efforts for North American sturgeon species:\\n\\n**Conservation Status of North American Sturgeon:**\\n- Lake sturgeon has origins dating back at least 150 million years and is one of the largest freshwater fish in North America. They are not currently listed under the Endangered Species Act, thanks to conservation efforts such as fish stocking ([U.S. Fish and Wildlife Service Press Release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list)).\\n- The North American Sturgeon and Paddlefish Society (NASPS) is dedicated to promoting the conservation and restoration of sturgeon species in North America by developing research on their biology, management, and utilization ([NASPS About](https://nasps-sturgeon.org/about/)).\\n- All 26 remaining sturgeon species are now threatened with extinction according to the IUCN ([WWF News](https://wwf.panda.org/wwf_news/?6080466/sturgeon-slipping-towards-extinction)).\\n- The USGS is conducting research on the conservation genetics of Atlantic sturgeon to ensure appropriate management strategies can be developed ([USGS Conservation Genetics](https://www.usgs.gov/centers/eesc/science/conservation-genetics-atlantic-sturgeon)).\\n\\n**Size and Lifespan of North American Sturgeon Species:**\\n- The white sturgeon (Acipenser transmontanus), also known as the Pacific sturgeon, can grow up to 20 feet long and weigh up to 1,800 pounds. It is the largest freshwater fish in North America ([American Oceans](https://www.americanoceans.org/facts/types-of-sturgeon/)).\\n- The shortnose sturgeon (Acipenser brevirostrum) can grow up to 4 feet long and weigh up to 50 pounds. It inhabits the eastern coast of North America ([American Oceans](https://www.americanoceans.org/facts/types-of-sturgeon/)).\\n- The green sturgeon (Acipenser medirostris) can reach up to 7 feet long and weigh up to 350 pounds ([American Oceans](https://www.americanoceans.org/facts/types-of-sturgeon/)).\\n\\n**Conservation Efforts for North American Sturgeon Species:**\\n- Long-term conservation efforts in North America have helped to stabilize and increase some sturgeon populations, such as the white sturgeon in the Fraser River in the U.S. ([WWF News](https://wwf.panda.org/wwf_news/?6080466/sturgeon-slipping-towards-extinction)).\\n- The NASPS works to foster the conservation of sturgeon species and restoration of sturgeon stocks in North America ([NASPS About](https://nasps-sturgeon.org/about/)).\\n- The collaborative conservation efforts, including fish stocking, have contributed to the conservation and resiliency of lake sturgeon ([U.S. Fish and Wildlife Service Press Release](https://www.fws.gov/press-release/2024-04/collaborative-conservation-keeps-lake-sturgeon-endangered-list)).\\n- Work in the Chesapeake Bay includes identifying and protecting habitat used by Atlantic sturgeon for spawning, seeking to minimize vessel strikes, and educating students about these fish ([NOAA Fisheries](https://www.fisheries.noaa.gov/feature-story/supporting-endangered-atlantic-sturgeon-chesapeake-bay)).\\n\\nThese resources provide comprehensive data on the state of North American sturgeon species and the various conservation efforts being undertaken to preserve and enhance their populations. If you require further information or assistance, feel free to ask.')]}}\n", - "---\n", - "{'supervisor': {'next': 'PaperWritingTeam'}}\n", - "---\n", - "{'PaperWritingTeam': {'messages': [HumanMessage(content='The outline for North American sturgeon species, including their conservation status, size, lifespan, and conservation efforts, has been successfully created and saved to a file named \"North_American_Sturgeon_Overview\". If you need to review the document or require additional information, please let me know.', name='NoteTaker')]}}\n", - "---\n", - "{'supervisor': {'next': 'PaperWritingTeam'}}\n", - "---\n", - "{'PaperWritingTeam': {'messages': [HumanMessage(content='The document \"North_American_Sturgeon_Overview\" contains the following outline:\\n\\n1. Conservation Status of North American Sturgeon\\n2. Size and Lifespan of North American Sturgeon Species\\n3. Conservation Efforts for North American Sturgeon Species\\n\\nIf you need more detailed information on any of these topics or have any other requests related to the document, feel free to let me know!', name='DocWriter')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='The document \"North_American_Sturgeon_Overview\" contains the following outline:\\n\\n1. Conservation Status of North American Sturgeon\\n2. Size and Lifespan of North American Sturgeon Species\\n3. Conservation Efforts for North American Sturgeon Species\\n\\nIf you need more detailed information on any of these topics or have any other requests related to the document, feel free to let me know!')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='The document \"North_American_Sturgeon_Overview\" contains the following outline:\\n\\n1. Conservation Status of North American Sturgeon\\n2. Size and Lifespan of North American Sturgeon Species\\n3. Conservation Efforts for North American Sturgeon Species\\n\\nIf you need more detailed information on any of these topics or have any other requests related to the document, feel free to let me know!')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='The document \"North_American_Sturgeon_Overview\" contains the following outline:\\n\\n1. Conservation Status of North American Sturgeon\\n2. Size and Lifespan of North American Sturgeon Species\\n3. Conservation Efforts for North American Sturgeon Species\\n\\nIf you need more detailed information on any of these topics or have any other requests related to the document, feel free to let me know!')]}}\n", - "---\n", - "{'supervisor': {'next': 'PaperWritingTeam'}}\n", - "---\n", - "{'PaperWritingTeam': {'messages': [HumanMessage(content='It seems that the document \"North_American_Sturgeon_Overview\" contains exactly the outline provided earlier, with three main topics:\\n\\n1. Conservation Status of North American Sturgeon\\n2. Size and Lifespan of North American Sturgeon Species\\n3. Conservation Efforts for North American Sturgeon Species\\n\\nIf you would like to delve into any of these topics or have another request regarding the document, please let me know how I can assist you further!', name='NoteTaker')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='It seems that the document \"North_American_Sturgeon_Overview\" contains exactly the outline provided earlier, with three main topics:\\n\\n1. Conservation Status of North American Sturgeon\\n2. Size and Lifespan of North American Sturgeon Species\\n3. Conservation Efforts for North American Sturgeon Species\\n\\nIf you would like to delve into any of these topics or have another request regarding the document, please let me know how I can assist you further!')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='It seems that the document \"North_American_Sturgeon_Overview\" contains exactly the outline provided earlier, with three main topics:\\n\\n1. Conservation Status of North American Sturgeon\\n2. Size and Lifespan of North American Sturgeon Species\\n3. Conservation Efforts for North American Sturgeon Species\\n\\nIf you would like to delve into any of these topics or have another request regarding the document, please let me know how I can assist you further!')]}}\n", - "---\n", - "{'supervisor': {'next': 'PaperWritingTeam'}}\n", - "---\n", - "{'PaperWritingTeam': {'messages': [HumanMessage(content='The document \"North_American_Sturgeon_Overview\" indeed contains the three main topics outlined earlier:\\n\\n1. Conservation Status of North American Sturgeon\\n2. Size and Lifespan of North American Sturgeon Species\\n3. Conservation Efforts for North American Sturgeon Species\\n\\nIf you need detailed information on any of these topics or have another specific request related to the document, please let me know, and I can provide the information or take further action as needed.', name='NoteTaker')]}}\n", - "---\n", - "{'supervisor': {'next': 'ResearchTeam'}}\n", - "---\n", - "{'ResearchTeam': {'messages': [HumanMessage(content='The document \"North_American_Sturgeon_Overview\" indeed contains the three main topics outlined earlier:\\n\\n1. Conservation Status of North American Sturgeon\\n2. Size and Lifespan of North American Sturgeon Species\\n3. Conservation Efforts for North American Sturgeon Species\\n\\nIf you need detailed information on any of these topics or have another specific request related to the document, please let me know, and I can provide the information or take further action as needed.')]}}\n", - "---\n", - "{'supervisor': {'next': 'FINISH'}}\n", + "{'supervisor': {'next': '__end__'}}\n", "---\n" ] } @@ -1088,16 +826,13 @@ "for s in super_graph.stream(\n", " {\n", " \"messages\": [\n", - " HumanMessage(\n", - " content=\"Write a brief research report on the North American sturgeon. Include a chart.\"\n", - " )\n", + " (\"user\", \"Research AI agents and write a brief report about them.\")\n", " ],\n", " },\n", " {\"recursion_limit\": 150},\n", "):\n", - " if \"__end__\" not in s:\n", - " print(s)\n", - " print(\"---\")" + " print(s)\n", + " print(\"---\")" ] } ], @@ -1117,7 +852,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/docs/docs/tutorials/multi_agent/multi-agent-collaboration.ipynb b/docs/docs/tutorials/multi_agent/multi-agent-collaboration.ipynb index 734b1097d..6d4bb1537 100644 --- a/docs/docs/tutorials/multi_agent/multi-agent-collaboration.ipynb +++ b/docs/docs/tutorials/multi_agent/multi-agent-collaboration.ipynb @@ -2,25 +2,25 @@ "cells": [ { "attachments": { - "02659c68-8b4b-42ed-a002-1c08f4a7c299.png": { - "image/png": "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T58q9/mLL75YtWrVe++9Jysojx07lpycfPLkSSHE5MmT5Q/xyZMnY8aMUU4VFRW1Y8eONN/lZdK5gRl5uRDCxsZG+cBIRqNx+/btQoiqVauK/4olhRBK8gsAAPB2omIOAIBscf/+fVnldPbs2Xnz5t25c+fBgwdFixaVy0cqvag+Pj4ybhNC1K1b98CBAydPnjQNHXx8fKysrD799NN//vknKipKCJGUlGRlZVWvXr3w8PAffvghMTGxUqVKGzZsCAwMVF518ODB9u3bKw8rVaqk0+mUdVflkUrx0YEDB4QQsbGxT548SdEDm0EfffSRRqMxGAy//PLL9u3b+/fvX7p06X///Xfnzp0y8enfv788UlkZwNbWVg6jefPmTk5OAwYMiImJMTMzs7KyUoq5lAosBwcHJQySRYIREREGg+Gff/4RQrRs2bJt27Zt27a9efPmo0ePrK2tTWup5LIGplmnQhaLBQYG7tmzp3z58jt37pSrcLRs2bJz584eHh4xMTEDBw5ctGiRXLpUkk2gV69elQ/lGEqUKGFubq5MC9ihQwcXF5dJkyZdvXq1dOnSSt9oRrzeqF6mYcOGISEhCxYsePbs2dixY+WMcqbk0h9CCFtb259//lnefJmfGgyGf//9V97M5cuXDxky5OrVq3PnzpU/02XLli1cuDA8PHzcuHHFihVr37690md67949CwuLlw0pnRv4ysupWbOmEEKn023evLlbt2758+c3Go3//PPPL7/8EhYWZmFhMWXKFCFE0aJFZet0dHS0aVUpAADA24ZgDgCALBYQEDBr1ixZ9ySEiIyMVLaFEMnJyU5OTjLsaN26ddOmTZWn6tSpI4T466+/ZAOjpaVlgQIFYmJixowZo8RSFhYWLi4uZmZmM2bM6NOnj16v/+mnn5QzTJ48ed++faGhoVu3bjUN5mQUsm3bNltb2/DwcLng6aeffiqflVnMF1988XqpnBCiVKlSu3btcnNz0+v1UVFRysxx0rhx4+SSDk+fPl2+fLkQonPnzsqaqnv37nVycjI3N7exsUlxWmX1zB9++EGp8CpUqJAMJZVuyj179owYMcLc3LxixYpKyqmQZYDnz59PPeymTZtaWFjo9Xq5eoDUtWvXX3/91dzc/N69e1OnTj169GiXLl1+/fVXpd6tXLlyQogTJ05s27bt8ePHsn/WxcXF9MyH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T9euXXv06KEclpSUNHHiRF9fX9PXLly4sFKlSr169crITdPr9X379o2IiJAPd+/evXv37tGjR48dO1Y55vbt20OGDFEiJCGElZXVtm3bTHszL168OHHiROUye/bs2bJly4oVKxYvXlwGUr17965bt27lypXLlCmTnJwsgzmZ2b2GkJCQYcOGKVnbokWLlICvdevWGb8/L168GDJkiOkiGwaDwc3NLSwsTHbgypv/3XffpTjJzp07UyewN2/eHDVqVGhoqHw4ePDg77///tGjR0KIjh07mh6Z/qcLAAAA2e0tWiwMAIC8YfHixUoqZ29vv2nTpk2bNg0fPlwIERIS0rlz51u3bnXr1s3KykoIcfnyZdPXzp49WwhRv359FxcXIcShQ4dkblKvXr3NmzefO3dOBnZ+fn4JCQlCiLVr18oXHjt2bNu2bWvXrj1//vzYsWMtLCwOHjyYziDLlClTu3ZtmbsVL15cvGRitcKFCyvbDx486N2798KFC0NCQiZMmBAQEKA8NWvWLBkYde3a9dChQyEhIQ4ODkIIGTJmxNKlS5VUztLSUtbKzZ8/X7nAq1evdunSJTw8XKPRuLq6jh8/3tXVNSYmpnnz5vfv31fOU7JkSWXb2dm5Z8+ejRo1qlq1qmmZWN26dWXaZWZmJrtWTc+QcZGRka6urvK+yfN7e3sr8/GZvuMr74+np6dM5RwcHHx9feXHQAihFE4+ffp08ODB8iStW7f++uuvJ02aJIRwcXHRarWmo5KJnkzl5BhWrFgRFhYm01XTUb3y0wUAAIDsRjAHAEAWU0KNtm3bbt261dnZ2dnZecKECSEhITY2NjExMQsWLDA3N5frfp4/f1554a5du2SaNnXq1Hz58sXHx3/33XfyqT59+pQrV+7SpUsycNFoNDJTO3bsmBBi7NixVapUkUdqNJrRo0efOnUq40VPspsyzSymUKFCciM2NtbV1dU0A5oyZYossrtw4cLixYuFEJaWll26dClSpEh4eHhkZKT4L/LLiL/++ksZ/65du/7888/OnTsLIWbOnPno0SODwdCnT5/Y2Fg7O7vAwMDZs2d/9dVXMtk0GAxLlixRzlOhQoWtW7fKadQOHjzYokWL77///vbt2y97X5k8JiYmvsYPWpbmWVhYbNq06dSpU6dOnVq2bNn06dPls8pP9pX359KlS35+fkKIzp07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FStWnD179uzZs2/evPndd98FBATodLpevXo1adJkwoQJ9vb2ab5d4cKFDQZDYmLiawxVr9e3adNGjqR+/fqtWrW6efNmgwYNtFrt+PHj+/btq8ya17Zt2/nz58siGmdn5wEDBri7u+t0ugULFkybNi2Dbyeb5tKcic90/OvXr5dZZJ06dbp27bp69eopU6ZMnz69Y8eOcvkL5C758+dXZnCbMGHCxo0bW7Vq5erqmuIw+fFI0UFpa2vbsGHDkJCQdKb8T8fnn3++fPlyIUTLli3TOezrr7/++uuvhRA7d+78+uuvLSwsBg0a9LKDnz9/LoQ4e/as6c6goCAhRI0aNV72qsjISDkSyTR//Ouvv4QQXbp0ydrJFp8+ffr9998rD1PP7PbKC9m8ebOSyrm6uvbp06devXqp30hZGzodSuHegAEDlKpYCwuL77//vlOnTqbLdNSqVatVq1bKKjq1atWS68YYjUbTwywsLDw9Pd3d3T///PPY2NjJkye7uLgwWyUAAMBrI5gDoCbZR5bmGo6Z8u2333bo0OFliy3Kr+IXL14UJlVjtra2qY+sWLHiqlWrTp48OXv27BMnThw9evTo0aNt27b96quvUn8xlt+KTftDAwMD7ezsMlJetHDhQjmSKVOmDBw4UNn/6aefxsTEbNu2TSlC8fHxMe3Rq1ChwrBhw7755pvUU+mnQw7SNIOLjY2Niopq2rSpUs4zduzYFBWC/fv3nz9/vl6vP3v2LMFcbierU99//33TnUlJSbGxsaYHmPLx8blz546FhUUODO/JkydCiHQmTYuPj5cldQaDwWAwyDT/7t27MTEx6bzQaDR+++23QghZ3ypM5poUQsgeT2Uayqwyb948Ob1j6nfM4IXI3nmpQoUKaf6yEkK8bClqxe3bt7dv3y6E6NGjR4UKFZSIcOXKlWneMdMTKgW5BQsWnD17thBi6NChSqyZlJRkZ2cXGxtrMBhSz5cHAACAjOMvUgDUlOYX19dQvXr1l6VyQgiZLERFRT18+PDBgwevPFuDBg02bty4devW1q1bCyEOHDjQuXPnkSNHPnv2zPQwGcApccbp06f79esne05fSZauaDQaNzc3ZeeaNWvkfqUvz8bGJkU5odFolN+007ne1OS8WqatrJMmTerTp8+5c+eUPam//B87dky+RFn1ArnXnTt3hBApejYXLVrU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e/du3769MoPVK5mbm/fs2VN5qNfrZTBXt25duee9996bOXPmjBkz7t27l5iYWLZs2deeyylfvnxeXl69evUSQmg0mj59+vTr1880mnRxcXFxcXn69OmjR48KFSqUwYZc5BZygrmX9aXKXKZ48eJ2dnYi3RUYXsOqVavq1auX/goSRqNR/kF72cIpsse2Z8+eTk5OAQEBoaGhERERc+bMEUK4u7un/nPh7e0tJ0qrUaPG3LlzL168eOTIEflU165de/fuLcvBOnXqlFXzowUGBvr4+Agh7Ozstm3bFhMTo9VqZf3a5MmTly1bJn9RZPZCFEWLFu3Tp4+zs/MXX3yh1+tXrVr1ySefKP+kceXKlf379yv/flCyZMmwsDB5ycOHD1fmqVTy2XHjxtWpUyf9f8+QxcVy4en8+fM3aNBg7969X375ZVhY2LRp0y5evDh9+vRXrj4BAACAVyKYA6Cm5s2bN2/eXHlYu3btlStXZuoM7733nrJ6YDrMzMzKlSsntxs1apROmUyRIkWGDBnSq1evc+fO6XS6R48elSxZskyZMs7OzinWYciXL9/q1aujoqLKly//5itXylY7jUaTYpot05G/iU8++eTUqVN37typWbOmaW+dqaJFixYtWvTN3wtvGzlloSykSk0WYF66dCk73vqVqZwQ4ubNm8Kk4To1WVPWvHlz2cIphOjYsaPcUNYvVuzevfu3336T2wcOHEjx7JAhQzw9PTt16iSEeGWlbQZdunRJts0KISIjI+USLoomTZrI6RozeCG7d++eMGFCjx49vvnmm0KFCpmeqkaNGs2aNdu+ffutW7fEfz84IcSmTZuU5FEI8fjx41mzZinXq+yXZYm1a9c2beB98uTJ48ePnz59mpSUpNFoypcvL/NB5ceh1+vl1JMVKlTw9fX98ccfN2zYsGXLlujo6N9//z2z1bsAAABIgWAOANJQtGhRufhD+oeZm5vLIqM3J+fF//jjj1+Wmr05CwuLNw8Qkevcv39fLgJw9uzZefPm3blz58GDB0WLFu3evbvsnZRZsMFgiIuLU4qwsoper0//UxcfH79p0ya5IdcevX//fmJiYtOmTeWaxdHR0bL0zMnJydzc3M3NTS4jK4Sws7Ozt7c3Pdvu3btHjRoltzUazccff+zg4FC7du0yZcrINKply5YajUYWl61Zs6ZUqVLW1tZl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paUJIWJjYz/88EONRiNbDVSvXv3LL7807rdQcEVyQjl3NZ9gTlEa/hBkZGTIn5oQolq1aiX81EIItVr97B0nAOSP/8cAAACEEKJatWqvvPJKxYoVa9asyecQAGVL9+7dT5w4IYRISEiQq4NNmDDBZM1++S+brDtTqNXqvn37RkdHF6RMeNGiRTKVU6vV77zzzvDhw00mV164cGHDhg13796Njo7WaDRy544dO5QD4uPjHz16VPDXVeQnlPGWLB4snNTU1Li4uCpVqjRo0KDQJykgmUVKNWvWLO6nM5acnCyEsLOzK8knBcon3nQCRUCv1+/evbtVq1ayq5FFOn78eE5OTvfu3c09EAAoLlZWVrKopDRUSQBA4SgplUlspNFosrKyRF5TQceOHduqVauuXbs+8eS1atWSGzqdztXVtW7duiYHzJo1KzIy0mRnq1atOnbs6O7u3rx58yZNmjxVn9NCn/DWrVvx8fGNGzeWU1MVsmCwEPNhL1++fODAgb179yrdGJYvXy5XYTMhG57ev3//X//6V+XKlZ/2iYzJsjUhhJubW+6XmZ6eHhMTo9frW7ZsafJ9Umpq6t69e6OiopKTk52cnLp06dKtW7en+usmVyQsgfARAMEcUFA5OTnGKztkZ2dPnTp10KBBffr0CQ4OnjFjhpub288//2zuYRalVatWpaenT58+/datW6NGjRJC/Pnnn9WrVzf3uACguBDJASjr7t27J4Rwc3MzfuP6xx9/vP7663I7JiYmMjKySZMmSmakVqsL+OXrp59+mpCQcPr0aSHE1KlTly9fPmHChEGDBinVds8995zM0fr06ZOamnrixAm1Wr1v3758zpmenv77778nJiba29vXqFGjU6dOxrV7hThhfHz8kiVL9u7dK2+6ubktW7bs+eeflzdlgPVU/9r/8ccf/v7+ufPB9PT03AdHRETMnz9fOdjb2/vLL72lZKsAAG5wSURBVL+UaeCNGzfu37/fsmVLIcS5c+dmzZql1+tXrFjh5uamPDwrK+vChQuJiYkeHh7VqlXLzMyU+/v27Wv8LNnZ2Xv27FmwYIHScnfKlCkzZsyQP/SMjIzx48fLCkrpm2++6d69+6JFi0xiynwkJCQIoygWQPEhmEN+MjMzf/rpJ51O16FDB+M/GOXB/fv3Z8+enZCQkJKSkpKSIv/mubi4LF++XP41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Rxew0F2h1+uPHTs2ZcqUtm3bzps3T1aS63S60NBQ5ZjLly9rtdozZ84U1WuURV6HDx8u3MNVKtWYMWPEP+ue5mnLli3ya7033njDycnJ7E2Lrly5IjeKqnJt6tSp4eHhjRo16t69u6Oj4wsvvCCEqFChQkhISERERHBwcGBg4Pz58//zn//INwo1atQw7xUAgGeXlpaWlpYm1ysAgDLn4sWL4jEVYcqX60rc81T+85//tG3bNigoyGAwmNzVo0cP+RX1nTt3lJ3KdFHls8OWLVvGjx+/bds2IYR82z9nzhyZyj169OiDDz5QHhsTE7N7926TZ8n/hHJVtTZt2qxbt844k/Lx8Vm7dq2Mxk6ePCkfGxUVZfyJRkn68tGuXbtt27b98MMPcrm3w4cPDxw4cMqUKbIO7ujRo0KISZMmzZkzR7nOarV61qxZEyZMkDflxxMhxNSpU9944w15TiHE6dOn5ScOZ2dnZW2c77//ftGiRUIImf3t3r07LS1Nr9fL+arLli0bPXq0MjZnZ+f169fLCa3JycmF+OGaCAwMlBuyFg9AsaJirjxKTk728/MTQuRuLSSE+P3331NTUytVqnT79m0hRP369eV+Nze3n376acCAAadPn54+ffratWsL8lx6vX737t2ff/658tWNWq329fXt3LlzXFzcgAEDTI5X+rs/O1niLteAK5wePXp8+eWXGo3GYDDkuRpuhQoVZBTVu3fvXr16mb2X399//y03inAksmbe2dn5zJkzymnt7OzybPLAIhQAyrrs7OzY2NjU1FRXV1eqgAGULdnZ2UePHpXvujdt2mRtbX3nzp2MjIymTZtOmTLF1ta2UqVKct5DXFxc06ZNn/b8f/31l1ar/eijjwICAl577TUXFxdra2udTnf9+vWQkBAZ9r388svK8cpXtgkJCfXq1TMYDPIrc5mvyfK9devWjRs37vr160uXLpW51ddff71q1arIyMgZM2ZUrlzZuBtp/if8448/hBA9e/b09vb29va+devWgwcPGjZsaDwdZ+fOnXJj//79+/fvl2GiHHn37t1Xrlz5uCmux48fb9q0ab169Tp06NChQ4eoqKiAgIAjR47s378/KSlpy5YtISEh8iSenp5vvfVWfHy8lZWVs7OzsgyO8mmoffv2SgQpfwpRUVGy+6pWq42JiWncuPGSJUvkfNXu3bt/++23PXr0iI+P//nnn5XAsV+/foMHD/73v/99/fr16tWrG7doUKbO3L1713hlunw8evRo//79iYmJaWlpKSkp0dHRSsVcRESEyQJEAIocwVx5VLVq1Z49e964cUN2H9dqtY6OjrNmzapVq1blypVbtmwp5y2mpqaK/1vmXb9+/aCgoN69ex88eDAxMTH/Ztt6vX7RokU//PCD8kfIyclp3Lhxb7zxhvyD9+KLLxofLL+zWr169U8//XT9+nWtVlu5cuVevXrJ1RYKQS4Ku2/fvocPH16/fj0pKUmlUjk7O3/11VcF7ByqfCS7f//+EyMns6dywugryuLoa57/C8z92wIAZVFmZmZcXNyff/6pUqleeOEFPooAKBNycnLefffdkJAQpQps3bp1xgcMGTJETm5t27ZtcHDwlStXcq/g9kSTJ08eN26cEEKj0ciJqCYWLlzYpk0b5aZKpZIrw3z99df37t3bt29fRESEEEKuc+fr6ztv3rzg4ODg4GB5vFqt3rx5c7t27dq0aSNXuZk4ceLEiRP9/PxkuJb/CaUDBw5MnjxZpVLVq1dPadr2OMaVgyEhISdOnOjXr1+eR3700Udy6ujbb7/dqFEjd3f3b7/9dtu2bTNnzoyIiFBWsjt48KCnp6dKpTJpviGM3kv/5z//URbLs7W1bdWqVWRkpCyJ0Ol0xt1X27dvv3z5cpVKNXXq1A8//HDLli3//e9/5V1Hjx7t27evvb197kWuleq/q1evygm8T7Rw4cLNmzfnedfHH3/Mn0KguBHMlUc2Njbr16+X2zNnzty2bVuvXr1kmx5j8g+GyVweV1fXjh07Ku3G83H27NmvvvpKbnt6eo4dO7ZHjx7Gj9Lr9WvXro2Ojk5ISFC+k4mMjDSu42vcuPFTvbTg4OAjR47cvn07MjJSvi/RarU//PCDckBKSkpWVlYBgzklZrp7927hasFycnJu376dkpLSqFGjolo7Lx9KvWH+mWlxkEtmyFZThZCWllahQoUn/lJlZ2dnZGRUqlSphF8dgPKjQoUKGRkZ165du3LlSpMmTfi+AUCZcOfOHSXektRqtZubm5ubW4sWLTw8PJQl515//fXg4GAlGHoq3t7eERERhw4dOnLkyLVr1+S373Z2drVr1/bw8HjrrbdyL2z3yiuvbNy48fDhw8ryMv7+/jVr1hRCjBw5MikpadWqVXJ/nz59Zs2aJd/8165de9u2baNGjYqNjV27du2bb77ZsGHDJ55wwIABhw8fjo6Onjdv3n//+1+TN5ZarTYpKWn27Nl16tT5+++/ExIS9Hq9Wq2uUqWKra3t2bNndTqd8iy5ubm5aTSajRs3bty40cnJydHRMTU1VfZ/E0Lk5OSMHDlyy5YtGzZseOGFF1577TXjxxoMBo1Gk56evmvXrpiYGJPeuB999NGIESPq1q07btw44zh17NixH374ofzY8vrrr+/evfvatWvNmzd3dXWNiYmZPn16o0aNTFK5rKyshIQEpa7wibmkwngZHCcnp2rVqtWvX79u3bovvvji45JKAEXIKvcCAShXpkyZsn///rlz5yqrtAohcnJyNBrNsmXLgoKCPv30U5NVRRMSEpKSkoy/DctTTEyM8oVPz549P//8c+MSayHE8ePHR40aZfIotVrdo0eP1q1bu7m5NW/eXP6VLSCdTvevf/0r9/5u3bq1a9dOnrBBgwa534ikp6fHxMTo9fqWLVsa/wnPzs6WX3b9+OOPcgGIAkpLSzty5MjBgweVd0guLi6//PJLnsHT4569EP7zn/9s3LhRCPHNN9+89NJLJvfeunUrPj6+cePGuWO7c+fOHT9+/NKlS2q1ulmzZoMGDapdu/ZTPfW//vUvnU63cePG7t27P/HggwcPKl2rJPnGzsnJ6fnnnx8+fLicBGEwGAYNGmTcYTY9PV3mre7u7q1bt/7oo48K3uoeAAooKCho+/btPXv2fOONN2rXrl0aCqIB4In27dsXExPj4uKi1+vlvJOKFSvmeaScB1oy/7glJib27dtXvtN79dVXfX19TT5E6HS6O3fuODk55e4Jm52dffbs2YYNGxq/L83nhAaDwdfX97fffhNCuLq6+vr6Nm3a9OHDh1FRUTKwE0KsX79eroxs4nEL1yjS0tK2bNkSEBCQe63tsWPHzpkz5/79+7169ZID69279yuvvOLk5JSUlHT69On9+/fL/efPn8/z+x69Xi8/BURGRl69etXBwcHNzc0kVsvKyrp7966Tk1NERIRSUTFmzJhOnTpVrVo1Li7u5MmTR48e1el07u7us2fPvnLlivEidPlLTU09evSora1tmzZtimPmDYD8EcyVdz4+PhEREd99951xNfvKlSsXL14stwcNGvTuu+82atTocX/a87Fz507j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qnl7e3t7e7do0ULWnf3www+1a9c+deqUbEp7+PBhlUqlrCu3fPny5cuXKyfs0qWLXGPu7t27yk4fH5/333//77///v7772UUmJmZ6e/vL+8NCQmRxYOSo6Njv379hBB37twRQtSrV0829zAZ9vz584UQkyZNkqmcECI4OFg5YOXKlUqRncmFMs7gNBpNTExM165dlarD6dOny1ROMWbMmOXLl2u12vPnzxfkxwGg4AjmUDZ06tQpIiKidu3a2dnZbdq0USrbjRdH0Ol0kZGRkZGRa9eudXd3HzFixCuvvGLBQZLBYDh8+HC3bt2Kr6+5o6NjaGiora2tlZXVpEmTrl69au4XDQAlJCUlpUaNGteuXbtz505mZiYV2QDM6969e8Y3PT093d3dq1WrplarK1as6OTk1LRpU7n2mfEURWdn5/bt2zdo0KBy5cr29vb29vZNmzZt1qyZLOYqhFq1avn6+m7cuPHDDz+8ePHinDlzHvfPY3p6utyQqdzPP//s5uaWnZ3t6Oio1WrPnTsnM6Du3bvLaa1CiODg4B9++EE5w8KFC1966aWKFSuK/xtvPY6SW+l0Orm9ZcuW+fPn//e//x0zZkz+j1WSwYULF65Zs0Y+qaQEWCqVqmLFit27d5c3a9asKWeYtmnTZvLkyYsWLVKqAn18fD755BM540dmWPJH9tlnn1lZWcnALjY29sGDB1evXpXFcVu3bl23bp0SzHl6en7++efVq1cXQiQnJz9u2Ldv35YPnzFjhtwTExMzb9485YCNGzeOGTPGpJmGbDVu/Hvy73//+9ixYwcOHFCyNldXV5Pn+v3335V17gry4wBQcLzLRJkh+46rVKrdu3fLv3MHDx4MCQlxd3cfN25c48aNs7Kyrl27dvjw4cOHD0dFRc2aNWv+/Plr167t2rWrucdeLDIzM8ePHz958uSPP/64+J5FeYszc+ZMc79iACg5lSpVatCgwfnz5+XHHqWGAgDM4qWXXvL19bWysho8eLCyqEueFixYEBQU1KVLlx49esj3z0Vr7ty5cXFxISEh69ev37dvn6+vb/fu3VNTU2/evHn37l1HR8eXXnqpevXqxtNdAwMD5XQWGxubfv36bd68+dSpU3LaY0hIyIEDB2rXrr1nz57NmzcLIXr27Dlw4EA/P7/4+Pi333579erVVatWlRNCExMT8xmYsoZa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l6ur6+LFi4s2XpS0Wm1SUlKzZs1YrgEwO4I5lGGffvrpt99+6+jouG7dujxXkXN0dPziiy9eeuklZc/OnTuVpkWKV155ZdWqVZcvX1aOVKvVb775Zs+ePevVq1elSpXc9Wj5nEe5uWnTprlz5xofMGfOnLFjx5p8lzhy5EgZijk5ObVo0SI2NjY2NlYI0apVq82bN8v1O0yeaOPGjTJ/9PT01Gg0SgelnTt37tixo2PHjpMnT1aabeXk5MycOTP324XPPvtMflG2atWqRYsWyVctv5CUa9yq1epdu3aZ1O4BAAAA5ZNer//++++9vb3l2nCK8+fPb9269fz58xqNpkaNGs2aNfP09PT09DRuuZA/jUazdevW8PDwq1evqtVqFxeXdu3aderUqXXr1rQFBywe/5OjDJP9gNLT09u1a/fFF18ozUkdHR1HjRrVqlWrTp062dnZKccfPXpUpmmtWrWaPXt2ixYtli5d+u233+7fvz8wMNB41QYvL68333zzcbXZ+Z9HzngKCAiQfZrkMOrVq7dw4cL58+enp6dPnTpVOdWVK1dkKufr66ss6Hvu3LlFixadOnXqm2++Wb58uRDCyclp7ty5arV65syZGo0mNDRUBnNy/mxmZqZer//888+/+eYbIURERIRer5crswohFi1aJFO5IUOGTJkypUqVKpMnTz59+vShQ4dGjBih1+vlN35ubm779++Xf/hv3bq1dOnSAwcOnD59mmAOAAAAEEKoVKrRo0fn3t+yZUs5HbXQnJyccn/rD6CcoPkDyrDMzExle9iwYT/++KMsbZNrlPbs2dM4lUtPT1dKxEeNGlWrVq0rV64opWEqlapOnTo//PCDXLz2yJEjPXr0mDt3bu4u4E88jxDi+++/l6ncggUL9u3bN3PmzNdff13ORV2yZElSUpJytr/++ks+cPr06crONm3abNu27erVq1u3bhVCODs7Hzp0qH///t27d585c6YQQunHVKlSJSGETqf78MMPZSonrV27VrZmv3Tpkuxd6+TkNGjQIHt7+8jISLngRZUqVYQQt27dkvNbP/nkE+XruHr16i1ZsuTixYujRo0y9w8ZAAAAAACLRcUcyjDZ/Eg2dbKxsWnXrt3BgwcnTJhw7ty5BQsWXL58+bPPPqtYsaI8eMeOHcricSbfR7377rtyemmHDh0OHToUHBy8ePHi+Pj4TZs2bdq06Z133hk7dqyyHuoTz5OTkyP7qU+aNElpwBQcHKwctnLlSqU47s6dO0KIevXq5e6y9Pfff8vIbPXq1cq9gwcP7tu3r1x7Tgghq/NmzZolG9JPmjRpxIgRnTt3FkIcP368cePGX375pTxSo9H4+voan1+uyqesOicTSQAAAAAAUGKomEMZpiz9pqRLderUCQoKevPNN4UQO3fuHDFixMOHD+VdR48eFUJMmjRpzpw5yvprarV61qxZEyZMUM5pY2MzcODAX3/9deXKlTKr+vbbb728vGTdWUHOc/v2bVkcp8R2MTExxn3iN27cKFeRE0IkJyc/7tVlZWXleYySygkh5BPJVG7x4sUzZ8587rnnBg0aJP6Z0Hro0CEhxLJly4yr7p2dndevX9++ffv8BwAAEELk5OSwIC8AAACKCcEcyjClw5GsO5NsbW0///xzucpDRETEqFGjMjIysrOzQ0JChBDdu3cfN27cX3/99euvv4aEhJw/f37ixInGrYj27dun0+lUKtWAAQMOHz789ddfy0XWFi5cuGTJkoKcR6nRW7t27blz59auXTto0CCdTufs7Lx582aZ5b355puXL18W/6Rvt27dyv3qmjZtKjdGjRq1a9cuWR5o4v79+3Jj6NChb7zxhtyWLSx+/fXXixcvyj39+vXz9/f/+++/f/755/Dw8BMnTihd25X47+7du+b+eQJAqaPX62NiYq5du5aenm7usQAAAMACEcyhDFPaIVlbm/4mDx8+XC7Qdu7cuZCQEOWAgwcPCiFUKpWLi0vjxo1NuoPfuHFj6tSpHTp0WLt2bUpKirW1dZ8+fYKDg2XL1xUrVij1Zfmcp2bNmt7e3kKIxYsXDx48+PPPP9fpdG5ubrt27erateuGDRuEEBqNZvDgwTt27JC5mE6nM154TqpSpcrixYvl9gcffPDCCy+8995733zzzalTpx48eCCESEtLk5NqnZ2dP/nkE+WBPXr0UKvVOp3uxo0bco+s8rO3t3dzczPpIaXkfVevXjX3zxMASp379+/v3r37xx9/zP2vNAAAAPDsCOZQhrm7u6vV6latWuW5Plrnzp03b94shHBwcLCyspLLvW3YsGHXrl0mRxoMhlu3bsXGxjo4ODg6Oup0us8///yFF17o0qVL//79X3zxxR9//FEemZOTU5DzfP75571791b2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" + "7d0ca3af-e391-4d23-981a-dd640e08e4c1.png": { + "image/png": "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CCCQL0BQme/BJwQQQAABBIoKEFQWZWElAggggEAnAgSVnQCxGQEEEEAAgUiAoDLC4C0CCCCAAAKlBCyoZDKEUkKsRwABBBAoJkBQWUyFdQgggAACCBQXIKgs7sJaBBBAAAEE8gQIKvM4+IAAAgggUKEAQWWFUBRDAAEEEEDACxBUchsggAACCCBQgYAFlRMmTHCDBg2qYA+KIIAAAggg4NxZZ53lRo8e7XjGMXcDAggggAACnQsQVHZuRAkEEEAAAQQcQSU3AQIIIIBAVwQIKruixj4IIIAAAu0qQFDZri3PdSOAAAIIVCzw0EMPuWHDhoXy06ZNq3g/CiKAAAIIIEBQyT2AAAIIIIBA5QIElZVbURIBBBBAoE0FCCrbtOEzctm5XM699tprbr755nO9evWquNZfffWVe+edd9wCCyzgevToUfF+FEQAgeoECCqr86I0AggggEB7CxBUtnf7c/UIIIAAAhUIEFRWgESRpgh89913bvfdd3d33XWX23DDDd2FF15YcT2032233eZmn312d+ONN7p+/fpVvC8FEUCgcgGCysqtKIkAAggggABBJfcAAggggAACnQjEQSWT6XSCxeaGCjz//PNugw02SM751FNPuVlmmSX5XO7NEkss4T7//PNQRKHlMcccU6442xBAoIsCBJVdhGM3BBBAAIG2FCCobMtm56IRQAABBKoRIKisRouyjRSYPHmyO/DAA8MpZ5ppJvf444+7GWaYoaIq7LvvvqEnpQqvueaa7rLLLqtoPwohgEB1AgSV1XlRGgEEEECgvQUIKtu7/bl6BBBAAIEKBAgqK0CiSFMEdt11V3fHHXeEc++2227u2GOPrbgeZ555pjv77LND+eWWW84p9GRBAIHaCxBU1t6UIyKAAAIItK4AQWXrti1XhgACCCBQIwH7kqnDMfS7RqgcptsCH3/8sVt66aWT4+h5kwMHDkw+d/bm6KOPdpdffnkotuCCC7q777677C56HuZbb73lXn/99TBpz2KLLeb+3//7f2X3YSMCCDhnf0NWWWUVN378eEgQQAABBBBAoIwAQWUZHDYhgAACCCAgAfuSqfcElVJgSYPAtdde6w466KBQlUUWWSTpWVlp3eKh36X2f++998Jxb7rpJvfggw92OPQ999zjBgwY0GF9qRUKOz/66KPwHM3pp5++VLGw/vrrr3d65uY+++zjZptttqTsp59+GgLTvn37hgmApptuumQbbxBIo4D9DSGoTGPrUCcEEEAAgbQJEFSmrUWoDwIIIIBA6gTsS6YqRlCZuuZp2wodccQR7qqrrgrXrzDv8MMPr8pim222ScLHTTbZxJ133nnJ/ppkR/f92LFjk3XF3owbN84NHjy42Ka8de+8844bM2ZMUl9tXGaZZULQWmx/haKqn5bf/e53TqGqAs4TTzzRXXPNNWG9/kfP5TzhhBPc0KFDk3W8QSBtAvY3hKAybS1DfRBAAAEE0ihAUJnGVqFOCCCAAAKpErAvmaoUQWWqmqatK6Nw7uGHHw4Go0ePdltssUXFHgoiNeu3LXvuuac76qijwsdPPvnEbbzxxu6NN96wzR1eFRD269cvTMbT2eQ9+plR2FhqGTFihNt+++3zNt9///1u2223Deu22267EMKut9567t13380rpw9rrLFGMoS9w0ZWIJACgfhvyLRp01JQI6qAAAIIIIBAegUIKtPbNtQMAQQQQCAlAvGXTILKlDQK1XBrr722e/XVV4PEpEmT3Iorrlixyp133ul22WWXpPwZZ5zhhg0bFj4feuihbuLEick2vdl0003dkCFD3OKLL+7mmGMO16NHj7ztpT5cffXV7rDDDsvbrGM8//zzeeuee+650DvSVsZBpQLZ77//Pq8n5dxzz+1+/vOfh16WClg33HBD25VXBFInEP8NIahMXfNQIQQQQACBlAkQVKasQagOAggggED6BOIvmQSV6Wufdq3R8ssv7z788MNw+Qr25p133oop4ol0tNPtt9/uFl100bD/oEGD8nouqsfipZdeGibQqfgEvqCeX7njjjsmuyhIVc9P1VNhzU477ZQErZpgRMNibXn22WfdRhttZB+T19lnn91pyPuWW25ZdX2Sg/AGgQYLxH9DCCobjM/pEEAAAQQyJ0BQmbkmo8IIIIAAAo0W2Hrrrd2UKVPCaQkqG63P+UoJxEHlY489VvEM3JrQRrOFa/i3FoV/jz76aNJL8rLLLnPHHnts3mnVC1LPh1xppZXy1pf6oB6Q66+/vnvppZdCkVVXXdX96U9/cn369Amfde511lknCURvuOGGvBnMNYnPyiuv3OHwt956q9Ns4ywIZEmAoDJLrUVdEUAAAQSaLUBQ2ewW4PwIIIAAAqkXIKhMfRO1ZQXjoPKWW24Jw7Irgfjb3/7m9tprr6Ro/HxKW1ksrNS2Nddc0w0fPtwtt9xyVrToq2bs3n///ZNtCi1333330JtSw9VHjRoVwlEV0PMuFbRaiKl13377rVt44YX1Nlk0JD0+ZrKBNwikXICgMuUNRPUQQAABBFIlQFCZquagMggggAACaRQgqExjq1AnPTfyySefDBCnnHJKMvlMOZmPP/449HSMJ6W566673EILLdRhNwWKer6kelsWLgosDzjggKLPxVRvyrXWWqvsZDzx8UaOHFl01m5N9hP3+tRM4J1N3BMfl/cIpEWAoDItLUE9EEAAAQSyIEBQmYVWoo4IIIAAAk0ViIPKgw46KPQoa2qFODkCXiAOPzS5jCbIUe/EUssXX3zhdt55Z/fQQw8lRTRRjYLCcouCSvWwvO666zoU05Duww8/3C277LLJtpdffjkM69YK1WfJJZdMZidPCv3w5qSTTnI77LBD4erwOZ4saO+99w7PpixakJUIpFwg/lnlGZUpbyyqhwACCCDQdAGCyqY3ARVAAAEEEEi7AEFl2luoPetXOOHM9ttv70444QTXs2fPDiAffPCBU9j38MMPJ9vmm28+d+ONN7pZZpklWVfuzZtvvukuvPBCd/nll3copuHgBx54oJtuuumcemgqENUyePBgN27cuDDLt4LU119/3fXu3Ts8j/JXv/qVU8Baaol7jJYLNEvtz3oE0iJAUJmWlqAeCCCAAAJZECCozEIrUUcEEEAAgaYKEFQ2lZ+TlxDI5XJO92bcQ1Izays0XGSRRUJvxueeey70hCwMFzWBzuTJk53CymKLZgEfOHBg0e1vv/22O//88zsElr/73e/cvvvu65544gm3+eabJ4d9/PHHw4Q9yYoK3wwZMiQZdn7IIYeEoeYV7koxBFIlQFCZquagMggggAACKRcgqEx5A1E9BBBAAIHmC8w///xJJRj6nVDwJgUCr732WngeZDVVWXDBBUPIOM888xTd7bPPPgvDtbVxm222CeHjvPPO26GshrBqJvA77rgj2TZ16tQQkP7iF79I1ik8VVAaT5aTbCzzJg4q1113XXfJJZeUKc0mBNIrQFCZ3rahZggggAAC6RMgqExfm1AjBBBAAIGUCRBUpqxBqE6ewJQpU9yuu+6aTDyTt7HgwyabbOJOPfVU17dv34ItP358//33O0ySoxm7V199dde/f/8woY0mzHnrrbecZhvX8yttueCCC9xGG23kxo4d60aMGGGrnYLLY4891q288spheLg2aEKfp556yinc1H/PPPOM22+//cIEPtq+3Xbbufvuu09vQw/ROBANK/kfBDIiQFCZkYaimggggAACqRAgqExFM1AJBBBAAIE0CxBUprl1qJsE1Lvxj3/8Y15oaDKLL76408Q06qG48MIL2+qSr999953bcsstkxnFSxYssuGGG24Iz5/89ttvwyQ5mqk7XjS5zoABA5x6gtqM3vH2NdZYIxlSHj/rcs8993RHHXVUXJT3CGRGgKAyM01FRRFAAAEEUiBAUJmCRqAKCCCAAALpFiCoTHf7ULsfBTRsWxPnfPnll2GlnkFZbibwH/fMf6cZwtVTcsyYMUUDxfzS//dpr732CjNza0IdLQo8TzvttNC78v9KlP9fzSB+8sknOw1Nt+Xmm292f//738PM4j/72c9sNa8IZEogDionTJjgBg0alKn6U1kEEEAAAQQaKUBQ2UhtzoUAAgggkEkBgspMNhuVroGAej1eddVVYQj2008/7T788MPkqJqxWz8bGhKuZ0iq52ax5cUXXwzPl9TQ7Xh/Teiz2mqrhdBmhRVWCJP3WMhZ7DisQyCrAgSVWW056o0AAggg0AwBgspmqHNOBBBAAIFMCRBUZqq5qGwdBdTT8ptvvnEzzzyz69WrV9VnUk9PhZ/q5Vnt5DpVn4wdEEiJwEMPPeSGDRsWakOPypQ0CtVAAAEEEEitAEFlapuGiiGAAAIIpEWAoDItLUE9EEAAgewJEFRmr82oMQIIIIBA8wQIKptnz5kRQAABBDIiQFCZkYaimggggEAKBQgqU9goVAkBBBBAILUCBJWpbRoqhgACCCCQFgGCyrS0BPVAAAEEsidAUJm9NqPGCCCAAALNEyCobJ49Z0YAAQQQyIgAQWVGGopqIoAAAikUIKhMYaNQJQQQQACB1AoQVKa2aagYAggggEBaBAgq09IS1AMBBBDIngBBZfbajBojgAACCDRPgKCyefacGQEEEEAgIwJxULnKKqu48ePHZ6TmVBMBBBBAoNkCBJXNbgHOjwACCCCQJQGCyiy1FnVFAAEEEGiKAEFlU9g5KQIIINASAgSVLdGMXAQCCCCAQIMECCobBM1pEEAAAQSyK0BQmd22o+YIIIBAswUIKpvdApwfAQQQQCBLAgSVWWot6ooAAggg0HCB+AumTs7Q74Y3ASdEAAEEMi0Q/x2ZMGGCGzRoUKavh8ojgAACCCBQTwGCynrqcmwEEEAAgcwLxF8wdTEElZlvUi4AAQQQaKhA/HeEoLKh9JwMAQQQQCCDAgSVGWw0qowAAggg0DiB+AumzkpQ2Th7zoQAAgi0gkD8d+Sggw5yw4cPb4XL4hoQQAABBBCoiwBBZV1YOSgCCCCAQKsIxF8wdU0Ela3SslwHAggg0BiB+O8IQWVjzDkLAggggEB2BQgqs9t21BwBBBBAoAEC8RdMnY6gsgHonAIBBBBoIYH47whBZQs1LJeCAAIIIFAXAYLKurByUAQQQACBVhGIv2DqmggqW6VluQ4EEECgMQLx3xGCysaYcxYEEEAAgewKEFRmt+2oOQIIIIBAAwTiL5g6HUFlA9A5BQIIINBCAvHfEYLKFmpYLgUBBBBAoC4CBJV1YeWgCCCAAAKtIhB/wdQ1EVS2SstyHQgggEBjBOK/IwSVjTHnLAgggAAC2RUgqMxu21FzBBBAAIEGCMRfMHU6gsoGoHMKBBBAoIUE4r8jBJUt1LBcCgIIIIBAXQQIKuvCykERQAABBFpFIP6CqWsiqGyVluU6EGiswOeff+769OnjevTo0dgTc7amC8R/Rwgqm94cVAABBBBAIOUCBJUpbyCqhwACCCDQXIH4C6ZqQlDZ3Pbg7AhkVWD55Zd3m2++uTvuuOOyegk1r/fTTz/tbrrpJvf++++7GWaYwS2++OJuiy22cDPNNFPNz9XMA8Z/Rwgqm9kSnBsBBBBAIAsCBJVZaCXqiAACCCDQNIH4C6YqQVDZtKbgxAhkWmCJJZZwffv2dfqdwuLcRRdd5E455ZQOFGuuuaa77LLLOqzP8or47whBZZZbkrojgAACCDRCgKCyEcqcAwEEEEAgswLxF0xdBEFlZpsyr+K5XM7dd999IThadtll87bxof4CH3zwgbvjjjvcVltt5Xr16lX/E6bgDAoqNfz7lVdeafo1v/DCC+711193G2ywQVNkHnvsMbfllluGcw8aNMgttdRSbsYZZ3Rnn312WDdt2rSm1KteJ43/jhBU1kuZ4yKAAAIItIoAQWWrtCTXgQACCCBQF4H4C6ZOQFCZz/zxxx+79957zw0cONB9+eWX7txzz3Wzzz67m3766UMQMsccc7hdd9216cFMfq2d++KLL8Iw07nnntvdf//9Da/fJ5984m644QZ32223uRdffNG9++67bvjw4U4hRjssI0eOdOecc47T69ChQ9vhkp0FlfqdovuumYuGoD/xxBPunnvucQMGDGh4VfbZZ58w5Luw96R+n3z22WdunnnmaXid6nnC+O8IQWU9pTk2AggggEArCBBUtkIrcg0IIIAAAnUTiL9g6iQElT9Sq3eYemS98cYbP64s8u6oo45ye+65Z5EtzVtlQaVq8Pjjj4dwtRG1UU+xCy+80F155ZVFT5eG3nZFK1bjlRZUKsRul2c2quegAulbb73VLbbYYjUWre5wFlRefPHFbv31169u526W/u6779xCCy0UjnL++ee7jTfeuJtHTP/u8d8Rgsr0txc1RAABBBBorgBBZXP9OTsCCCCAQMoF4i+YqipB5Y8N9uGHHzpNEFJsWWSRRdwss8ziFlhgAbf99tu7tA2vjoPKRvQqk5V6EI4bNy7hktE777wThgNr8pAjjzwyWCUFWviNBZUa/nvWWWe18JX+eGlrr722e/XVV93NN9/sllxyyR83NOGdBZV/+MMf3G9+85uG1kAGstDy1FNPhd8TDa1AE04W/x0hqGxCA3BKBBBAAIFMCRBUZqq5qCwCCCCAQKMF4i+YOjdBZX4LaPiohnzPNtts7re//a1TIHfmmWe6IUOG5BdM2ScNvdZz8bSoR5eek6hn9mlRaKggrVbDT5988km38847Bxsd/4ADDnDbbbedm3POOZ2elfnpp5+GZ2VqW7ssp512mrvgggtCT9Zf//rX7rXXXgs+mvlZIdZ+++3XchSbbrqp072gIf9LL710U69P9/wzzzzjlllmmTAkXT15Fd7rOZF77723Gzx4cN3qp5m+N9lkk3Du66+/vm7nSdOB478jBJVpahnqggACCCCQRgGCyjS2CnVCAAEEEEiNQPwFU5UiqCzdNDa0NQ09xkrVUj3IJk2aFIbgliqj9VdccYVbffXVQ5Fvv/3WTZw4MYQrmrW5mkXDfPfYY4+wy3zzzecuueQSt+iii1ZziKaW/f77790jjzzilltuOde7d+9QFw3d1VLtJDgKw/Qczpdffjn0Ig0HKfI/uo8mTJhQZEu2Vymc1gROkydPDp61vpo333wz/KNBfH9988034XmxOpfuRQXE6tFYbjn44IPdgQceWK5It7Y9//zz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V9my7bDxgw8+CJME6We+nh76XXDttde6mWaayT333HOZorV7ZrPNNnNjxozJVN2zWNn4bwhBZRZbkDojgAACCDRSgKCykdqcCwEEEECg5gL6AliqF2U1E+aUq1j8JZOgspwU22opoAl/nnjiCXfPPfe4AQMGlDy0hU66T+eee+6S5dplw8iRI90555zj9KrHPdRrWXfddd1LL72UuaDyww8/dHpcgJaNN97YqWcoS30F4r8hBJX1teboCCCAAALZFyCozH4bcgUIIIBAWwpU0ouymglzyiHGXzIJKstJsa2WAhZUXnzxxW799dcveWjd5++++6679dZbw0zeJQu2yQYLKnfddVdXr+d2qgerAmItWetR+fe//z2Z8Z4elY35oYj/hhBUNsacsyCAAAIIZFeAoDK7bUfNEUAAgbYVUEg5evTo8Ey+N998M8+hqxPm5B2k4ANfMgtA+NgQAQsq//CHP7jf/OY3Jc+59tpru1dffdXdfPPNbskllyxZrl02WFC55ZZburPOOqsul60h+WuuuWY49nzzzRee91iXE9XhoBMnTgzPptSht99+ezdixIg6nIVDxgL8DYk1eI8AAggggEB5AYLK8j5sRQABBBBImYAN89aX7XiZd955XXcmzImPVfieL5mFInxuhICG5T7zzDNumWWWCb339AzKL774ws0444xu7733doMHDw7V2HTTTd2TTz4ZZnFeeumlG1G1VJ/jtNNOcxdccIGbffbZ3a9//Wv32muvOQ13nmGGGZxC3f3226/b9f/HP/7h1BtRi3q0qpdcVpaxY8cm4eQhhxziDjjggKxUPbP15G9IZpuOiiOAAAIINEGAoLIJ6JwSAQQQQKB6ARvqrZ6Uhb0oazFhTrka8SWznE72tn300UfuuuuuC8OpSz3T8auvvnK5XM716dOnqgv89NNPnSbB+eUvf5ns9+2334ZJXaabbrpkXak3Gr6toE09JMstBx98sDvwwANDEZvBefLkyW655ZYrt1tmt33yySdO4eAjjzziHn744RDWHn/88W6BBRYI16QQV5Ncvfzyy2FioVIXWqtQUXWw518OGzbMnXHGGaVOmbr19vxOVUy/T7fYYovU1bHVKhT/DZk2bVqrXR7XgwACCCCAQE0FCCprysnBEEAAAQTqIaAvedaTMg4p1YtSwYNm99VrvZb4SybPF6uXcveP+/HHH7tHH33UPfbYY653794htFMPw1lnnTXv4OpBpqCy1EQiOo7Cb/XAmzJlSjiWDqBeeeqpd+WVV7q+ffs6DS1WSNivX7/k+Hou4h133OHuvfde9+WXX7ojjzwyTIij5xjuvvvunc4Yb70jkwP6N+oZuOGGG4aelUsttZRbeOGF3U9+8pOkyI477hgm3LnmmmvcCiuskKzv7E0l19PZMWy7Ql0NPX/wwQeDh8Kv2MXKFb4qxP3ss8+CZ8+ePQs3B/OTTjopzK5duPHMM88Mvai1XsPjzz333MIibpNNNgkm6pW62GKLhedJdihU4YrvvvsumUX8/vvvd9tuu23Y88ILLwztEx/mm2++cePHj3fqvShn3Wu//e1viwbJsrvqqqtCeQWtGr6vEHr11VePD5n3vjO3vML+Q1z3UaNGJTN9K/j9+c9/nle8lvdF3oHb+EP8N4Sgso1vBC4dAQQQQKAiAYLKipgohAACCCDQDAHrRamQUoFRvFgvSgWU/fv3jzfV/H38JZOgsua8NTmgQrpjjjmmaG+63XbbzR1xxBFJuKeecOoRV+rZguplpmcbKlxUL77pp5/e6ZmECiYV4sSLnlN42WWXJausd6NCs8MPPzyvPqWC0WRn/+aKK65wJ598sltrrbXc008/7d54440Qdu61115xsbz3Fo4q7Fp11VXDNvUwVJimz4ssskheeX2o9Ho67FhkhQLZXXbZJYSU8WY9V1PXIr/CRTOZq80U6mpiGi0KFeVmPU+//vrrMLz6+eefD9ttKLfabeaZZw7l1UZaNDu6HAYOHOj++9//hufXFrZNKNiF/1EvVwWhmuFbddBs37/4xS/cscceG4727LPPhvrYoUt5qK6671R3WzSUX6HkbbfdZquSVwWd+j0XL5W4WXmFkwpRL7/88jDZku4DTcok73HjxrkVV1zRTZo0yYqH11reF3kHbvMP+n2i3ytaCCrb/Gbg8hFAAAEEOhfw/4rLggACCCCAQOoEfDCZ889Py6222mo5H0zk/af12t6oReeyOjTyvI26vqyf56ijjkraR+2kzxdddFFuzz33TNb7kDH33nvvhUv1YXOy3odKeZf/n//8J7f44ouH7X627bDNDzvO+SHVYZ2O40PA8N/KK68c1j333HPJMfyzI8M6O8b++++f82FjzvcazPlecEm5St74noThWH6obtni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NwIIlTwJEMiBgATIDz74wL322mvulVdesUyln376adY7x40b57p165b1eiEuSCzdbbfd3F577eWUzKCcSjmvrZz2Kb6WKVOmuEGDBtnzOHbs2PglziEAAQhAAAIQgAAEIAABCEAAAhkJIFRmxEIlBH4j8PPPP7urrrrK3Xrrre67776rhmW99dZzW2yxhVtnnXUsDt+SJUvc7bff7iTMrLrqqm7x4sVuyy23rHZfISo+/PBD17NnT+v6vffec61atSrEMI3SZzmvrVGAFmHQqVOnupNOOsltvvnm7oEHHijCiAwBAQhAAAIQgAAEIAABCEAAAqVOAKGy1HeQ+ReUwIwZM9yhhx5aZQxZLO6+++5mMdm+ffsq18ILWV7uv//+9lLWjQcddFC4VLBjXMx76aWX3BprrFGwsYrdcTmvrdgsizXek08+6Y455hi3ySabuMcee6xYwzIOBCAAAQhAAAIQgAAEIAABCJQwAYTKEt48pl54Ap9//rnr0aOHWVPKlVsZjFdbbbVaB3744YfdgAEDonayKJNlWSFLXMxTIpM111zTzZo1y+Jmyrpys802c8svv3whp1Cwvst5bQWD1sgdK+t3v3793EYbbeSeeOKJRp4Nw0MAAkkjoPcIsnknbVeYDwQgAAEIQAACEGh8AgiVjb8HzCDhBAYPHuzuuusuSwxy6qmn5jTbX3/91V133XX2Ty7jV155pTvkkENyujdTI7mgK0amkuRI+ImXTz75xMnV+80333SjR4+OX6pyPmzYMPenP/2pSl3SX5Tz2jKxX7RokVOSpvXXX9+1bds2U5OSqQtWxQqP8Mwzz5TMvJkoBCBQeAISKUeNGuVGjBhBdvDC42YECEAAAhCAAAQgUFIEECpLaruYbGMQCEKljkoOkk/5/vvv3TfffGNu2M2aNTPLTImWsm6UtVkqlXJyL1dinoqKCrfNNtu4FVZYwYb4+OOP3aOPPur0gU5tQozMSy65xB199NHWJm5pmG1enTt3tvHkfr711ltna9bg9VqbMpDXtTTU2uo7D80/iM257FtY7zvvvOMktCqGabt27UJ1laME6Mcff9yyxz/77LPu/ffft+t6BiT0tWjRokr78ELJneTer2drxx13bBBRU8+b4rEed9xx9hxqLD17ejY7dOhgz86yyy4bpmBHWezOnDnTrbLKKq5Tp04unun+3//+t/vDH/5g8VvVT76ltjXWZU/ynQPtIQCBwhBQSBQJlRMmTMCqsjCI6RUCEIAABCAAAQiULAGEypLdOiZeLAJBqDzxxBPdeeedFw0rIUXijiwdP/vsM7N23Hvvvd3KK68ctUk/eeutt1yvXr3MKvLBBx906jNubXb44Ye7v/zlL5a858ILL0y/3V7HrTOnT5/ujjjiiGrtTj75ZLfffvu5jh07VhGPqjXMoUJC36uvvuokpElw23PPPSMxNf32r7/+2pIJPffccya+yfpTLvPbbrut23fffd1yyy2XfkvW1/VZW0POQxPMdd/Udu7cuU5798gjj+ille23394yscu6MBS1U7IZCZLpRcmZtP648BfavPjii+6cc86JRE3VS4S+4oorsvKVqCexcN68eSYq/v73v3cbbrhh6NKOt912m7vgggssruSxxx5ronx8bmPGjHEHHHCAtRVf/S7ce++9UR/a61tuucVtsMEGVqffC8VyVaiEl19+OWqXy0kua8xnT3IZkzYQgEDxCCBUFo81I0EAAhCAAAQgAIFSI4BQWWo7xnyLTiAIlRKZevfubeLRG2+84ZQsJL2ceeaZ7pRTTomqJWQq4U6wRJOVncRMiTcSLO+44w5rqzhdsriUpaSs02RtGYrETAk+EgllaSchNPQnF/MbbrjB/fDDD26rrbYyazjd99BDD5kVZeijrkdZNZ5wwglOx1A0zj333OMkbIlHSNozbdo0c49fuHBhaFrlqBidmqssR3MpdV1bQ89Dc81l3/bZZx8TKSUaShBU2XnnnSMherfddjMhzy74H9rjYGkoNscff7yTgKhs8SuuuGJGMVgC8GGHHWZd6FmQFeMLL7xgryUc6lmJl2+//dade+65VQTFcF1xI+NhBJSt/vzzz7ckULLs1JpV9thjD7d06VITMTfddNNqawz96aj+ZCGqZ1nPvuK7ap5vv/22NdNzKlfPV155xfXt27daoio1ynWNue6JDcwPCEAgUQQQKhO1HUwGAhCAAAQgAAEIJIoAQmWitoPJJJFAECqzzU0io1yqJTJJjGrTpo01DRaBl156qTvqqKOsTgJWPHmABEtZoUn8C0VWaiEWpq5L3Nthhx3C5RqPmoOs5xrCnU4WlBK+1J+KrPxat27tZE138803uwMPPNCEKQleEp70OhRZXepetVc/Y8eOdRIwJVpJEJOF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dlRAAAIQgAAEIAABCEAAAg1LAKGyYXnSGwQgkCOB2bNnu6FDh5rlZPv27d3BBx/ssKTMER7NIACBghG48cYb3aWXXlqtf9UdddRR1eqzVSxcuNCtttpqdvnZZ591l19+uRsyZIjbbbfdst3SqPU///yza968eU5zSKVS7tdff3XLLrtsTu0L3eiXX35xzZo1c8sss0ydhspn7XUaoERu+umnn9wPP/zg2rRp43R+5plnuh9//NGNHDnSLbfcciWyCqYJAQhAAAIQgECpE0CoLPUdZP4QKEECEilHjRrlJk6c6CRSdunSxaluxowZrmvXrg537xLcVKYMgTIgMGXKFHv/CUsJQmMQHV9++eVwqcbjO++84/bee297n/vjH//ohg0b5iZMmOBOOukkd84552S9V6LQK6+84mRt/vHHHzuN+9VXX1mM3lNPPdVtttlmWe+tzwUJdfvuu6/73e9+Z3Ouqa+vv/7a1iGL07vvvtttuummNTUv+DWxPu6449yKK67opk6d6lq0aJHXmA8++KA777zz3A033OB22GGHnO+VkJfvWDl3Xo+G1113nRs3bpyJ6nvssYfbaKONcu7ttNNOcy+88IL9LZ4zZ47r3r273fv666+7lVZaKed+aAgBCEAAAhCAAATqQwChsj70uBcCEKgTAVlSIlLWCR03QQACdSTw0UcfuVVXXdUtv/zy7tNPPzWX7nbt2kW9SXhSIi+Jg3vuuadTEi+JM99//737n//5H7PYO+GEE6L2NZ08+eST7phjjnHHHnusu+iii+xcdeqzb9++0a0S/ST4vfrqq+6ll15yTz31VHQt/aRz5872vpler/nKCm7ddde1BGSydKyoqMjZOlL9afwDDjjAun7jjTdc27Zt04eJXkvYO/nkk+21RLHevXtH18KJxNZbbrnFBMRtttkmVBfkeMEFF7jbbrvN+pbIttZaa+U1jkTOadOmuT59+rjKysqs94qrrP4nTZpkguh3333n9tprLzd27Nis99R2QX28++67FlqgNmtWWbEuWrTILV261K2++uquZcuWGbv//e9/79RvKBLbN9lkE7f77ru72oRL7eWbb75pz6K+PDzwwAPNKjhXgT6MyRECEIAABCAAAQjUhwBCZX3ocS8EIJA3AX3Ik/ujiqwnVbCkNAz8gAAEGoiARMfzzz/fffbZZ07CjQTBO+64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zAlInNf+UyCQD4GTTz7ZnhtvUW639e/fv8r7yrnnnpvy4TSs7tprr63StYS88L670047pT766CO7Ht6bttlmm6i993qp0q+PMRy990WNYif5ttetei/V+154H9Tc9HclfMmj+vS/A9OnT4/eq3Vd78kSHzV3fdGjOr0O5YmYsCtBVGPqX+CgdVEgAAEIQAACSSSAUJnEXWFOJU1AwmP4j6c+jMWLRMy4taXaZrK4jN/DOQQgkCwC+vAYfsf1wTle9CHQZ+RO/fjjj1YdPjyqvSwIQwnWiPogOWbMGPtwrQ/PPmxE6t133w3NMh5lMRME0TAPffCUpU2mUlv7MEd92I1baIYP8GEMHb27rg3h3cEjBrJSihef7Ty6NmXKFLs0YsQIq/Mu3ykfi9KsyPUhW4LDrbfemvIJfOJdcF7mBBAqy3yDC7A8rNixYi/AY0WXEIAABCCQUALEqCwtT31mm3AC8SQ6mmo802968hwyfCd8M5keBDIQ8AKkxdVbuHChXb3zzjudFxgztPytShm0t9xyS4vjOHr0aIvHqCvKpK2ixDV77723ndf045133nE//PBDlezbn332mVOSHv1TUfZsZR6vqKhw+bT/9ttvozmccsopbo899rBYld6d2/o99dRTLRanEuccdthhzlv9OMXdPPjgg+264mnuv//+lhhH2cJ1LZTXX3/drbTSSnbP9ddfbzEu77//fte8efPQhGMTJKCs9krQ9MknnzTB1bPkuhD461//6nyICrv1X//6l1t99dXt/JJLLnE33XSTxdW9++67LT7upZdeau81iqsbit6jFAs3W0mPwat2em9UvF09r6Eofq9ij/fs2dMts8wyodqOubaP/13Q/L0FetSP/xLKxlSF4v4+99xzFo84nohMcYO9NWl0j06U2E1xgI866iin9U+bNs0p3nG2okRu8XVla0c9BCAAAQhAoFEIJFRAZVoQKEkCwUpElkdxt+8QjytYJsmqsjZLSrmDy028tnYlCYpJQ6BECSg2Wfg9VqyvuAVitiUFd0UvAkZNgmu0LCu9sBfV60R9yjpSlonvvfeeXfPJeGxc3adYlvESj9UmS0uVfNrHXSLD2sJRrom6HlzYVe9FWqvzH7AjFqF9/OgT40TT1BrDNcXkXbJkSXRNJ3JT9x/IzdoS68oqaMryRfhbWZaLY1ENTgAr9lQKK/YG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8MNml112sSnGguyMM85YotQ3J1XLhZyvGFN7orKqav7NVT+ZzYcffmgmTpxolllmGTNgwIBknum6UAjsscce5pZbbrFpeuGFF8zUU09dqPTVOTGuJhOwKNsEuc7lx7wTgTIjoIXzKpKUUjY6n7Dj5F+Q4ZMIEAEgUCZZEJpjuETtySeftAQYiJlRo0aZBRdc0PZtPg3Dyy67zBx66KG2sLfffntz9NFHl7rgl1hiCfPJJ5/YPDzzzDNm+umnL0R+IGNdeeWV5oorrgjTJwmDlj7GHpp0ERg9erQ55JBDbKD77beflaHSjSHf0LRcWOV5WVxUa01UupPXKqn5x60APnd33HFHuDoxePBgs9NOO5kll1zSLLDAAj7ntCs4Arvttpu57bbbbCrvuusus/DCCxc8xfVKnl49k5yzLxIk+CQCRCALBPT8pw6TYZ1f4MkFoSxqFcMkAuVDoCx9w9dff21AOMpZkz6kr776arPSSis1fbr22mtDAmeTTTYxp556apObMlloohKalYsuumjPkv+///3P3HjjjebSSy81jz32WMt03HTTTWappZZq6YYfkyGgicoNN9zQnHHGGckCKKBrLRfWfWG1tkRlWQamXrQfrYGn459vvvnsqiO0LNdZZx2z7LLLmimmmEI74XsBEZAtEkha3Tu8AhaPTZJezYdFHYiDopYF00UEqo6Anv/Uqa/RmgooY5KVVa/pzB8RaI1AWfoEaA9ieyu2ubYyG2+8sTnttNOanNx6661m9913t/Y/+tGPzLnnntvkpkwWmqi87rrrzPLLL5978vv6+uz2c5z5+dJLL7WNH+dpPvDAA2baaadt65YO4iOgicrVV1/d3skQ33dxXYpcWKc5mq80SFR+iwo1mCZXjw022MBAlb6dQae72WabmS233NIstthi7Zzze48Q+NWvfmVX+xD9mWeeaVC+NMVCQBMHkjKSyoIEn0SACKSFgO5r6jgBLgsxkVZ5MxwiQASiEdD9IVwVURaExh52tt13331hRkB2rbvuuuaDDz4wd999d2iPF5/W3v3332+22247664KZI4mKi+88EKz5pprNmCQ9Y8nnnjCnvn51FNPNUSFclljjTUMzqjEeYkgmGGHi0xxLih2J9Kki4AmKqGtivpfBaP7pjrLg7UkKjlRbd2EtQZea5eTvw4dOtTAX96DxeQU8C0KgQMP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ooptueglln763DqAGEhWpo8zQyQCLgJawCdR+Q06n3/+uVlkkUXsjxdffNEMHDjQPPzww1abcvDgwWbs2LEujLn9fu+99wzKbIMNNjAgVKH1CYP0TT311LmlQ0f0v//9zyy00ELW6qqrrjIrr7yy/sz3CiFQxPoHeHU/ht9Vn0fWiqjkZBBVmoYIEIGyIaAHJh5hUbbSY3qJgDEuQVX1ySXLPBqBn/70p2b8+PENDkicNMDBH0QgdQT0PAqBcy5lzEcffWSWXnppi/VTTz1lZpppJnP33XebHXfc0Sy++OLm1ltvTb0c4gZ4xBFHmIsvvtgSM1999ZVZddVVrddXXnnF9OvXL24wqbp76aWXzLBhw2yYJ5xwgiV0U42gZoG9++675s477zRbbLGFmXLKKQuV+yLWPwBUt36s0kQlClS0kPDOiSBQyN7wvJPsMY4bA1ZL+/fvX7gBIG7603BXhfN29MDEyXUatYJhEIH8ECBJmR/WZYmJZGVZSorprBICWiYs22IR5vOPPfaY+fDDDw22oYJYPProo80UU0zRcRG98847ZqWVVrL+n3/+eTPNNNOYm2++2fzyl780yy+/vLnuuus6Drtbj4ceeqi57LLLzA033GC+/PJL85Of/MT0WsvztttuM7vttpvN2jHHHGO22267brNZa/8jR440p512msFzyy23LBQWRax/AEjLg7VQwOuruJlvvvn65G/rrbeueG57n71PP/3U4h2sxIWJefDBB63d3nvvHdoV4eU///lPH/6qbIYPH9637rrr9gWDfEfZDLY59P3rX//qyG9RPBW1/iXB55RTTrFtiH1YEtTolgj0HgFpuzIPCTTpep8opqAQCKA/l3ohT/bxhSgaJqKiCOg2V6a+eNy4cX2Qq6SfkOfjjz/eVUm9/PLLYZgS0JVXXmntdthhB7HqyfOQQw6x6UA5XXvttfYddr00J510UohXoJTTy6RUIm7B86ijjorMz8SJE/v23HPPvrPPPjvSTRYfilj/kE89p8R71Y2pegb1oISOnSZbBIJzRMJOHO8wwcHM1i5YDcs28jahByrmfTfeeKN1BUI1WC20f3ivqkHnjnr/xhtvxMoiCM2rr766TzAJVlWt/2DlNpb/IjoqSv3rBhvpx+owKHWDE/0SgSIhoCeU6IfLJBgXCccqp0X6diEe8IQdDREgAukjoNtb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qLz//vtNv379Mq9CSJn5KuQCGiCgz4ca3XvSpEnuaIzs3QBUDoEAArkXIKjMfRVzgQgg0AqBal3B1VJsmWWWMXqcffbZW1EczoFApgSyGlaq1aFaH2rkcnVdz8sUdsl/8803TY8ePfJyaam9DrXIX2ihhVz51llnHdf1W0G4pmmnndaMHDnSzDbbbO55Fv4jpMxCLVHGZgroc6FCSv0LQ0pG9m6mOsdGAIG8CBBU5qUmuQ4EEEhcwHcFVwskdQXXcz8poNxkk02MuvoQVnoVHhH4r0AWg427777b7Ljjjmbo0KHmnHPO+e/FNHhOIdZzzz1nxo8fb5588kkzzTTTmCOPPDK6P2YjT/fLL7+Yueee2x1S90tUiMzUPIFTTz3VXH/99ebDDz+sepIrr7zSrLDCClW3ScvKLP4sp8WOcuRDQJ//1Iry2muvjS5If6xWSKk/bDEhgAACCFQXIKis7sNaBBBAoG4BfUDVh1P9FT0eVg4ZMsQMHjzYaKRg3cuSCQEE/isQBhz6QjdixIj/rkzh3DXXXGMOOOAAF1YeccQRdZfw559/Nt988437XdC9e/d2+6v7tQa2+cc//tFu3emnn2423njjdsu1QF2Htf755583q666qtloo43MuuuuW3ZbLfz111+NP//3338fDZ6z3XbbmaOPPrrifpVWvPrqq+app54y77zzjjvWIoss4kYO9+eotJ+Wv/322+bOO+90rdAXW2yxapt2uK4j3/gBPv74YzPVVFOZ6aefPr6qzfM33njD+T744IOui7Z8hw8fbqaeeuo224VPfvrpJ3c/YwWS2267rWulqtHi9V4Qn/r3729WW201IzfdA7VPnz6mW7du8c1S+Tz8Gc7ifWdTiUqhMiVASJmp6qKwCCCQUgGCypRWDMVCAIFsC1QKKxloJ9v1SumbLxAGHWkPK8877zxz0kknmT322MMceOCBDkcjgU822WRVoRQk3nDDDUYtMhVWaVKrzLPPPjva98cffzTrr7++UeinSYOrKGycY445zHTTTee2V5fg+HTyySebc889N77Y3HjjjSYe/OnYxx9/vHnooYdc92K12FPL75133tntf9VVV5nll1++3bEqLVDQd+ihh7rrim+j1pkXXHBBNPiQ1n/99dfuHp9rrbWWK4eCv6233jra9ZFHHulUC/RafKOT2Jl7773XtYhVuKpJ7gp633//fdfdOhyB27eidRsG/x1yyCFm1113jZacf/755uKLLzajR482AwYMMAp95axp++23N0cddZTRa2Xfffd1LfBXXnllc/XVV7v15erKrUj5f1n62U05JcXLqACD5mS04ig2AgikToCgMnVVQoEQQCAvAgor1QWcruB5qVGuo1UCWQk8TjjhBKNASuHhn/70J9eCUK0gNbjOEkssYVZffXWz5pprtmEbNWqUOfHEE90yBY3zzjuv+c9//uNaP+60007m8MMPd+tefPFFF0bqibbTYD29evVy6yr9d8kll7gu4VqvAFL3z1Q4eOaZZxqFgTq3n15++WWz9tpr+6dlH9XNvKNz+h0nTJjgAj4fvM4333zuucJIhbK+a7OC1M0339ztJisN3KPrUytKtTb3+2sDtVbdc889/SlqeqzVVwdTUKhQ95RTTml3bIWXCoYVIPrgV93vFWJq2mqrrcx+++1nXnjhBddCUtegeR9qqiXsRRdd5I4tmwsvvLDNObRsiimmaLNMrxWFx2qNv9RSS7VZl/YnWfmZTbsj5cuuQDykZNCc7NYlJUcAgRQI2A9pTAgggAACTRSwN1Ev2dY5peWWW65kA43on57bL7ol2320pG2YEEDgvwL6mfE/L5pP43TwwQdHZfRltQFdm2VXXHFFVHQb2EXrbMBZst2s3bpw+RdffOGW2e7YpfD4Oq4NIqN9ooP+PqP9/Lm9l+1WXtp9993dOW3LvWgX2yW6ZAf4csttK8vSAw88UPrggw9Kr732WsmGl1EZn3nmmWifjmZskBrtp3Pars7RLjrf5ZdfHq2fOHGiW/fvf/87WubPa4PBkg1r3XLbsjM6Ri0zoWNHvjqeDQ+j89uu1iU7OFLps88+K911112le+65x62Tj58222wzt8y2Oi3Ze3m6f/4Y4Xba/phjjnHb+uvS68PejzKqo5deeskfNnq0Iajbx4a20bIszGThZzULjpQxuwL2j9KlYcOGuZ9f/azbluglewug7F4QJUcAAQQSFtBfk5kQQAABBJosoCDStq4p2VZN0QdZH2wosNQXPcLKJlcCh8+cQNoDkL322sv9PCsgVGD1yiuvlBQwfvnll6UddtjBrVOAZVtMlhTW+SDx0ksvbVMXYZhl70fZZp1tpVPyAZZ+Z+h42v+7775rs50duKHN7xZt58+n/VQ2P9mWktG2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ww8/NO+//77p0aOH6dOnjzn77LNNt27dWlKWJE4Svv79+fMSUvrr0SOBZahR3Hl9BgpH9SawLu5rgStHAIHsCRBUZq/OKDECCCBQlwCtK+viys3GYUBZ7j6UfiRv3YdS3XEVWBZhCsMawsrm1fgdd9xhTjrpJPP2229XPcm+++5r9t5776rbdGXlt99+a0aMGGGeeuop8+mnn1Y8lFpZqrXxzDPPXHGbLK8IX/f+OvIYUvpr0yOBZahRrPmw7mlFWay652oRQCAfAgSV+ahHrgIBBBCoKlBr60p9cVVgVZTQqipaRlfWGlD6Lt5ZHiins1UUhjaElca1Khw/frx58cUXXcvCxRdf3Mw333xmqqmmakessO/HH39024UrNaq3LI855hiz8MILm/XWW888//zz4SZGYeBaa63lWlYutNBCZp555nGtGdtsZJ+88MILbsALtbhUa0xtG59KpZJrmanfVfFynnLKKebLL780xx9/vPHdveP7r7jiiu72F2rlqYGjVLa8TuHr3V9j3kNKf516DEOrcLkM/O9B3Y+XKfsC+qwTHzBHv5fUe4QJAQQQQCA7AgSV2akrSooAAgh0WaBaYKkv/Gp5oC9um266KWFll7VbewACyvq8w/CmqGHlDz/8YM4880xz7rnnlsWT0UYbbRStUziocEddphUmTj/99NG6888/35xwwgmuK/fBBx9srrzyShcUrrzyyi4Afe+998whhxxidt1112ifcjMnn3xyu/Istthi5rzzzjOzzjprtIvuwbv//vtH5/Mrfv75ZxeA6rmC119++cWN9P3rr7+aueee29x6661uUwWrk002md8tt4/h69xfZJFCSn/NeiSwDDXyNx+vX73O9budEDp/dc0VIYBA/gUIKvNfx1whAggg0E5AX9p/+ukn90/z4dS9e3cz+eSTR//0nCm9AgqPVIcKaBTK6J+WnXXWWebiiy92o3arxZhvOVTEFpSVai8McYoWVup1om7/vmv2tNNOa3bYYQcz5ZRTmnvuucc8++yzjm2rrbYyRx11lGv5+MEHH7igUituvPFGowDRT+pefcMNN7gWkPHg87jjjjMXXnihOeCAA8yee+7pd2n3+Oijj5otttjCLVdLR92e4KGHHnLPFVLedNNNplevXu75gQceaMaOHWsUhF566aXRsd555x23TAvU/XzgwIHRus8//9wsuuii7rnuk6nfc3mewtd3eJ0TJ04MnxZuPh5oeQAF7WuvvbbRz8IUU0zhF/OYcgG9/+mPLt9//717L9Rnlqmnntq1tObzS8orj+IhgAACFQQIKivAsBgBBBBAAIEsC+j+gM899xwBZQeVGIY5RWpp9s0337guz+KZa665jFoohvdnVJij4FL3eNSAFBtuuKEJg8QJEyZEYU4YYA4fPtyceOKJbdTVFfucc84xO++8sznssMParAufaKAbDXijVpynn366a/Go4HHjjTd295dUGVQWTVqm+07Gw88jjjgiCi41iJgGyfGTggwfXOpnY8YZZ/SrcvcYvq7Di1O4Swuz30TigaWCyi233DLkYh4BBBBAAAEEEhAgqEwAnVMigAACCCDQbIH777/ftSihBWXH0mGoU5SwUq1u+/bt63DGjBkTtZQMta6++mqjbtzLLbec0bwfNbt///7m7rvvjjZVF2wFnZq23nproxaU4aRwUcbbbLONOfbYY6NVX3zxhQsjfRdyvVYVjMbDNL2Wt912W7efwslZZpnFtdzUSOJqqTl48GC3Tq0kwxHENYhU7969o/Pp3przzjuve/7kk09GrTO1QF3BvUe0Q0ZnwtdzeAlx13Bdked9YLnuuusSVBb5hcC1I4AAAgikRoCgMjVVQUEQQAABBBBonIC6wsUHGWnc0fN3pDDcKUJYGbYuVBDYr1+/dpWqcFDrll56aRdEhgPTvPnmm647eBgi6gAaLGfUqFFtjnXBBRe4+1Xq3rcjR45069QNe7XVVjNLLrmk0XoNfqNBeDQp0NSgPP4ekuqmrvtLalJYqvkNNtjAdU9XkLrbbru5Wx5owAzfZV3bjhs3zg0KpHk/zTnnnG5W1+Lndf9LtUC+/fbbo1amfvusPYav47DshJShRvn5jz/+uE14XX4rliKAAAIIIIBAswUIKpstzPERQAABBBBAIBMCYciT97BS4Z8CSI3ivd122xl1mfb3c1N3a93jVPec1HTbbbeZBRdc0IRdvNUyUttrZG21glQX7FNPPdWNnq1Wj926dYvq3A984wNPrVAoqIBxyJAhLqhUi8aVVlop2kfdk48++mjXVTsMQzUYjkYB/9vf/mauuOIKV66DDjrItcLUutVXX92opabKoPJsttlm0TE1oxHNdc2+Fanub6cyqCt7uWCzzc4pfxK+fsOiElKGGswjgAACCCCAQNoFCCrTXkOUDwEEEEAAAQRaJhCGPXkPK9Xy0d9PUgOIzDPPPOaTTz5xo3p7cA1UowFr/LT77ru74NI/16NG6tbAPAoBNd1yyy1R60g91+A8ut+lJo0GrpZrCjgVGPr7X7766qtmzTXXdEGnuoL7QX7cTr//pyBz9OjRriWnRvQeOnRouNq1nlQX9bPPPttcdNFFLrTU9uGkc+hcw4YNc/fCvO6661xr0TnmmMO1HvVhbbhPFubD121YXkLKUIN5BBBAAAEEEMiCAEFlFmqJMiKAAAIIIIBAywTC0CfPYeV//vMfF+hdfvnl5r333ot8NcK2BrZRmDfDDDNEyzXz008/uWDyH//4h2sB+de//jXqNn7kkUeaSy65xLWQVCtFP6mFo0bxjk8KHjUyvcLBl19+2Y24rO0uu+wys99++0X3wVSIqtHA1XIyHI15/Pjx7p6XX3/9tdGo7QoudSy1ztTozRpIJ7yXps6vQXrOPPPMeFFcgLrCCiu0W56FBeHrNSwvIWWowTwCCCCAAAIIZEWAoDIrNUU5EUAAgS4KqKXU888/b7777jt370IFAuFAE108PLsjkCuBMPzJc1jpK033iNR9TRUEdmU0bLVWVMvMHj16+EO7R3X/VpCpbuKLLbaYG7REo3hPPvnkbr1vUal7Vl5//fVumcqkLuozzTRTm2PV8kT76j6Y8QFytHynnXYyGkxHAajua6l7cfp7YNZy7DRtE75Ow3IpuB0xYkS4iPkEBXj/TRCfUyOAAAIIZE6AoDJzVUaBEUAAgfoFNDJufCReHUXLNEpvrZO6avrQ4JFHHjEnnHCCa/kUjrRb67HYrj6Bn3/+2QVJPXv2NJrXPQHVuk1BxZRTTlnfwdi6JoEwBCpCWFkTShc20h9JFFRq1O749Nprr7l7RYb3sYxv08jnH374oftdFrbQbOTxW3Gs8PUZno+QMtRIfp733+TroKsl4P23q4LsjwACCNQnQFBZnxdbI4AAApkTUBdNfXH1kw8afej4zDPP+FVVH32LJ39PuQMPPNANYLHrrruaQw45pOq+Sa1U11aNbqyWU1mf1MVWrcAee+wx8/7775vll1/eXdILL7xgdE8/puYIhGEQYWVzjHXUf/3rX+41Pd9887lBbZp3pnwcOXxdhldESBlqJD/P+y/vv8m/CikBAgggkD0Bgsrs1RklRgABBCIBjc77xz/+0UwzzTTuHnNTTTWVmW222aL1agWwzDLLuEErBg8ebEaOHOlCLYV3GjFXI/PuuOOO0fbVZvzIuxohWKPxqruklumYm266abVdE1u33nrrue6fuk9d1lsdrrPOOuall15yoxkr1Nlggw1ci7Bag+bEKiEHJw5DIcLK5lSounir+7X+qPDKK6805yQ5OWr4egwviZAy1Gj+PO+/1Y15/63uw1oEEEAAgcoCBJWVbViDAAIIpEpAoePhhx/uRsydf/75je61pkEw4tOxxx5rttlmG7f48ccfdwNi6Mkdd9xhBg4cGN+85uc+qNSgFieddJJZccUVXTh68803lx0oo+YDN3FDOamr6VVXXRW1QIyfTq0uNYKwBg2Zc84546tT89wHlY8++qh5+umnzV577WXU5V6DkTA1XyAMhwgrm+M9aNAgN+L4nXfeaQYMGNCck2T8qOHrMLwUBs4JNRo/z/tv/aa8/9Zvxh4IIIAAAr8JEFTySkAAAQQyInDbbbeZ3XffvcPSarTdCy64wG33v//7v67Fo0bxVWjZlUnh2EYbbWRWX311M3r06CjUe+qpp8rec64r52rUvv6L0qhRo8xaa61V9rC6lmOOOcatU8vL/v37l90u6YUKnx944AFzyy23uC7gCqR969aky1aU84chEWFl42tdtze46aab3IjjRx11VONPkPEjhn94Ci+FkDLUaM4877/1u/L+W78ZeyCAAAII/CZAUMkrAQEEEMiIgFpQDhs2zHWNVOs/hWqaVl55Zbd8uummc12BdY83denWpHtHqjWhRrlVa8yuTGp5qC7f6p6p0EyB5YQJE4y6v/nzdeX4zdjXf1E67LDDXMj63nvvuRaW6iI/xxxzmD/96U9Ggd9FF13kTq9A95577knlPS2fffZZc9ZZZ5nTTjvNjB8/3vzlL38xf/vb39xjM+w4ZnkBwsryLo1Yetddd7nfVfpjgf/91ojj5uEYhJTJ1iLvv/X78/5bvxl7IIAAAgj8JkBQySsBAQQQyKiA/xKg1pNqRVluOvjgg83VV1/twjjfHbzcdp1ZpuBMIaVaWaZp+vzzz83zzz/vushrwJ9Kk7qu6z6dX3zxhTn++OPNNddc4za97777zFxzzVVpt1QsVzdEhauy79WrVyrKVKRCEFY2p7b1ut5vv/3cLSpqaT3enFKk76iElOmrE95/y9cJ77/lXViKAAIIIFCfAEFlfV5sjQACCKRCoFQqmb59+7qylI5vbMwAABqMSURBVOuu/Ouvv5qPPvrIqOu3gspDDz3U7LLLLi0puwbFePPNN80HH3zguoerBWZHk65nsskm62izDtfrutXCVC0nK01qcbrwwgubFVZYwehm/37SFyxNM844o1/UsMevv/7a6F+fPn3KHlPlVhf6r776yg1+9Ic//KHsdixMjwBhZXrqIs8lIaRMX+3y/lu+Tnj/Le/CUgQQQACB+gUIKus3Yw8EEEAgcQG1AlxkkUVcOV5//XWjrszhdM4555hTTjklWjR06FDXUkmDxXTv3j1a3ugZBZR77rmnefXVV6NDa7RxdVmeeuqpo2WaefDBB939Fh966CEX0C211FJmySWXdF3KFSbGJ7XgVNDnWxBqcKDzzjvPXc+FF17oRj//7rvvTLl9NejMrrvuahZccMG6unXXck5fzjfeeMPoPmZq5RgflEfdtFVe3edz5pln9ru4R3XjVhd9daP3k46hAYuyPlK5v568PhJW5rVm03FdhJTpqId4KXj//W1wPt5/468MniOAAAIINEqAoLJRkhwHAQQQaKGAgsA111zTVBokR8HdcccdV7ZESy+9tFlooYVci0K1PmzU/SUVPG699dZlz7nttttGA9boXl+6L+S1115bdlst1L0wtY9vZamRu9XVTtPbb79t7r33XncvO7fA/rfhhhuaM844wz1VIPjkk08ajSCs+znKKhwJ3e8TPur+mxMnTjT9+vWLFtdzTu208847G41WvNtuuxl1uQ8nH1xeeumlrsWnX6cRvDWKuqZpp53WLLDAAq7seq77auqYTOkWCMMkBthJd11lrXSbb765eeyxx9oUm4Fz2nAk8oT3X95/E3nhcVIEEECgSAK2+wITAggggEDGBGwgVrKDwZTsqM9lS25bFpbs/RdLiy22mNtO25b7t/7665fdv96FL774YnT8ZZZZpjRu3LjS+++/X7KtK93yTTbZJDqkvVdmtK1t/ejKab/4lWwX9pId9Cdap31tN3K3n713XbTctkAsab/49dhuZ9E5/Iw//5lnnukXlX20gaY73iuvvBKtr/ecum6VyY4gHh1DM7alZFTWm2++OVr3f//3f9HyHXbYoWS7hrt1J554olu+zz77RNsyk24BGyZFdanXgJ4zIdAVATtwWpvXFK+rrmg2dl/ef3n/bewriqMhgAACCMQFTHwBzxFAAAEE0i9w+eWXuy+xCrWqTTfeeKPbzracLCmEe+aZZ0qXXXZZad999y0pPOxo/2rHDtf5QFABom2ZGK367LPPSgoBdV4/KVz1IWMYDPr1Dz/8cLT+1ltvdYu/+eabaJkPKRXC6rj+WHZgH3+I6NEOzOHW28FyomXlZmyLS7edHVAnWl3POcNQ85///Gd0DM0ocPRltAP1ROv8ObVOIe2nn35asiOORyHsqFGjom2ZSb8AYWX66ygrJSSkTHdN8f47h/sjKO+/6X6dUjoEEEAgywIElVmuPcqOAAKFFbD3L3Th13XXXVfV4K677nLbKahs1qSWjD6IUyvOjibfavLwww+vuKkPM33rxEmTJkXn8OfyIaftau3W2ftDtjveQQcd5NbFz6XWnnbwmmh7ew9Nt10YaNZzzp9++ikq33PPPRcdV+fw5dWjDyrtPc7aLA+30bzqS0EpU7YECCuzVV9pLC0hZRprpW2ZeP+dw/3hUyq8/7Z9bfAMAQQQQKAxAgSVjXHkKAgggEBLBewI3i7o8sFXpZPbeyBGgVilbRqx3Adt9j6RHR7O3nfNlalSa87XXnstKrO9p6M7nsI/fw49htd9ySWXVDzeCSec4Nbtv//+Ubl+/PFH12pRLTN91/IxY8a47dZee+1ou3rOqeP48l111VXuGN9//70LHP1yPfoWm3bQIbe9HXm8ZAcTKql1pcLJIUOGuFauCjKZsilAWJnNektDqQkp01ALHZeB99/7IiTefyMKZhBAAAEEGihAUNlATA6FAAIItELgpZdeiroHq1uxAj892oFXSnYAlpJaAvpJLQd9UPb555/7xQ1/tKNUR+dREHn77be7ezO+9dZbJd2/UuHNCy+84M57xBFHuG0VFOpa/KQWhAom/X01N9tss9IPP/zgVuuel/46Tj31VL+Le3ziiSfcunL32zz33HPdOpXPTz681fn9pK7X/vh2YB23uN5z6vw6xrrrrlu66aabSv657j+59957u3U+MA3v6fnhhx/6YvCYEwHCypxUZAsv4/TTT49+B/nfRXodMaVLgPdf3n/T9YqkNAgggEA+BRj1u0gjJ3GtCCCQaQGNYH3llVcaey/DqtdhB44xG2ywgdvGvnW5kaQ1gvUNN9xgllhiiar7dnalRkHdcsstOyzbyy+/bD755BOzyiqrRKfSaNdTTTVVm30XXHBBY1s5mp49e7rt7H0r3fG1rR1Mx42Q7Q/w1VdfuVHM9dzeM8vMNNNMfpXRCLkHHnige27v+ejOYQMB97jHHntE61Qu25rSHdd2KXfb13tOjfgdH6Xbhq7m4osvNja4NEceeaQ7vg0pjR3syNjBd4zqRdc6evRo07t376jc9p6XbrRy2/LSrLrqqmaGGWaI1jGTDYFwNHCVmNGas1FvSZTyf/7nf4xtjd7m1Lxe2nAk/oT33y3d+xfvv4m/FCkAAgggUAgBgspCVDMXiQACeRBYfPHF24R5/poWWWQRM++885qFF17YaF7BV/fu3f1qF5DZ7lnGdjt24Vi0osEzCiD15dreK9KFbP7wChdt60Wz8cYbm+HDh7vFTz/9tFGg+sADD/jN3ONKK61k7P0pjR7Da9DKa6+91vz5z382gwYNarOPnth7TBoFkbYbdZug8oMPPjDLLrtsu+379+9vrr/+ejP99NO7dbbrtgtPFXLaAYii7es9p22ZaUaOHOn2HzFihBk8eLCZbLLJzJdffmlst25jW08a2yLHBbC21amx9/eKzjV06FAzyyyzmNdff93YVp/Rcns/NLPFFltEz5nJjgBhZXbqKqmSElImJV/feXn/5f23vlcMWyOAAAIIdEWAoLIreuyLAAIItFDAdgN0wd7cc89tevToYWwXZbPaaqtFYVuloiiE++ijj8zss89eaZOGL1dLTjvIjgsbFdRVmtRy0I4MrtuQuFaDalnZmUn761+3bt3a7X7hhRea4447zi2fa665XMtMBX8KUMPJ3rvS2K7mHXr6faqd028TPurYtiu8a+HqlyuQtCOwuwDTL/OPKt8222xj9tprr3Zl9dvwmH4Bwsr011FSJSSkTEq+/vPy/lvZrNp7Ie+/ld1YgwACCCBQWYCgsrINaxBAAAEEciJgB6cx+te3b9/UXZGCZHufTRdiKizt16+fGTBggAuWq4W8qbsQClRRgLCyIk1hV8RDSrX8tvcaLttivLBIXHguBHj/zUU1chEIIIBASwUIKlvKzckQQAABBBBAoIgChJVFrPXy11wupNQ9eZkQQAABBBBAAAEEjCGo5FWAAAIIIIAAAgi0QICwsgXIKT8FIWXKK4jiIYAAAggggEDiAgSViVcBBUAAAQQQQACBoggQVhalpttfJyFlexOWIIAAAggggAACcQGCyrgIzxFAAAEEEEAAgSYKEFY2ETelhyakTGnFUCwEEEAAAQQQSJ0AQWXqqoQCIYAAAggggEDeBQgr817D/70+Qsr/WjCHAAIIIIAAAgh0JEBQ2ZEQ6xFAAAEEEEAAgSYIEFY2ATVlhySkTFmFUBwEEEAAAQQQSL0AQWXqq4gCIoAAAggggEBeBQgr81qzxsRDyn322ceMGDEivxfMlSGAAAIIIIAAAg0QIKhsACKHQAABBBBAAAEEOitAWNlZufTuR0iZ3rqhZAgggAACCCCQbgGCynTXD6VDAAEEEEAAgQIIEFbmp5IJKfNTl1wJAggggAACCLRegKCy9eacEQEEEEAAAQQQaCdAWNmOJHMLCCkzV2UUGAEEEEAAAQRSJkBQmbIKoTgIIIAAAgggUFwBwsrs1j0hZXbrjpIjgAACCCCAQHoECCrTUxeUBAEEEEAAAQQQMISV2XsREFJmr84oMQIIIIAAAgikU4CgMp31QqkQQAABBBBAoMAChJXZqXxCyuzUFSVFAAEEEEAAgfQLEFSmv44oIQIIIIAAAggUUICwMv2VTkiZ/jqihAgggAACCCCQLQGCymzVF6VFAAEEEEAAgQIJEFamt7IJKdNbN5QMAQQQQAABBLIrQFCZ3bqj5AgggAACCCBQAAHCyvRVMiFl+uqEEiGAAAIIIIBAPgQIKvNRj1wFAggggAACCORYgLAyPZUbDynHjh1rBg0alJ4CUhIEEEAAAQQQQCDDAgSVGa48io4AAggggAACxREgrEy+rgkpk68DSoAAAggggAAC+RYgqMx3/XJ1CCCAAAIIIJAjAcLK5CqTkDI5e86MAAIIIIAAAsURIKgsTl1zpQgggAACCCCQAwHCytZXYhhSLrvssmafffahu3frq4EzIoAAAggggEABBAgqC1DJXCICCCCAAAII5EsgDCsVnI0ZMyZfF5iiq4mHlFinqHIoCgIIIIAAAgjkToCgMndVygUhgAACCCCAQBEECCubX8uElM035gwIIIAAAggggEAoQFAZajCPAAIIIIAAAghkSICwsnmVRUjZPFuOjAACCCCAAAIIVBIgqKwkw3IEEEAAAQQQQCADAgRqja8kTBtvyhERQAABBBBAAIFaBAgqa1FiGwQQQAABBBBAIMUCBGuNq5zQUoPmjBgxonEH50gIIIAAAggggAACVQUIKqvysBIBBBBAAAEEEMiGQBiwMcBO5+osNCSk7JwheyGAAAIIIIAAAl0RIKjsih77IoAAAggggAACKRIIgzbCyvoqJrQjpKzPjq0RQAABBBBAAIFGCRBUNkqS4yCAAAIIIIAAAikQCAM3wsraKiQ0I6SszYytEEAAAQQQQACBZggQVDZDlWMigAACCCCAAAIJCoTBG2Fl9YoIrQgpq1uxFgEEEEAAAQQQaLYAQWWzhTk+AggggAACCCCQgEAYwOn0Y8eONYMGDUqgJOk95eabb24ee+wxV0BCyvTWEyVDAAEEEEAAgeIIEFQWp665UgQQQAABBBAomABhZeUKJ6SsbMMaBBBAAAEEEEAgKQGCyqTkOS8CCCCAAAIIINACAcLK9siElO1NWIIAAggggAACCKRBgKAyDbVAGRBAAAEEEEAAgSYKxMPKonZzfvzxx80ZZ5xBd+8mvtY4NAIIIIAAAggg0BUBgsqu6LEvAggggAACCCCQEYGih5UKKYcNGxbVVlHD2giAGQQQQAABBBBAIIUCBJUprBSKhAACCCCAAAIINEOgqGElIWUzXk0cEwEEEEAAAQQQaLwAQWXjTTkiAggggAACCCCQWoGihZXxkHLZZZc1Y8aMSW39UDAEEEAAAQQQQKDIAgSVRa59rh0BBBBAAAEECilQlLAyfp2ElIV8uXPRCCCAAAIIIJAhAYLKDFUWRUUAAQQQQAABBBolEA/x8nbPxvj1EVI26pXDcRBAAAEEEEAAgeYJEFQ2z5YjI4AAAggggAACqRaIh3l5CSvj10VImeqXIYVDAAEEEEAAAQQiAYLKiIIZBBBAAAEEEECgeAJ5C/Xydj3Fe0VyxQgggAACCCBQZAGCyiLXPteOAAIIIIAAAghYgbyEe/HryEsLUV6kCCCAAAIIIIBAUQQIKotS01wnAggggAACCCBQRSAe8mnTsWPHmkGDBlXZKz2rNt98c/PYY49FBSKkjCiYQQABBBBAAAEEMiNAUJmZqqKgCCCAAAIIIIBAcwWyGlYSUjb3dcHREUAAAQQQQACBVgkQVLZKmvMggAACCCCAAAIZECgXVqa1deLjjz9uhg0b1kY1rWVtU0ieIIAAAggggAACCJQVIKgsy8JCBBBAAAEEEECguAJZCCsJKYv7+uTKEUAAAQQQQCC/AgSV+a1brgwBBBBAAAEEEOi0QJqDwCwEqZ2GZ0cEEEAAAQQQQKDAAgSVBa58Lh0BBBBAAAEEEKgmUC6sXHbZZc2YMWOq7dbUdYSUTeXl4AgggAACCCCAQKICBJWJ8nNyBBBAAAEEEEAg/QLxwWpU4laPCK7Q9IwzzmgzsncS5Uh/bVFCBBBAAAEEEEAguwIEldmtO0qOAAIIIIAAAgi0TCDJloyVWnZq4JxBgwa1zIATIYAAAggggAACCDRXgKCyub4cHQEEEEAAAQQQyI1AEmFluXMm3f08NxXKhSCAAAIIIIAAAikTIKhMWYVQHAQQQAABBBBAIM0C5Vo3qrxq3ThixIiGFb1SV+9Gn6dhBeZACCCAAAIIIIAAAl0WIKjsMiEHQAABBBBAAAEEiidQrqVjo0LEcvfEVCtKunoX73XGFSOAAAIIIIBAsQQIKotV31wtAggggAACCCDQMIFyYaUO3tnAstLx6OrdsCrjQAgggAACCCCAQKoFCCpTXT0UDgEEEEAAAQQQSL9ApYBRgaVCxmoD3lTq4q2rphVl+uueEiKAAAIIIIAAAo0UIKhspCbHQgABBBBAAAEECixQKbAUiULHZZZZxuk88cQT7vGxxx5zj/H/CCjjIjxHAAEEEEAAAQSKIUBQWYx65ioRQAABBBBAAIGWCCis1HTGGWfUfT4CyrrJ2AEBBBBAAAEEEMiVAEFlrqqTi0EAAQQQQAABBNIjoG7dajVZKbRUMKmJQXLSU2eUBAEEEEAAAQQQSFKAoDJJfc6NAAIIIIAAAggUTEDhpaZq960sGAmXiwACCCCAAAIIIPC7AEElLwUEEEAAAQQQQAABBBBAAAEEEEAAAQQQSFyAoDLxKqAACCCAAAIIIIAAAggggAACCCCAAAIIIEBQyWsAAQQQQAABBBBAAAEEEEAAAQQQQAABBBIXIKhMvAooAAIIIIAAAggggAACCCCAAAIIIIAAAggQVPIaQAABBBBAAAEEEEAAAQQQQAABBBBAAIHEBQgqE68CCoAAAggggAACCCCAAAIIIIAAAggggAACBJW8BhBAAAEEEEAAAQQQQAABBBBAAAEEEEAgcQGCysSrgAIggAACCCCAAAIIIIAAAggggAACCCCAAEElrwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQSFyAoDLxKqAACCCAAAIIIIAAAggggAACCCCAAAIIIEBQyWsAAQQQQAABBBBAAAEEEEAAAQQQQAABBBIXIKhMvAooAAIIIIAAAggggAACCCCAAAIIIIAAAggQVPIaQAABBBBAAAEEEEAAAQQQQAABBBBAAIHEBQgqE68CCoAAAggggAACCCCAAAIIIIAAAggggAACBJW8BhBAAAEEEEAAAQQQQAABBBBAAAEEEEAgcQGCysSrgAIggAACCCCAAAIIIIAAAggggAACCCCAAEElrwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQSFyAoDLxKqAACCCAAAIIIIAAAggggAACCCCAAAIIIEBQyWsAAQQQQAABBBBAAAEEEEAAAQQQQAABBBIXIKhMvAooAAIIIIAAAggggAACCCCAAAIIIIAAAggQVPIaQAABBBBAAAEEEEAAAQQQQAABBBBAAIHEBQgqE68CCoAAAggggAACCCCAAAIIIIAAAggggAACBJW8BhBAAAEEEEAAAQQQQAABBBBAAAEEEEAgcQGCysSrgAIggAACCCCAAAIIIIAAAggggAACCCCAAEElrwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQSFyAoDLxKqAACCCAAAIIIIAAAggggAACCCCAAAIIIEBQyWsAAQQQQAABBBBAAAEEEEAAAQQQQAABBBIXIKhMvAooAAIIIIAAAggggAACCCCAAAIIIIAAAggQVPIaQAABBBBAAAEEEEAAAQQQQAABBBBAAIHEBQgqE68CCoAAAggggAACCCCAAAIIIIAAAggggAACBJW8BhBAAAEEEEAAAQQQQAABBBBAAAEEEEAgcQGCysSrgAIggAACCCCAAAIIIIAAAggggAACCCCAAEElrwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQSFyAoDLxKqAACCCAAAIIIIAAAggggAACCCCAAAIIIEBQyWsAAQQQQAABBBBAAAEEEEAAAQQQQAABBBIXIKhMvAooAAIIIIAAAggggAACCCCAAAIIIIAAAggQVPIaQAABBBBAAAEEEEAAAQQQQAABBBBAAIHEBQgqE68CCoAAAggggAACCCCAAAIIIIAAAggggAACBJW8BhBAAAEEEEAAAQQQQAABBBBAAAEEEEAgcQGCysSrgAIggAACCCCAAAIIIIAAAggggAACCCCAAEElrwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQSFyAoDLxKqAACCCAAAIIIIAAAggggAACCCCAAAIIIEBQyWsAAQQQQAABBBBAAAEEEEAAAQQQQAABBBIXIKhMvAooAAIIIIAAAggggAACCCCAAAIIIIAAAggQVPIaQAABBBBAAAEEEEAAAQQQQAABBBBAAIHEBQgqE68CCoAAAggggAACCCCAAAIIIIAAAggggAACBJW8BhBAAAEEEEAAAQQQQAABBBBAAAEEEEAgcQGCysSrgAIggAACCCCAAAIIIIAAAggggAACCCCAAEElrwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQSFyAoDLxKqAACCCAAAIIIIAAAggggAACCCCAAAIIIEBQyWsAAQQQQAABBBBAAAEEEEAAAQQQQAABBBIXIKhMvAooAAIIIIAAAggggAACCCCAAAIIIIAAAggQVPIaQAABBBBAAAEEEEAAAQQQQAABBBBAAIHEBQgqE68CCoAAAggggAACCCCAAAIIIIAAAggggAACBJW8BhBAAAEEEEAAAQQQQAABBBBAAAEEEEAgcQGCysSrgAIggAACCCCAAAIIIIAAAggggAACCCCAAEElrwEEEEAAAQQQQAABBBBAAAEEEEAAAQQQSFyAoDLxKqAACCCAAAIIIIAAAggggAACCCCAAAIIIEBQyWsAAQQQQAABBBBAAAEEEEAAAQQQQAABBBIXIKhMvAooAAIIIIAAAggggAACCCCAAAIIIIAAAgj8f32IOSxyIyadAAAAAElFTkSuQmCC" } }, "cell_type": "markdown", "id": "39fd1948-b5c3-48c4-b10e-2ae7e8c83334", "metadata": {}, "source": [ - "# Basic Multi-agent Collaboration\n", + "# Multi-agent network\n", "\n", "A single agent can usually operate effectively using a handful of tools within a single domain, but even using powerful models like `gpt-4`, it can be less effective at using many tools. \n", "\n", - "One way to approach complicated tasks is through a \"divide-and-conquer\" approach: create an specialized agent for each task or domain and route tasks to the correct \"expert\".\n", + "One way to approach complicated tasks is through a \"divide-and-conquer\" approach: create an specialized agent for each task or domain and route tasks to the correct \"expert\". This is an example of a [multi-agent network](https://langchain-ai.github.io/langgraph/concepts/multi_agent/#network) architecture.\n", "\n", "This notebook (inspired by the paper [AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation](https://arxiv.org/abs/2308.08155), by Wu, et. al.) shows one way to do this using LangGraph.\n", "\n", "The resulting graph will look something like the following diagram:\n", "\n", - "![multi_agent diagram](attachment:02659c68-8b4b-42ed-a002-1c08f4a7c299.png)\n", + "![multi_agent diagram](attachment:7d0ca3af-e391-4d23-981a-dd640e08e4c1.png)\n", "\n", "Before we get started, a quick note: this and other multi-agent notebooks are designed to show _how_ you can implement certain design patterns in LangGraph. If the pattern suits your needs, we recommend combining it with some of the other fundamental patterns described elsewhere in the docs for best performance.\n", "\n", @@ -37,12 +37,12 @@ "outputs": [], "source": [ "%%capture --no-stderr\n", - "%pip install -U langchain langchain_openai langsmith pandas langchain_experimental matplotlib langgraph langchain_core" + "%pip install -U langchain_community langchain_anthropic langchain_experimental matplotlib langgraph" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "743c19df-6da9-4d1e-b2d2-ea40080b9fdc", "metadata": {}, "outputs": [], @@ -56,7 +56,7 @@ " os.environ[var] = getpass.getpass(f\"Please provide your {var}\")\n", "\n", "\n", - "_set_if_undefined(\"OPENAI_API_KEY\")\n", + "_set_if_undefined(\"ANTHROPIC_API_KEY\")\n", "_set_if_undefined(\"TAVILY_API_KEY\")" ] }, @@ -73,57 +73,6 @@ "" ] }, - { - "cell_type": "markdown", - "id": "5e4344a7-21df-4d54-90d2-9d19b3416ffb", - "metadata": {}, - "source": [ - "## Create Agents\n", - "\n", - "The following helper functions will help create agents. These agents will then be nodes in the graph.\n", - "\n", - "You can skip ahead if you just want to see what the graph looks like." - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "4325a10e-38dc-4a98-9004-e1525eaba377", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_core.messages import (\n", - " BaseMessage,\n", - " HumanMessage,\n", - " ToolMessage,\n", - ")\n", - "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", - "\n", - "from langgraph.graph import END, StateGraph, START\n", - "\n", - "\n", - "def create_agent(llm, tools, system_message: str):\n", - " \"\"\"Create an agent.\"\"\"\n", - " prompt = ChatPromptTemplate.from_messages(\n", - " [\n", - " (\n", - " \"system\",\n", - " \"You are a helpful AI assistant, collaborating with other assistants.\"\n", - " \" Use the provided tools to progress towards answering the question.\"\n", - " \" If you are unable to fully answer, that's OK, another assistant with different tools \"\n", - " \" will help where you left off. Execute what you can to make progress.\"\n", - " \" If you or any of the other assistants have the final answer or deliverable,\"\n", - " \" prefix your response with FINAL ANSWER so the team knows to stop.\"\n", - " \" You have access to the following tools: {tool_names}.\\n{system_message}\",\n", - " ),\n", - " MessagesPlaceholder(variable_name=\"messages\"),\n", - " ]\n", - " )\n", - " prompt = prompt.partial(system_message=system_message)\n", - " prompt = prompt.partial(tool_names=\", \".join([tool.name for tool in tools]))\n", - " return prompt | llm.bind_tools(tools)" - ] - }, { "cell_type": "markdown", "id": "b4b40de2-5dd4-4d5b-882e-577210723ff4", @@ -136,7 +85,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 3, "id": "ca076f3b-a729-4ca9-8f91-05c2ba58d610", "metadata": {}, "outputs": [], @@ -155,7 +104,7 @@ "\n", "\n", "@tool\n", - "def python_repl(\n", + "def python_repl_tool(\n", " code: Annotated[str, \"The python code to execute to generate your chart.\"],\n", "):\n", " \"\"\"Use this to execute python code. If you want to see the output of a value,\n", @@ -180,37 +129,6 @@ "Now that we've defined our tools and made some helper functions, will create the individual agents below and tell them how to talk to each other using LangGraph." ] }, - { - "cell_type": "markdown", - "id": "0c6a8c3c-86a0-46aa-b970-ab070fb787d9", - "metadata": {}, - "source": [ - "### Define State\n", - "\n", - "We first define the state of the graph. This will just a list of messages, along with a key to track the most recent sender" - ] - }, - { - "cell_type": "code", - "execution_count": 64, - "id": "290c91d4-f6f4-443c-8181-233d39102974", - "metadata": {}, - "outputs": [], - "source": [ - "import operator\n", - "from typing import Annotated, Sequence\n", - "from typing_extensions import TypedDict\n", - "\n", - "from langchain_openai import ChatOpenAI\n", - "\n", - "\n", - "# This defines the object that is passed between each node\n", - "# in the graph. We will create different nodes for each agent and tool\n", - "class AgentState(TypedDict):\n", - " messages: Annotated[Sequence[BaseMessage], operator.add]\n", - " sender: str" - ] - }, { "cell_type": "markdown", "id": "911a283e-ea04-40c1-b792-f9e5f7d81203", @@ -218,77 +136,91 @@ "source": [ "### Define Agent Nodes\n", "\n", - "We now need to define the nodes. First, let's define the nodes for the agents." + "We now need to define the nodes.\n", + "\n", + "First, we'll create a utility to create a system prompt for each agent." ] }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 4, + "id": "4325a10e-38dc-4a98-9004-e1525eaba377", + "metadata": {}, + "outputs": [], + "source": [ + "def make_system_prompt(suffix: str) -> str:\n", + " return (\n", + " \"You are a helpful AI assistant, collaborating with other assistants.\"\n", + " \" Use the provided tools to progress towards answering the question.\"\n", + " \" If you are unable to fully answer, that's OK, another assistant with different tools \"\n", + " \" will help where you left off. Execute what you can to make progress.\"\n", + " \" If you or any of the other assistants have the final answer or deliverable,\"\n", + " \" prefix your response with FINAL ANSWER so the team knows to stop.\"\n", + " f\"\\n{suffix}\"\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 5, "id": "71b790ca-9cef-4b22-b469-4b1d5d8424d6", "metadata": {}, "outputs": [], "source": [ - "import functools\n", + "from langchain_core.messages import HumanMessage\n", + "from langchain_anthropic import ChatAnthropic\n", "\n", - "from langchain_core.messages import AIMessage\n", + "from langgraph.prebuilt import create_react_agent\n", + "from langgraph.graph import MessagesState\n", "\n", "\n", - "# Helper function to create a node for a given agent\n", - "def agent_node(state, agent, name):\n", - " result = agent.invoke(state)\n", - " # We convert the agent output into a format that is suitable to append to the global state\n", - " if isinstance(result, ToolMessage):\n", - " pass\n", - " else:\n", - " result = AIMessage(**result.dict(exclude={\"type\", \"name\"}), name=name)\n", + "llm = ChatAnthropic(model=\"claude-3-5-sonnet-latest\")\n", + "\n", + "# Research agent and node\n", + "research_agent = create_react_agent(\n", + " llm,\n", + " tools=[tavily_tool],\n", + " state_modifier=make_system_prompt(\n", + " \"You can only do research. You are working with a chart generator colleague.\"\n", + " ),\n", + ")\n", + "\n", + "\n", + "def research_node(state: MessagesState) -> MessagesState:\n", + " result = research_agent.invoke(state)\n", + " # wrap in a human message, as not all providers allow\n", + " # AI message at the last position of the input messages list\n", + " result[\"messages\"][-1] = HumanMessage(\n", + " content=result[\"messages\"][-1].content, name=\"researcher\"\n", + " )\n", " return {\n", - " \"messages\": [result],\n", - " # Since we have a strict workflow, we can\n", - " # track the sender so we know who to pass to next.\n", - " \"sender\": name,\n", + " # share internal message history of research agent with other agents\n", + " \"messages\": result[\"messages\"],\n", " }\n", "\n", "\n", - "llm = ChatOpenAI(model=\"gpt-4o\")\n", - "\n", - "# Research agent and node\n", - "research_agent = create_agent(\n", + "# Chart generator agent and node\n", + "# NOTE: THIS PERFORMS ARBITRARY CODE EXECUTION, WHICH CAN BE UNSAFE WHEN NOT SANDBOXED\n", + "chart_agent = create_react_agent(\n", " llm,\n", - " [tavily_tool],\n", - " system_message=\"You should provide accurate data for the chart_generator to use.\",\n", + " [python_repl_tool],\n", + " state_modifier=make_system_prompt(\n", + " \"You can only generate charts. You are working with a researcher colleague.\"\n", + " ),\n", ")\n", - "research_node = functools.partial(agent_node, agent=research_agent, name=\"Researcher\")\n", "\n", - "# chart_generator\n", - "chart_agent = create_agent(\n", - " llm,\n", - " [python_repl],\n", - " system_message=\"Any charts you display will be visible by the user.\",\n", - ")\n", - "chart_node = functools.partial(agent_node, agent=chart_agent, name=\"chart_generator\")" - ] - }, - { - "cell_type": "markdown", - "id": "71c7f1b2-24a3-4340-bcb2-feb22e344fb6", - "metadata": {}, - "source": [ - "### Define Tool Node\n", "\n", - "We now define a node to run the tools" - ] - }, - { - "cell_type": "code", - "execution_count": 66, - "id": "d9a79c76-5c7c-42f6-91cf-635bc8305804", - "metadata": {}, - "outputs": [], - "source": [ - "from langgraph.prebuilt import ToolNode\n", - "\n", - "tools = [tavily_tool, python_repl]\n", - "tool_node = ToolNode(tools)" + "def chart_node(state: MessagesState) -> MessagesState:\n", + " result = chart_agent.invoke(state)\n", + " # wrap in a human message, as not all providers allow\n", + " # AI message at the last position of the input messages list\n", + " result[\"messages\"][-1] = HumanMessage(\n", + " content=result[\"messages\"][-1].content, name=\"chart_generator\"\n", + " )\n", + " return {\n", + " # share internal message history of chart agent with other agents\n", + " \"messages\": result[\"messages\"],\n", + " }" ] }, { @@ -303,22 +235,15 @@ }, { "cell_type": "code", - "execution_count": 67, - "id": "4f4b4d37-e8a3-4abb-8d42-eaea26016f35", + "execution_count": 6, + "id": "c30f800f-99dc-4207-ba09-0164e226dade", "metadata": {}, "outputs": [], "source": [ - "# Either agent can decide to end\n", - "from typing import Literal\n", - "\n", - "\n", - "def router(state):\n", + "def router(state: MessagesState):\n", " # This is the router\n", " messages = state[\"messages\"]\n", " last_message = messages[-1]\n", - " if last_message.tool_calls:\n", - " # The previous agent is invoking a tool\n", - " return \"call_tool\"\n", " if \"FINAL ANSWER\" in last_message.content:\n", " # Any agent decided the work is done\n", " return END\n", @@ -337,53 +262,41 @@ }, { "cell_type": "code", - "execution_count": 68, - "id": "4dce3901-6ad5-4df5-8528-6e865cf96cb0", + "execution_count": 7, + "id": "2c4a5ade-5912-494b-bf62-8a99278f9f12", "metadata": {}, "outputs": [], "source": [ - "workflow = StateGraph(AgentState)\n", + "from langgraph.graph import StateGraph, START, END\n", "\n", - "workflow.add_node(\"Researcher\", research_node)\n", + "workflow = StateGraph(MessagesState)\n", + "workflow.add_node(\"researcher\", research_node)\n", "workflow.add_node(\"chart_generator\", chart_node)\n", - "workflow.add_node(\"call_tool\", tool_node)\n", "\n", "workflow.add_conditional_edges(\n", - " \"Researcher\",\n", + " \"researcher\",\n", " router,\n", - " {\"continue\": \"chart_generator\", \"call_tool\": \"call_tool\", END: END},\n", + " {\"continue\": \"chart_generator\", END: END},\n", ")\n", "workflow.add_conditional_edges(\n", " \"chart_generator\",\n", " router,\n", - " {\"continue\": \"Researcher\", \"call_tool\": \"call_tool\", END: END},\n", + " {\"continue\": \"researcher\", END: END},\n", ")\n", "\n", - "workflow.add_conditional_edges(\n", - " \"call_tool\",\n", - " # Each agent node updates the 'sender' field\n", - " # the tool calling node does not, meaning\n", - " # this edge will route back to the original agent\n", - " # who invoked the tool\n", - " lambda x: x[\"sender\"],\n", - " {\n", - " \"Researcher\": \"Researcher\",\n", - " \"chart_generator\": \"chart_generator\",\n", - " },\n", - ")\n", - "workflow.add_edge(START, \"Researcher\")\n", + "workflow.add_edge(START, \"researcher\")\n", "graph = workflow.compile()" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 8, "id": "97f8e0eb", "metadata": {}, "outputs": [ { "data": { - "image/jpeg": "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", + "image/png": "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", "text/plain": [ "" ] @@ -396,7 +309,7 @@ "from IPython.display import Image, display\n", "\n", "try:\n", - " display(Image(graph.get_graph(xray=True).draw_mermaid_png()))\n", + " display(Image(graph.get_graph().draw_mermaid_png()))\n", "except Exception:\n", " # This requires some extra dependencies and is optional\n", " pass" @@ -414,73 +327,30 @@ }, { "cell_type": "code", - "execution_count": 70, - "id": "176a99b0-b457-45cf-8901-90facaa852da", + "execution_count": 10, + "id": "9f478b05-3f09-447f-a9f4-1b2eae73f5ef", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{'Researcher': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_3zDlnDMUkWEJxnHASo59doCL', 'function': {'arguments': '{\"query\":\"UK GDP 2018 to 2023\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 26, 'prompt_tokens': 221, 'total_tokens': 247}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, name='Researcher', id='run-ac6640c6-2bb4-478f-b3c4-eabf98cf4900-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'UK GDP 2018 to 2023'}, 'id': 'call_3zDlnDMUkWEJxnHASo59doCL'}])], 'sender': 'Researcher'}}\n", - "----\n", - "{'call_tool': {'messages': [ToolMessage(content='[{\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp/timeseries/ihyp/pn2\", \"content\": \"Preliminary estimate of GDP time series (PGDP), released on 27 April 2018\\\\nPublications that use this data\\\\nContact details for this data\\\\nFooter links\\\\nHelp\\\\nAbout ONS\\\\nConnect with us\\\\nAll content is available under the Open Government Licence v3.0, except where otherwise stated Year on Year growth: CVM SA %\\\\nDownload full time series as:\\\\nDownload filtered time series as:\\\\nTable\\\\nNotes\\\\nFollowing a quality review it has been identified that the methodology used to estimate elements of purchased software within gross fixed capital formation (GFCF) has led to some double counting from 1997 onwards. GDP quarterly national accounts time series (QNA), released on 22 December 2023\\\\nIHYP: UK Economic Accounts time series (UKEA), released on 22 December 2023\\\\nIHYP: GDP first quarterly estimate time series\\\\n(PN2), released on 10 November 2023\\\\nIHYP: Year on Year growth: CVM SA %\\\\nSource dataset: GDP first quarterly estimate time series (PN2)\\\\nContact: Niamh McAuley\\\\nRelease date: 10 November 2023\\\\nView previous versions\\\\n %\\\\nFilters\\\\nCustom time period\\\\nChart\\\\nDownload this time seriesGross Domestic Product:\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp\", \"content\": \"Quarter on Quarter growth: CVM SA %\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product: q-on-q4 growth rate CVM SA %\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product at market prices: Current price: Seasonally adjusted \\\\u00a3m\\\\nCurrent Prices (CP)\\\\nGross Domestic Product: quarter on quarter growth rate: CP SA %\\\\nCurrent Prices (CP)\\\\nGross Domestic Product: q-on-q4 growth quarter growth: CP SA %\\\\nCurrent Prices (CP)\\\\nDatasets related to Gross Domestic Product (GDP)\\\\n A roundup of the latest data and trends on the economy, business and jobs\\\\nTime series related to Gross Domestic Product (GDP)\\\\nGross Domestic Product: chained volume measures: Seasonally adjusted \\\\u00a3m\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product: Hide\\\\nData and analysis from Census 2021\\\\nGross Domestic Product (GDP)\\\\nGross domestic product (GDP) estimates as the main measure of UK economic growth based on the value of goods and services produced during a given period. Contains current and constant price data on the value of goods and services to indicate the economic performance of the UK.\\\\nEstimates of short-term indicators of investment in non-financial assets; business investment and asset and sector breakdowns of total gross fixed capital formation.\\\\n Monthly gross domestic product by gross value added\\\\nThe gross value added (GVA) tables showing the monthly and annual growths and indices as published within the monthly gross domestic product (GDP) statistical bulletin.\\\\n\"}, {\"url\": \"https://www.macrotrends.net/global-metrics/countries/GBR/united-kingdom/gdp-gross-domestic-product\", \"content\": \"U.K. gdp for 2021 was $3,141.51B, a 16.45% increase from 2020. U.K. gdp for 2020 was $2,697.81B, a 5.39% decline from 2019. U.K. gdp for 2019 was $2,851.41B, a 0.69% decline from 2018. GDP at purchaser\\'s prices is the sum of gross value added by all resident producers in the economy plus any product taxes and minus any subsidies not included in ...\"}, {\"url\": \"https://www.statista.com/statistics/281744/gdp-of-the-united-kingdom/\", \"content\": \"Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\\\u00a0Mio. facts.\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nGDP of the UK 1948-2022\\\\nUK economy expected to shrink in 2023\\\\nHow big is the UK economy compared to others?\\\\nGross domestic product of the United Kingdom from 1948 to 2022\\\\n(in million GBP)\\\\nAdditional Information\\\\nShow sources information\\\\nShow publisher information\\\\nUse Ask Statista Research Service\\\\nDecember 2023\\\\nUnited Kingdom\\\\n1948 to 2022\\\\n*GDP is displayed in real terms (seasonally adjusted chained volume measure with 2019 as the reference year)\\\\n Statistics on\\\\n\\\\\"\\\\nEconomy of the UK\\\\n\\\\\"\\\\nOther statistics that may interest you Economy of the UK\\\\nGross domestic product\\\\nLabor Market\\\\nInflation\\\\nGovernment finances\\\\nBusiness Enterprise\\\\nFurther related statistics\\\\nFurther Content: You might find this interesting as well\\\\nStatistics\\\\nTopics Other statistics on the topicThe UK economy\\\\nEconomy\\\\nRPI annual inflation rate UK 2000-2028\\\\nEconomy\\\\nCPI annual inflation rate UK 2000-2028\\\\nEconomy\\\\nAverage annual earnings for full-time employees in the UK 1999-2023\\\\nEconomy\\\\nInflation rate in the UK 1989-2023\\\\nYou only have access to basic statistics.\\\\n Customized Research & Analysis projects:\\\\nGet quick analyses with our professional research service\\\\nThe best of the best: the portal for top lists & rankings:\\\\n\"}, {\"url\": \"https://www.statista.com/topics/3795/gdp-of-the-uk/\", \"content\": \"Monthly growth of gross domestic product in the United Kingdom from January 2019 to November 2023\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nContribution to gross domestic product growth in the United Kingdom in January 2023, by sector\\\\nGDP growth rate in the UK 1999-2021, by country\\\\nAnnual growth rates of gross domestic product in the United Kingdom from 1999 to 2021, by country\\\\nGDP growth rate in the UK 2021, by region\\\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\\\nGDP growth of Scotland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Scotland in 2021, by local (ITL 3) area\\\\nGDP growth of Wales 2021, by local area\\\\nAnnual growth rates of gross domestic product in Wales in 2021, by local (ITL 3) area\\\\nGDP growth of Northern Ireland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Northern Ireland in 2021, by local (ITL 3) area\\\\nGDP per capita\\\\nGDP per capita\\\\nGDP per capita in the UK 1955-2022\\\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\\\nAnnual GDP per capita growth in the UK 1956-2022\\\\nAnnual GDP per capita growth in the United Kingdom from 1956 to 2022\\\\nQuarterly GDP per capita in the UK 2019-2023\\\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nQuarterly GDP per capita growth in the UK 2019-2023\\\\nQuarterly GDP per capita growth in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nGDP per capita of the UK 1999-2021, by country\\\\nGross domestic product per capita of the United Kingdom from 1999 to 2021, by country (in GBP)\\\\nGDP per capita of the UK 2021, by region\\\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\\\nGlobal Comparisons\\\\nGlobal Comparisons\\\\nCountries with the largest gross domestic product (GDP) 2022\\\\n Monthly GDP of the UK 2019-2023\\\\nMonthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100)\\\\nGVA of the UK 2022, by sector\\\\nGross value added of the United Kingdom in 2022, by industry sector (in million GBP)\\\\nGDP of the UK 2021, by country\\\\nGross domestic product of the United Kingdom in 2021, by country (in million GBP)\\\\nGDP of the UK 2021, by region\\\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\\\nGDP of Scotland 2021, by local area\\\\nGross domestic product of Scotland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Wales 2021, by local area\\\\nGross domestic product of Wales in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Northern Ireland 2021, by local area\\\\nGross domestic product of Northern Ireland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP growth\\\\nGDP growth\\\\nGDP growth forecast for the UK 2000-2028\\\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\\\nAnnual GDP growth in the UK 1949-2022\\\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\\\nQuarterly GDP growth of the UK 2019-2023\\\\nQuarterly growth of gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023\\\\nMonthly GDP growth of the UK 2019-2023\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nUK GDP - Statistics & Facts\\\\nUK economy expected to shrink in 2023\\\\nCharacteristics of UK GDP\\\\nKey insights\\\\nDetailed statistics\\\\nGDP of the UK 1948-2022\\\\nDetailed statistics\\\\nAnnual GDP growth in the UK 1949-2022\\\\nDetailed statistics\\\\nGDP per capita in the UK 1955-2022\\\\nEditor\\\\u2019s Picks\\\\nCurrent statistics on this topic\\\\nCurrent statistics on this topic\\\\nKey Economic Indicators\\\\nMonthly GDP growth of the UK 2019-2023\\\\nKey Economic Indicators\\\\nMonthly GDP of the UK 2019-2023\\\\nKey Economic Indicators\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nRelated topics\\\\nRecommended\\\\nRecommended statistics\\\\nGDP\\\\nGDP\\\\nGDP of the UK 1948-2022\\\\nGross domestic product of the United Kingdom from 1948 to 2022 (in million GBP)\\\\nQuarterly GDP of the UK 2019-2023\\\\nQuarterly gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in million GBP)\\\\n The 20 countries with the largest gross domestic product (GDP) in 2022 (in billion U.S. dollars)\\\\nGDP of European countries in 2022\\\\nGross domestic product at current market prices of selected European countries in 2022 (in million euros)\\\\nReal GDP growth rates in Europe 2023\\\\nAnnual real gross domestic product (GDP) growth rate in European countries in 2023\\\\nGross domestic product (GDP) of Europe\\'s largest economies 1980-2028\\\\nGross domestic product (GDP) at current prices of Europe\\'s largest economies from 1980 to 2028 (in billion U.S dollars)\\\\nUnited Kingdom\\'s share of global gross domestic product (GDP) 2028\\\\nUnited Kingdom (UK): Share of global gross domestic product (GDP) adjusted for Purchasing Power Parity (PPP) from 2018 to 2028\\\\nRelated topics\\\\nRecommended\\\\nReport on the topic\\\\nKey figures\\\\nThe most important key figures provide you with a compact summary of the topic of \\\\\"UK GDP\\\\\" and take you straight to the corresponding statistics.\\\\n Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\\\u00a0Mio. facts.\\\\n\"}]', name='tavily_search_results_json', tool_call_id='call_3zDlnDMUkWEJxnHASo59doCL')]}}\n", - "----\n", - "{'Researcher': {'messages': [AIMessage(content=\"The search results provide some information about the UK's GDP over the past years, but most of the relevant data is either not in a structured format that can be easily extracted or it is behind a source that requires further access for detailed statistics. To proceed with generating a line graph, we need specific GDP values for each year from 2018 to 2023.\\n\\nHowever, one of the search results from macrotrends.net does provide specific GDP values for the years 2018 to 2021:\\n\\n- U.K. GDP for 2021 was $3,141.51 billion, a 16.45% increase from 2020.\\n- U.K. GDP for 2020 was $2,697.81 billion, a 5.39% decline from 2019.\\n- U.K. GDP for 2019 was $2,851.41 billion, a 0.69% decline from 2018.\\n\\nWe still need the GDP values for 2022 and 2023 to complete the dataset for the past five years. I will now conduct a further search to find the missing GDP data for 2022 and 2023.\", additional_kwargs={'tool_calls': [{'id': 'call_nvB1wQyQuNeTrOXQZnEtgNDZ', 'function': {'arguments': '{\"query\":\"UK GDP 2022 2023\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 263, 'prompt_tokens': 3199, 'total_tokens': 3462}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, name='Researcher', id='run-25901401-0d62-485f-b7d5-37e3c159effe-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'UK GDP 2022 2023'}, 'id': 'call_nvB1wQyQuNeTrOXQZnEtgNDZ'}])], 'sender': 'Researcher'}}\n", - "----\n", - "{'call_tool': {'messages': [ToolMessage(content='[{\"url\": \"https://www.statista.com/statistics/281744/gdp-of-the-united-kingdom/\", \"content\": \"Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\\\u00a0Mio. facts.\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nGDP of the UK 1948-2022\\\\nUK economy expected to shrink in 2023\\\\nHow big is the UK economy compared to others?\\\\nGross domestic product of the United Kingdom from 1948 to 2022\\\\n(in million GBP)\\\\nAdditional Information\\\\nShow sources information\\\\nShow publisher information\\\\nUse Ask Statista Research Service\\\\nDecember 2023\\\\nUnited Kingdom\\\\n1948 to 2022\\\\n*GDP is displayed in real terms (seasonally adjusted chained volume measure with 2019 as the reference year)\\\\n Statistics on\\\\n\\\\\"\\\\nEconomy of the UK\\\\n\\\\\"\\\\nOther statistics that may interest you Economy of the UK\\\\nGross domestic product\\\\nLabor Market\\\\nInflation\\\\nGovernment finances\\\\nBusiness Enterprise\\\\nFurther related statistics\\\\nFurther Content: You might find this interesting as well\\\\nStatistics\\\\nTopics Other statistics on the topicThe UK economy\\\\nEconomy\\\\nRPI annual inflation rate UK 2000-2028\\\\nEconomy\\\\nCPI annual inflation rate UK 2000-2028\\\\nEconomy\\\\nAverage annual earnings for full-time employees in the UK 1999-2023\\\\nEconomy\\\\nInflation rate in the UK 1989-2023\\\\nYou only have access to basic statistics.\\\\n Customized Research & Analysis projects:\\\\nGet quick analyses with our professional research service\\\\nThe best of the best: the portal for top lists & rankings:\\\\n\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp\", \"content\": \"Quarter on Quarter growth: CVM SA %\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product: q-on-q4 growth rate CVM SA %\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product at market prices: Current price: Seasonally adjusted \\\\u00a3m\\\\nCurrent Prices (CP)\\\\nGross Domestic Product: quarter on quarter growth rate: CP SA %\\\\nCurrent Prices (CP)\\\\nGross Domestic Product: q-on-q4 growth quarter growth: CP SA %\\\\nCurrent Prices (CP)\\\\nDatasets related to Gross Domestic Product (GDP)\\\\n A roundup of the latest data and trends on the economy, business and jobs\\\\nTime series related to Gross Domestic Product (GDP)\\\\nGross Domestic Product: chained volume measures: Seasonally adjusted \\\\u00a3m\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product: Hide\\\\nData and analysis from Census 2021\\\\nGross Domestic Product (GDP)\\\\nGross domestic product (GDP) estimates as the main measure of UK economic growth based on the value of goods and services produced during a given period. Contains current and constant price data on the value of goods and services to indicate the economic performance of the UK.\\\\nEstimates of short-term indicators of investment in non-financial assets; business investment and asset and sector breakdowns of total gross fixed capital formation.\\\\n Monthly gross domestic product by gross value added\\\\nThe gross value added (GVA) tables showing the monthly and annual growths and indices as published within the monthly gross domestic product (GDP) statistical bulletin.\\\\n\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/gdpfirstquarterlyestimateuk/octobertodecember2023\", \"content\": \"This review covered:\\\\nprocesses and quality assurance in making revisions to GDP\\\\npotential improvements to early estimates of GDP enabled through enhanced access to data\\\\ncommunication of revisions to GDP, the story behind the most recent set of revisions in particular, and uncertainty in early estimates of GDP\\\\nWe have already started work looking into the recommendations of this review and have set out our plans on how we will improve the way we communicate uncertainty.\\\\n Source: GDP first quarterly estimate from the Office for National Statistics\\\\nNotes\\\\nOffice for Statistics Regulation Revisions of estimates of UK GDP review\\\\nThe Office for Statistics Regulation (OSR) have completed a review of the practices around the preparation and release of information about revisions to estimates of GDP in our Impact of Blue Book 2023 article released on 1 September 2023, as announced on 6 September 2023 on the OSR website. Across 2023, the services sector sees revisions for the following reasons, with only Quarter 1 2023 seeing growth revised from our previous publication, including:\\\\nupdated input data for the deflator used for telecommunications\\\\nupdated seasonal adjustment which now uses a complete year of data for 2023\\\\nProduction\\\\nThe production sector is estimated to have decreased by 1.0% in the latest quarter after growth of 0.1% in Quarter 3 2023 (unrevised from our previous publication). Important quality information\\\\nThere are common pitfalls in interpreting data series, and these include:\\\\nexpectations of accuracy and reliability in early estimates are often too high\\\\nrevisions are an inevitable consequence of the trade-off between timeliness and accuracy\\\\nearly estimates are often based on incomplete data\\\\nVery few statistical revisions arise as a result of \\\\u201cerrors\\\\u201d in the popular sense of the word. Construction output in Great Britain: December 2023, new orders and Construction Output Price Indices, October to December 2023\\\\nBulletin | Released 15 February 2024\\\\nShort-term measures of output by the construction industry, contracts awarded for new construction work in Great Britain and a summary of the Construction Output Price Indices (OPIs) in the UK for Quarter 4 (October to December) 2023.\\\\n\"}, {\"url\": \"https://www.statista.com/topics/3795/gdp-of-the-uk/\", \"content\": \"Monthly growth of gross domestic product in the United Kingdom from January 2019 to November 2023\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nContribution to gross domestic product growth in the United Kingdom in January 2023, by sector\\\\nGDP growth rate in the UK 1999-2021, by country\\\\nAnnual growth rates of gross domestic product in the United Kingdom from 1999 to 2021, by country\\\\nGDP growth rate in the UK 2021, by region\\\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\\\nGDP growth of Scotland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Scotland in 2021, by local (ITL 3) area\\\\nGDP growth of Wales 2021, by local area\\\\nAnnual growth rates of gross domestic product in Wales in 2021, by local (ITL 3) area\\\\nGDP growth of Northern Ireland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Northern Ireland in 2021, by local (ITL 3) area\\\\nGDP per capita\\\\nGDP per capita\\\\nGDP per capita in the UK 1955-2022\\\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\\\nAnnual GDP per capita growth in the UK 1956-2022\\\\nAnnual GDP per capita growth in the United Kingdom from 1956 to 2022\\\\nQuarterly GDP per capita in the UK 2019-2023\\\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nQuarterly GDP per capita growth in the UK 2019-2023\\\\nQuarterly GDP per capita growth in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nGDP per capita of the UK 1999-2021, by country\\\\nGross domestic product per capita of the United Kingdom from 1999 to 2021, by country (in GBP)\\\\nGDP per capita of the UK 2021, by region\\\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\\\nGlobal Comparisons\\\\nGlobal Comparisons\\\\nCountries with the largest gross domestic product (GDP) 2022\\\\n Monthly GDP of the UK 2019-2023\\\\nMonthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100)\\\\nGVA of the UK 2022, by sector\\\\nGross value added of the United Kingdom in 2022, by industry sector (in million GBP)\\\\nGDP of the UK 2021, by country\\\\nGross domestic product of the United Kingdom in 2021, by country (in million GBP)\\\\nGDP of the UK 2021, by region\\\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\\\nGDP of Scotland 2021, by local area\\\\nGross domestic product of Scotland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Wales 2021, by local area\\\\nGross domestic product of Wales in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Northern Ireland 2021, by local area\\\\nGross domestic product of Northern Ireland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP growth\\\\nGDP growth\\\\nGDP growth forecast for the UK 2000-2028\\\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\\\nAnnual GDP growth in the UK 1949-2022\\\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\\\nQuarterly GDP growth of the UK 2019-2023\\\\nQuarterly growth of gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023\\\\nMonthly GDP growth of the UK 2019-2023\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nUK GDP - Statistics & Facts\\\\nUK economy expected to shrink in 2023\\\\nCharacteristics of UK GDP\\\\nKey insights\\\\nDetailed statistics\\\\nGDP of the UK 1948-2022\\\\nDetailed statistics\\\\nAnnual GDP growth in the UK 1949-2022\\\\nDetailed statistics\\\\nGDP per capita in the UK 1955-2022\\\\nEditor\\\\u2019s Picks\\\\nCurrent statistics on this topic\\\\nCurrent statistics on this topic\\\\nKey Economic Indicators\\\\nMonthly GDP growth of the UK 2019-2023\\\\nKey Economic Indicators\\\\nMonthly GDP of the UK 2019-2023\\\\nKey Economic Indicators\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nRelated topics\\\\nRecommended\\\\nRecommended statistics\\\\nGDP\\\\nGDP\\\\nGDP of the UK 1948-2022\\\\nGross domestic product of the United Kingdom from 1948 to 2022 (in million GBP)\\\\nQuarterly GDP of the UK 2019-2023\\\\nQuarterly gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in million GBP)\\\\n The 20 countries with the largest gross domestic product (GDP) in 2022 (in billion U.S. dollars)\\\\nGDP of European countries in 2022\\\\nGross domestic product at current market prices of selected European countries in 2022 (in million euros)\\\\nReal GDP growth rates in Europe 2023\\\\nAnnual real gross domestic product (GDP) growth rate in European countries in 2023\\\\nGross domestic product (GDP) of Europe\\'s largest economies 1980-2028\\\\nGross domestic product (GDP) at current prices of Europe\\'s largest economies from 1980 to 2028 (in billion U.S dollars)\\\\nUnited Kingdom\\'s share of global gross domestic product (GDP) 2028\\\\nUnited Kingdom (UK): Share of global gross domestic product (GDP) adjusted for Purchasing Power Parity (PPP) from 2018 to 2028\\\\nRelated topics\\\\nRecommended\\\\nReport on the topic\\\\nKey figures\\\\nThe most important key figures provide you with a compact summary of the topic of \\\\\"UK GDP\\\\\" and take you straight to the corresponding statistics.\\\\n Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\\\u00a0Mio. facts.\\\\n\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/quarterlynationalaccounts/latest\", \"content\": \"Looking at the quarters open to revision, real GDP growth is unrevised in five of the seven quarters compared with the first quarterly estimate; however, it is important to note that the typical absolute average revision between the initial quarterly GDP estimate and the estimate three years later is 0.2 percentage points, as there is potential for revision to GDP when the annual supply and use balance occurs as more comprehensive annual data sources are available at a detailed industry and product level; all the GDP growth vintages for these quarters are shown in Table 4.\\\\n Overall the revisions to production reflect:\\\\nrevised volume data from the\\\\u00a0Department for Energy Security and Net Zero (DESNZ) for electricity, gas, steam and air conditioning supply\\\\nnew Value Added Tax (VAT) turnover data for Quarter 2 2023\\\\nnew and revised Monthly Business Survey data\\\\nseasonal adjustment models\\\\nFigure 7: Revisions to production output across 2022 and 2023 are mainly driven by manufacturing; and the electricity, gas and steam subsectors\\\\nConstruction\\\\nConstruction output rose by 0.4% in Quarter 3 2023, revised up from a first estimate increase of 0.1%. Professional, scientific and technical activities: the upward revision in Quarter 4 (Oct to Dec) 2022 and Quarter 1 2023 are driven by new and revised survey data within the advertising and market research industry; in Quarter 3 2023, six of the eight industries in this section are revised down, with the largest contribution coming from architecture and engineering activities; technical testing and analysis, because of revised survey data since our last publication and the new VAT data for Quarter 2 2023.\\\\n This review covered:\\\\nprocesses and quality assurance in making revisions to GDP\\\\npotential improvements to early estimates of GDP enabled through enhanced access to data\\\\ncommunication of revisions to GDP, the story behind the most recent set of revisions in particular, and uncertainty in early estimates of GDP\\\\nWe have already started work looking into the recommendations of this review and will set out plans more fully during January 2024.\\\\n Important quality information\\\\nThere are common pitfalls in interpreting data series, and these include:\\\\nexpectations of accuracy and reliability in early estimates are often too high\\\\nrevisions are an inevitable consequence of the trade-off between timeliness and accuracy\\\\nearly estimates are based on incomplete data\\\\nVery few statistical revisions arise as a result of \\\\\"errors\\\\\" in the popular sense of the word.\"}]', name='tavily_search_results_json', tool_call_id='call_nvB1wQyQuNeTrOXQZnEtgNDZ')]}}\n", - "----\n", - "{'Researcher': {'messages': [AIMessage(content=\"The search results did not provide exact figures for the UK's GDP in 2022 and 2023. While there are several references to GDP data, growth rates, and quarterly figures, we do not have the specific annual GDP values in a consistent currency format (such as USD or GBP) that would allow us to compile a complete dataset for the past five years.\\n\\nTo proceed, we will need to find another source or use a different method to obtain the missing GDP data for 2022 and 2023. If this data is not available, we may not be able to draw an accurate line graph of the UK's GDP over the past five years.\", response_metadata={'token_usage': {'completion_tokens': 134, 'prompt_tokens': 6996, 'total_tokens': 7130}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-aa7d307d-cfdd-4c83-ad09-b6b0efbffe6e-0')], 'sender': 'Researcher'}}\n", - "----\n", - "{'chart_generator': {'messages': [AIMessage(content=\"It seems we have hit a roadblock in finding the exact GDP figures for the UK for the years 2022 and 2023. The information provided by the search results does not include the specific data we need. Therefore, we currently do not have the complete dataset to generate a line graph of the UK's GDP over the past five years.\\n\\nTo proceed, we might need to look for an official statistical release or a comprehensive economic report that includes the GDP figures for 2022 and 2023. If such data can be obtained, we can then use it to create the desired line graph. Without this data, we cannot fulfill the request as specified.\", response_metadata={'token_usage': {'completion_tokens': 134, 'prompt_tokens': 7150, 'total_tokens': 7284}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-a667e647-45b2-414e-b301-81f846fa59ad-0')], 'sender': 'chart_generator'}}\n", - "----\n", - "{'Researcher': {'messages': [AIMessage(content=\"I am unable to proceed with generating the line graph as we lack the specific GDP data for the UK for the years 2022 and 2023. To complete this task, we would need to acquire the missing data points. If the data becomes available, or if there's an alternative source that can provide the figures, we can revisit this task and generate the line graph accordingly. For now, we must conclude this attempt.\", response_metadata={'token_usage': {'completion_tokens': 87, 'prompt_tokens': 7276, 'total_tokens': 7363}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-1731ab9f-7ee9-4ff9-a920-7b998a41fe4d-0')], 'sender': 'Researcher'}}\n", - "----\n", - "{'chart_generator': {'messages': [AIMessage(content=\"As of my last attempt, I have not been able to find the exact GDP figures for the UK for the years 2022 and 2023. Without these figures, it's not possible to generate a complete and accurate line graph of the UK's GDP over the past five years. Should the data become available or if an alternative source can provide the figures, we can proceed with creating the graph. For the time being, this task cannot be completed.\", response_metadata={'token_usage': {'completion_tokens': 93, 'prompt_tokens': 7383, 'total_tokens': 7476}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-6e12dccd-b1b2-4c7d-befd-4a338d027756-0')], 'sender': 'chart_generator'}}\n", - "----\n", - "{'Researcher': {'messages': [AIMessage(content=\"I have attempted to gather the UK's GDP data for the past five years to draw a line graph, but I was only able to obtain confirmed figures for the years 2018 through 2021. The GDP data for 2022 and 2023 was not available in the search results. Without complete data, it is not possible to generate the line graph as requested. If the missing data for 2022 and 2023 becomes available or an alternative verified source can provide these figures, we can revisit this task to create the graph.\", response_metadata={'token_usage': {'completion_tokens': 111, 'prompt_tokens': 7468, 'total_tokens': 7579}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-9d93e637-cedd-4b17-945a-cc97e490432c-0')], 'sender': 'Researcher'}}\n", - "----\n", - "{'chart_generator': {'messages': [AIMessage(content=\"I've exhausted the available tools to find the UK's GDP for the past five years, but I was able to obtain data up to the year 2021. Unfortunately, without the GDP data for 2022 and 2023, I cannot create a complete line graph for the UK's GDP over the past five years.\\n\\nTo generate the line graph, we would need to have all the necessary data points for each year from 2018 to 2023. If you can provide the GDP data for 2022 and 2023 or direct me to a specific source where these figures can be found, I will be able to create the line graph for you.\", response_metadata={'token_usage': {'completion_tokens': 136, 'prompt_tokens': 7599, 'total_tokens': 7735}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-82063d76-6655-473e-9f6a-a5c005d92bd3-0')], 'sender': 'chart_generator'}}\n", - "----\n", - "{'Researcher': {'messages': [AIMessage(content=\"I have exhausted the available tools to find the UK's GDP for the past five years, but I was able to obtain data up to the year 2021. Unfortunately, without the GDP data for 2022 and 2023, I cannot create a complete line graph for the UK's GDP over the past five years.\\n\\nTo generate the line graph, we would need to have all the necessary data points for each year from 2018 to 2023. If you can provide the GDP data for 2022 and 2023 or direct me to a specific source where these figures can be found, I will be able to create the line graph for you.\", response_metadata={'token_usage': {'completion_tokens': 136, 'prompt_tokens': 7727, 'total_tokens': 7863}, 'model_name': 'gpt-4o', 'system_fingerprint': 'fp_85bf4c41a2', 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-954e7bb2-ae3e-4610-9927-4b16d733414e-0')], 'sender': 'Researcher'}}\n", - "----\n", - "{'chart_generator': {'messages': [AIMessage(content=\"Having attempted to find the UK's GDP figures for the years 2022 and 2023 through available data sources and coming up short, it appears we are currently unable to produce a complete line graph of the UK's GDP over the past five years as initially requested. To create a graph, we would need precise GDP data for each year from 2018 to 2023.\\n\\nIf the missing GDP data for 2022 and 2023 becomes available or if an alternative source can provide these figures, we can then proceed to generate the line graph. As of now, this task must be paused until the necessary data can be obtained.\", response_metadata={'token_usage': {'completion_tokens': 130, 'prompt_tokens': 7883, 'total_tokens': 8013}, 'model_name': 'gpt-4o', 'system_fingerprint': 'fp_85bf4c41a2', 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-8d1382e2-a77c-4d2f-b06a-2597be59542b-0')], 'sender': 'chart_generator'}}\n", - "----\n", - "{'Researcher': {'messages': [AIMessage(content=\"The search results do not provide the exact GDP figures for the UK for 2022 and 2023. Without this information, it is not possible to generate a line graph of the UK's GDP over the past five years. We would require the GDP values for those two years to complete the dataset and create the graph. As of now, I must conclude this task until the necessary data becomes available.\", response_metadata={'token_usage': {'completion_tokens': 82, 'prompt_tokens': 8005, 'total_tokens': 8087}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-246b9b29-ffc7-4da9-a09a-0dcfbbb3bd7a-0')], 'sender': 'Researcher'}}\n", - "----\n", - "{'chart_generator': {'messages': [AIMessage(content=\"I have attempted to find the UK's GDP for the past five years to create a line graph, but I could only obtain confirmed figures for the years 2018 through 2021. The GDP data for 2022 and 2023 was not available in the search results. Without complete data, it is not possible to generate the line graph as requested. If the missing data for 2022 and 2023 becomes available or an alternative verified source can provide these figures, we can revisit this task to create the graph.\", response_metadata={'token_usage': {'completion_tokens': 108, 'prompt_tokens': 8107, 'total_tokens': 8215}, 'model_name': 'gpt-4o', 'system_fingerprint': 'fp_85bf4c41a2', 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-f2847a80-610d-49c5-924a-ccffccb7cd5a-0')], 'sender': 'chart_generator'}}\n", - "----\n", - "{'Researcher': {'messages': [AIMessage(content=\"As of now, I was unable to obtain the complete data for the UK's GDP over the past five years due to lack of specific information for the years 2022 and 2023. Therefore, it's not possible to draw a line graph of the UK's GDP for this period without the complete dataset. Further action to acquire the missing data would be required to proceed.\", response_metadata={'token_usage': {'completion_tokens': 77, 'prompt_tokens': 8207, 'total_tokens': 8284}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-28e09000-8787-4ac0-a7d8-0aba888c2520-0')], 'sender': 'Researcher'}}\n", - "----\n", - "{'chart_generator': {'messages': [AIMessage(content=\"It appears we have encountered a limitation in obtaining the complete GDP data for the UK for 2022 and 2023. Without these figures, we cannot create the line graph of the UK's GDP over the past five years as requested. If the data becomes available, or if there's an alternative source that can provide the figures, we can revisit this task and generate the line graph accordingly. For now, this task will have to be concluded without completion.\", response_metadata={'token_usage': {'completion_tokens': 93, 'prompt_tokens': 8304, 'total_tokens': 8397}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-8bf8f247-cb86-4ef0-a81b-14da2d27b6f1-0')], 'sender': 'chart_generator'}}\n", - "----\n", - "{'Researcher': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_df3UdS3vJkJFB30O0WYq38k8', 'function': {'arguments': '{\"query\":\"UK GDP 2022 2023 statistics\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 26, 'prompt_tokens': 8389, 'total_tokens': 8415}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, name='Researcher', id='run-e1577cc7-5673-4821-9683-34947c7a2bc5-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'UK GDP 2022 2023 statistics'}, 'id': 'call_df3UdS3vJkJFB30O0WYq38k8'}])], 'sender': 'Researcher'}}\n", - "----\n", - "{'call_tool': {'messages': [ToolMessage(content='[{\"url\": \"https://www.statista.com/statistics/281744/gdp-of-the-united-kingdom/\", \"content\": \"Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\\\u00a0Mio. facts.\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nGDP of the UK 1948-2022\\\\nUK economy expected to shrink in 2023\\\\nHow big is the UK economy compared to others?\\\\nGross domestic product of the United Kingdom from 1948 to 2022\\\\n(in million GBP)\\\\nAdditional Information\\\\nShow sources information\\\\nShow publisher information\\\\nUse Ask Statista Research Service\\\\nDecember 2023\\\\nUnited Kingdom\\\\n1948 to 2022\\\\n*GDP is displayed in real terms (seasonally adjusted chained volume measure with 2019 as the reference year)\\\\n Statistics on\\\\n\\\\\"\\\\nEconomy of the UK\\\\n\\\\\"\\\\nOther statistics that may interest you Economy of the UK\\\\nGross domestic product\\\\nLabor Market\\\\nInflation\\\\nGovernment finances\\\\nBusiness Enterprise\\\\nFurther related statistics\\\\nFurther Content: You might find this interesting as well\\\\nStatistics\\\\nTopics Other statistics on the topicThe UK economy\\\\nEconomy\\\\nRPI annual inflation rate UK 2000-2028\\\\nEconomy\\\\nCPI annual inflation rate UK 2000-2028\\\\nEconomy\\\\nAverage annual earnings for full-time employees in the UK 1999-2023\\\\nEconomy\\\\nInflation rate in the UK 1989-2023\\\\nYou only have access to basic statistics.\\\\n Customized Research & Analysis projects:\\\\nGet quick analyses with our professional research service\\\\nThe best of the best: the portal for top lists & rankings:\\\\n\"}, {\"url\": \"https://www.statista.com/topics/3795/gdp-of-the-uk/\", \"content\": \"Monthly growth of gross domestic product in the United Kingdom from January 2019 to November 2023\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nContribution to gross domestic product growth in the United Kingdom in January 2023, by sector\\\\nGDP growth rate in the UK 1999-2021, by country\\\\nAnnual growth rates of gross domestic product in the United Kingdom from 1999 to 2021, by country\\\\nGDP growth rate in the UK 2021, by region\\\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\\\nGDP growth of Scotland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Scotland in 2021, by local (ITL 3) area\\\\nGDP growth of Wales 2021, by local area\\\\nAnnual growth rates of gross domestic product in Wales in 2021, by local (ITL 3) area\\\\nGDP growth of Northern Ireland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Northern Ireland in 2021, by local (ITL 3) area\\\\nGDP per capita\\\\nGDP per capita\\\\nGDP per capita in the UK 1955-2022\\\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\\\nAnnual GDP per capita growth in the UK 1956-2022\\\\nAnnual GDP per capita growth in the United Kingdom from 1956 to 2022\\\\nQuarterly GDP per capita in the UK 2019-2023\\\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nQuarterly GDP per capita growth in the UK 2019-2023\\\\nQuarterly GDP per capita growth in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nGDP per capita of the UK 1999-2021, by country\\\\nGross domestic product per capita of the United Kingdom from 1999 to 2021, by country (in GBP)\\\\nGDP per capita of the UK 2021, by region\\\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\\\nGlobal Comparisons\\\\nGlobal Comparisons\\\\nCountries with the largest gross domestic product (GDP) 2022\\\\n Monthly GDP of the UK 2019-2023\\\\nMonthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100)\\\\nGVA of the UK 2022, by sector\\\\nGross value added of the United Kingdom in 2022, by industry sector (in million GBP)\\\\nGDP of the UK 2021, by country\\\\nGross domestic product of the United Kingdom in 2021, by country (in million GBP)\\\\nGDP of the UK 2021, by region\\\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\\\nGDP of Scotland 2021, by local area\\\\nGross domestic product of Scotland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Wales 2021, by local area\\\\nGross domestic product of Wales in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Northern Ireland 2021, by local area\\\\nGross domestic product of Northern Ireland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP growth\\\\nGDP growth\\\\nGDP growth forecast for the UK 2000-2028\\\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\\\nAnnual GDP growth in the UK 1949-2022\\\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\\\nQuarterly GDP growth of the UK 2019-2023\\\\nQuarterly growth of gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023\\\\nMonthly GDP growth of the UK 2019-2023\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nUK GDP - Statistics & Facts\\\\nUK economy expected to shrink in 2023\\\\nCharacteristics of UK GDP\\\\nKey insights\\\\nDetailed statistics\\\\nGDP of the UK 1948-2022\\\\nDetailed statistics\\\\nAnnual GDP growth in the UK 1949-2022\\\\nDetailed statistics\\\\nGDP per capita in the UK 1955-2022\\\\nEditor\\\\u2019s Picks\\\\nCurrent statistics on this topic\\\\nCurrent statistics on this topic\\\\nKey Economic Indicators\\\\nMonthly GDP growth of the UK 2019-2023\\\\nKey Economic Indicators\\\\nMonthly GDP of the UK 2019-2023\\\\nKey Economic Indicators\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nRelated topics\\\\nRecommended\\\\nRecommended statistics\\\\nGDP\\\\nGDP\\\\nGDP of the UK 1948-2022\\\\nGross domestic product of the United Kingdom from 1948 to 2022 (in million GBP)\\\\nQuarterly GDP of the UK 2019-2023\\\\nQuarterly gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in million GBP)\\\\n The 20 countries with the largest gross domestic product (GDP) in 2022 (in billion U.S. dollars)\\\\nGDP of European countries in 2022\\\\nGross domestic product at current market prices of selected European countries in 2022 (in million euros)\\\\nReal GDP growth rates in Europe 2023\\\\nAnnual real gross domestic product (GDP) growth rate in European countries in 2023\\\\nGross domestic product (GDP) of Europe\\'s largest economies 1980-2028\\\\nGross domestic product (GDP) at current prices of Europe\\'s largest economies from 1980 to 2028 (in billion U.S dollars)\\\\nUnited Kingdom\\'s share of global gross domestic product (GDP) 2028\\\\nUnited Kingdom (UK): Share of global gross domestic product (GDP) adjusted for Purchasing Power Parity (PPP) from 2018 to 2028\\\\nRelated topics\\\\nRecommended\\\\nReport on the topic\\\\nKey figures\\\\nThe most important key figures provide you with a compact summary of the topic of \\\\\"UK GDP\\\\\" and take you straight to the corresponding statistics.\\\\n Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\\\u00a0Mio. facts.\\\\n\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/quarterlynationalaccounts/latest\", \"content\": \"Looking at the quarters open to revision, real GDP growth is unrevised in five of the seven quarters compared with the first quarterly estimate; however, it is important to note that the typical absolute average revision between the initial quarterly GDP estimate and the estimate three years later is 0.2 percentage points, as there is potential for revision to GDP when the annual supply and use balance occurs as more comprehensive annual data sources are available at a detailed industry and product level; all the GDP growth vintages for these quarters are shown in Table 4.\\\\n Overall the revisions to production reflect:\\\\nrevised volume data from the\\\\u00a0Department for Energy Security and Net Zero (DESNZ) for electricity, gas, steam and air conditioning supply\\\\nnew Value Added Tax (VAT) turnover data for Quarter 2 2023\\\\nnew and revised Monthly Business Survey data\\\\nseasonal adjustment models\\\\nFigure 7: Revisions to production output across 2022 and 2023 are mainly driven by manufacturing; and the electricity, gas and steam subsectors\\\\nConstruction\\\\nConstruction output rose by 0.4% in Quarter 3 2023, revised up from a first estimate increase of 0.1%. Professional, scientific and technical activities: the upward revision in Quarter 4 (Oct to Dec) 2022 and Quarter 1 2023 are driven by new and revised survey data within the advertising and market research industry; in Quarter 3 2023, six of the eight industries in this section are revised down, with the largest contribution coming from architecture and engineering activities; technical testing and analysis, because of revised survey data since our last publication and the new VAT data for Quarter 2 2023.\\\\n This review covered:\\\\nprocesses and quality assurance in making revisions to GDP\\\\npotential improvements to early estimates of GDP enabled through enhanced access to data\\\\ncommunication of revisions to GDP, the story behind the most recent set of revisions in particular, and uncertainty in early estimates of GDP\\\\nWe have already started work looking into the recommendations of this review and will set out plans more fully during January 2024.\\\\n Important quality information\\\\nThere are common pitfalls in interpreting data series, and these include:\\\\nexpectations of accuracy and reliability in early estimates are often too high\\\\nrevisions are an inevitable consequence of the trade-off between timeliness and accuracy\\\\nearly estimates are based on incomplete data\\\\nVery few statistical revisions arise as a result of \\\\\"errors\\\\\" in the popular sense of the word.\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/gdpmonthlyestimateuk/latest\", \"content\": \"The following list contains the full SIC names of industries included in consumer-facing services and their corresponding shortened industry name where this has been used in Figure 5:\\\\nwholesale and retail trade and repair of motor vehicles and motorcycles - sales and repairs of motor vehicles\\\\nretail trade, except of motor vehicles and motorcycles - retail except motor vehicles\\\\nrail transport\\\\naccommodation\\\\nfood and beverage service activities - food and beverage\\\\nbuying and selling, renting and operating of own or leased real estate, excluding imputed rent - real estate activities\\\\nveterinary activities\\\\ntravel agency, tour operator and other reservation service and related activities - travel and tourism activities\\\\ngambling and betting services\\\\nsports activities and amusement and recreation activities - sports, amusement and recreation\\\\nactivities of membership organisations\\\\nother personal service activities\\\\nactivities of households as employers of domestic personnel - households as employers of domestic personnel\\\\nAdditional bank holiday in May 2023 for the Coronation of King Charles III\\\\nThere was an additional bank holiday for the coronation of King Charles III on Monday 8 May 2023. Source: Monthly GDP estimate from Office for National Statistics\\\\nThe main reasons for revisions in October 2023 are:\\\\nin the services sector, the upwards revision is mainly from updated and late monthly business survey responses primarily in the information and communication subsection\\\\nin the production sector, the downward revision is from source data replacing forecasts in mining and quarrying and electricity, gas, steam and air conditioning supply, as well as revised and late monthly business survey responses predominantly in the manufacture of pharmaceutical products and pharmaceutical preparations, and sewerage industries\\\\nin the construction sector, the upwards revisions is because of updated and late monthly business survey responses for new public housing and other public new work\\\\nDetails on the revisions to monthly GDP prior to October 2023 are provided in our GDP quarterly national accounts, UK: July to September 2023 bulletin.\\\\n This review covered:\\\\nprocesses and quality assurance in making revisions to GDP\\\\npotential improvements to early estimates of GDP enabled through enhanced access to data\\\\ncommunication of revisions to GDP, the story behind the most recent set of revisions in particular, and uncertainty in early estimates of GDP\\\\nWe have already started work looking into the recommendations of this review and will set out plans more fully during January 2024.\\\\n11. The main data source for these statistics is the Monthly Business Survey (MBS) and response rates for each can be found in our:\\\\nOutput in the construction industry dataset\\\\nMonthly Business Survey (production) response rates dataset\\\\nCurrent and historical Monthly Business Survey (services) response rates dataset\\\\nOur monthly gross domestic product (GDP) data sources catalogue provides a full breakdown of the data used in this publication.\\\\n On the negative side, the lack of demand for construction products was prevalent across manufacturing, with manufacture of wood, rubber and plastic, glass, cement and plaster all seeing declines on the month in November 2023 in line with the two consecutive monthly falls in construction output in October and November 2023.\\\\n\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp\", \"content\": \"Quarter on Quarter growth: CVM SA %\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product: q-on-q4 growth rate CVM SA %\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product at market prices: Current price: Seasonally adjusted \\\\u00a3m\\\\nCurrent Prices (CP)\\\\nGross Domestic Product: quarter on quarter growth rate: CP SA %\\\\nCurrent Prices (CP)\\\\nGross Domestic Product: q-on-q4 growth quarter growth: CP SA %\\\\nCurrent Prices (CP)\\\\nDatasets related to Gross Domestic Product (GDP)\\\\n A roundup of the latest data and trends on the economy, business and jobs\\\\nTime series related to Gross Domestic Product (GDP)\\\\nGross Domestic Product: chained volume measures: Seasonally adjusted \\\\u00a3m\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product: Hide\\\\nData and analysis from Census 2021\\\\nGross Domestic Product (GDP)\\\\nGross domestic product (GDP) estimates as the main measure of UK economic growth based on the value of goods and services produced during a given period. Contains current and constant price data on the value of goods and services to indicate the economic performance of the UK.\\\\nEstimates of short-term indicators of investment in non-financial assets; business investment and asset and sector breakdowns of total gross fixed capital formation.\\\\n Monthly gross domestic product by gross value added\\\\nThe gross value added (GVA) tables showing the monthly and annual growths and indices as published within the monthly gross domestic product (GDP) statistical bulletin.\\\\n\"}]', name='tavily_search_results_json', tool_call_id='call_df3UdS3vJkJFB30O0WYq38k8')]}}\n", - "----\n", - "{'Researcher': {'messages': [AIMessage(content=\"The search results unfortunately do not provide specific figures for the UK's GDP in 2022 and 2023. While there are references to GDP data and related economic indicators, the exact annual GDP values needed to create the line graph are not present.\\n\\nOne possible approach to obtain these figures would be to access detailed statistical databases or reports from official sources such as the Office for National Statistics (ONS) or economic research institutions that publish historical GDP data. These sources might have the most recent and accurate GDP figures available for the UK, which are necessary to complete the line graph.\\n\\nSince I cannot directly access or retrieve the data from these sources using the tools available to me, I recommend consulting such databases or reports to find the UK's GDP for 2022 and 2023. Once the data is obtained, it can be used to create the line graph.\", response_metadata={'token_usage': {'completion_tokens': 172, 'prompt_tokens': 12099, 'total_tokens': 12271}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-7b4bcbc3-3ed0-4fa0-8e5d-a366c5a80d5a-0')], 'sender': 'Researcher'}}\n", - "----\n", - "{'chart_generator': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_JPVxDAzEFi21crVT7Rt6SRJl', 'function': {'arguments': '{\"code\":\"import matplotlib.pyplot as plt\\\\n\\\\n# UK GDP data obtained from search results for 2018-2021 (in billion USD)\\\\n# The data for 2022 and 2023 are placeholders and should be replaced with actual figures when available\\\\ngdp_data = {\\\\n \\'Year\\': [2018, 2019, 2020, 2021, 2022, 2023],\\\\n \\'GDP (Billion USD)\\': [2851.41, 2697.81, 3141.51, None, None]\\\\n}\\\\n\\\\n# Plot a line graph\\\\nplt.figure(figsize=(10, 5))\\\\nplt.plot(gdp_data[\\'Year\\'], gdp_data[\\'GDP (Billion USD)\\'], marker=\\'o\\')\\\\n\\\\n# Title and labels\\\\nplt.title(\\'UK GDP from 2018 to 2023\\')\\\\nplt.xlabel(\\'Year\\')\\\\nplt.ylabel(\\'GDP (Billion USD)\\')\\\\n\\\\n# Show grid\\\\nplt.grid(True)\\\\n\\\\n# Display the graph\\\\nplt.show()\"}', 'name': 'python_repl'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 240, 'prompt_tokens': 12291, 'total_tokens': 12531}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, name='chart_generator', id='run-6cff57bc-ba87-4690-9528-4d15bba7986c-0', tool_calls=[{'name': 'python_repl', 'args': {'code': \"import matplotlib.pyplot as plt\\n\\n# UK GDP data obtained from search results for 2018-2021 (in billion USD)\\n# The data for 2022 and 2023 are placeholders and should be replaced with actual figures when available\\ngdp_data = {\\n 'Year': [2018, 2019, 2020, 2021, 2022, 2023],\\n 'GDP (Billion USD)': [2851.41, 2697.81, 3141.51, None, None]\\n}\\n\\n# Plot a line graph\\nplt.figure(figsize=(10, 5))\\nplt.plot(gdp_data['Year'], gdp_data['GDP (Billion USD)'], marker='o')\\n\\n# Title and labels\\nplt.title('UK GDP from 2018 to 2023')\\nplt.xlabel('Year')\\nplt.ylabel('GDP (Billion USD)')\\n\\n# Show grid\\nplt.grid(True)\\n\\n# Display the graph\\nplt.show()\"}, 'id': 'call_JPVxDAzEFi21crVT7Rt6SRJl'}])], 'sender': 'chart_generator'}}\n", - "----\n", - "{'call_tool': {'messages': [ToolMessage(content=\"Successfully executed:\\n```python\\nimport matplotlib.pyplot as plt\\n\\n# UK GDP data obtained from search results for 2018-2021 (in billion USD)\\n# The data for 2022 and 2023 are placeholders and should be replaced with actual figures when available\\ngdp_data = {\\n 'Year': [2018, 2019, 2020, 2021, 2022, 2023],\\n 'GDP (Billion USD)': [2851.41, 2697.81, 3141.51, None, None]\\n}\\n\\n# Plot a line graph\\nplt.figure(figsize=(10, 5))\\nplt.plot(gdp_data['Year'], gdp_data['GDP (Billion USD)'], marker='o')\\n\\n# Title and labels\\nplt.title('UK GDP from 2018 to 2023')\\nplt.xlabel('Year')\\nplt.ylabel('GDP (Billion USD)')\\n\\n# Show grid\\nplt.grid(True)\\n\\n# Display the graph\\nplt.show()\\n```\\nStdout: ValueError('x and y must have same first dimension, but have shapes (6,) and (5,)')\\n\\nIf you have completed all tasks, respond with FINAL ANSWER.\", name='python_repl', tool_call_id='call_JPVxDAzEFi21crVT7Rt6SRJl')]}}\n", + "{'researcher': {'messages': [HumanMessage(content=\"First, get the UK's GDP over the past 5 years, then make a line chart of it. Once you make the chart, finish.\", additional_kwargs={}, response_metadata={}, id='fa1f5e95-9e1a-47d4-b4b6-e93f345e339d'), AIMessage(content=[{'text': \"I'll help search for the UK's GDP data over the past 5 years. Then my colleague can help create the line chart.\", 'type': 'text'}, {'id': 'toolu_01Jd9dxa4Ss2NhzBhCuwUX3E', 'input': {'query': 'UK GDP annual data past 5 years 2019-2023'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_014nCkfVHnG6LAsiS6pY7zcd', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 555, 'output_tokens': 101}}, id='run-e2297529-9972-4de6-835d-23d920b0e29b-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'UK GDP annual data past 5 years 2019-2023'}, 'id': 'toolu_01Jd9dxa4Ss2NhzBhCuwUX3E', 'type': 'tool_call'}], usage_metadata={'input_tokens': 555, 'output_tokens': 101, 'total_tokens': 656, 'input_token_details': {}}), ToolMessage(content='[{\"url\": \"https://www.macrotrends.net/global-metrics/countries/GBR/united-kingdom/gdp-gross-domestic-product\", \"content\": \"Dollar figures for GDP are converted from domestic currencies using single year official exchange rates. For a few countries where the official exchange rate does not reflect the rate effectively applied to actual foreign exchange transactions, an alternative conversion factor is used. U.K. gdp for 2023 was $3,340.03B, a 8.13% increase from 2022.\"}, {\"url\": \"https://www.statista.com/topics/3795/gdp-of-the-uk/\", \"content\": \"Monthly growth of gross domestic product in the United Kingdom from January 2019 to November 2023\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nContribution to gross domestic product growth in the United Kingdom in January 2023, by sector\\\\nGDP growth rate in the UK 1999-2021, by country\\\\nAnnual growth rates of gross domestic product in the United Kingdom from 1999 to 2021, by country\\\\nGDP growth rate in the UK 2021, by region\\\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\\\nGDP growth of Scotland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Scotland in 2021, by local (ITL 3) area\\\\nGDP growth of Wales 2021, by local area\\\\nAnnual growth rates of gross domestic product in Wales in 2021, by local (ITL 3) area\\\\nGDP growth of Northern Ireland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Northern Ireland in 2021, by local (ITL 3) area\\\\nGDP per capita\\\\nGDP per capita\\\\nGDP per capita in the UK 1955-2022\\\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\\\nAnnual GDP per capita growth in the UK 1956-2022\\\\nAnnual GDP per capita growth in the United Kingdom from 1956 to 2022\\\\nQuarterly GDP per capita in the UK 2019-2023\\\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nQuarterly GDP per capita growth in the UK 2019-2023\\\\nQuarterly GDP per capita growth in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nGDP per capita of the UK 1999-2021, by country\\\\nGross domestic product per capita of the United Kingdom from 1999 to 2021, by country (in GBP)\\\\nGDP per capita of the UK 2021, by region\\\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\\\nGlobal Comparisons\\\\nGlobal Comparisons\\\\nCountries with the largest gross domestic product (GDP) 2022\\\\n Monthly GDP of the UK 2019-2023\\\\nMonthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100)\\\\nGVA of the UK 2022, by sector\\\\nGross value added of the United Kingdom in 2022, by industry sector (in million GBP)\\\\nGDP of the UK 2021, by country\\\\nGross domestic product of the United Kingdom in 2021, by country (in million GBP)\\\\nGDP of the UK 2021, by region\\\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\\\nGDP of Scotland 2021, by local area\\\\nGross domestic product of Scotland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Wales 2021, by local area\\\\nGross domestic product of Wales in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Northern Ireland 2021, by local area\\\\nGross domestic product of Northern Ireland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP growth\\\\nGDP growth\\\\nGDP growth forecast for the UK 2000-2028\\\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\\\nAnnual GDP growth in the UK 1949-2022\\\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\\\nQuarterly GDP growth of the UK 2019-2023\\\\nQuarterly growth of gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023\\\\nMonthly GDP growth of the UK 2019-2023\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nUK GDP - Statistics & Facts\\\\nUK economy expected to shrink in 2023\\\\nCharacteristics of UK GDP\\\\nKey insights\\\\nDetailed statistics\\\\nGDP of the UK 1948-2022\\\\nDetailed statistics\\\\nAnnual GDP growth in the UK 1949-2022\\\\nDetailed statistics\\\\nGDP per capita in the UK 1955-2022\\\\nEditor’s Picks\\\\nCurrent statistics on this topic\\\\nCurrent statistics on this topic\\\\nKey Economic Indicators\\\\nMonthly GDP growth of the UK 2019-2023\\\\nKey Economic Indicators\\\\nMonthly GDP of the UK 2019-2023\\\\nKey Economic Indicators\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nRelated topics\\\\nRecommended\\\\nRecommended statistics\\\\nGDP\\\\nGDP\\\\nGDP of the UK 1948-2022\\\\nGross domestic product of the United Kingdom from 1948 to 2022 (in million GBP)\\\\nQuarterly GDP of the UK 2019-2023\\\\nQuarterly gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in million GBP)\\\\n The 20 countries with the largest gross domestic product (GDP) in 2022 (in billion U.S. dollars)\\\\nGDP of European countries in 2022\\\\nGross domestic product at current market prices of selected European countries in 2022 (in million euros)\\\\nReal GDP growth rates in Europe 2023\\\\nAnnual real gross domestic product (GDP) growth rate in European countries in 2023\\\\nGross domestic product (GDP) of Europe\\'s largest economies 1980-2028\\\\nGross domestic product (GDP) at current prices of Europe\\'s largest economies from 1980 to 2028 (in billion U.S dollars)\\\\nUnited Kingdom\\'s share of global gross domestic product (GDP) 2028\\\\nUnited Kingdom (UK): Share of global gross domestic product (GDP) adjusted for Purchasing Power Parity (PPP) from 2018 to 2028\\\\nRelated topics\\\\nRecommended\\\\nReport on the topic\\\\nKey figures\\\\nThe most important key figures provide you with a compact summary of the topic of \\\\\"UK GDP\\\\\" and take you straight to the corresponding statistics.\\\\n Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\xa0Mio. facts.\\\\n\"}, {\"url\": \"https://data.worldbank.org/indicator/NY.GDP.MKTP.KD.ZG?locations=GB\", \"content\": \"GDP growth (annual %) - United Kingdom | Data - World Bank Data\"}, {\"url\": \"https://www.statista.com/topics/6500/the-british-economy/\", \"content\": \"Output per hour worked in the UK 1971 to 2023\\\\nEconomic output per hour worked in the United Kingdom from 1st quarter 1971 to 2nd quarter 2023 (2019=100)\\\\nAnnual unemployment rate in the UK 2000-2028\\\\nAnnual unemployment rate in the United Kingdom from 2000 to 2028\\\\nInflation\\\\nInflation\\\\nInflation rate in the UK 1989-2023\\\\nInflation rate for the Consumer Price Index (CPI) in the United Kingdom from January 1989 to October 2023\\\\nRPI inflation rate in the UK 1948-2023\\\\nInflation rate for the Retail Price Index (RPI) in the United Kingdom from June 1948 to October 2023\\\\nCPIH inflation rate in the UK 1989-2023\\\\nInflation rate for the Consumer Price Index including owner occupiers\\' housing costs (CPIH) in the United Kingdom from January 1989 to October 2023\\\\nPPI in the UK 2010-2023\\\\nProducer Price Index (PPI) in the United Kingdom from October 2010 to October 2023\\\\nCPI inflation rate in the UK 2023, by sector\\\\nInflation rate for the Consumer Price Index (CPI) in the United Kingdom in October 2023, by sector\\\\nConsumer Price Index in the UK 1988-2023\\\\nConsumer Price Index (CPI) in the United Kingdom from 1st quarter 1988 to 3rd quarter 2023\\\\nRetail Price Index in the UK 1987-2023\\\\nRetail Price Index (RPI) in the United Kingdom from 1st quarter 1987 to 3rd quarter 2023\\\\nConsumer Price Index including housing in the UK 1988-2023\\\\nConsumer Price Index including owner occupiers\\' housing costs (CPIH) in the United Kingdom from 1st quarter 1988 to 3rd quarter 2023\\\\nRPI annual inflation rate UK 2000-2028\\\\nAnnual inflation rate of the Retail Price Index in the United Kingdom from 2000 to 2028\\\\nCPI annual inflation rate UK 2000-2028\\\\nAnnual inflation rate of the Consumer Price Index in the United Kingdom from 2000 to 2028\\\\nGovernment finances\\\\nGovernment finances\\\\nGovernment spending as a percentage of GDP in the UK 1900-2029\\\\nTotal managed expenditure expressed as a percentage of GDP in the United Kingdom from 1900/01 to 2028/29\\\\nGovernment revenue as a percentage of GDP in the UK 1900-2029\\\\nTotal public sector current receipts expressed as a percentage of GDP in the United Kingdom from 1900/01 to 2028/29 (in million GBP)\\\\nGovernment borrowing as a percentage of GDP in the UK 1900-2029\\\\nPublic sector borrowing expressed as a percentage of GDP in the United Kingdom from 1900/01 to 2028/29\\\\nNational debt as a percentage of GDP in the UK 1900-2029\\\\nPublic sector net debt expressed as a percentage of GDP in the United Kingdom from 1900/01 to 2028/29\\\\nPublic sector spending in the United Kingdom 2023/24\\\\nBudgeted public sector expenditure on services in the United Kingdom in 2023/24, by function (in billion GBP)\\\\nGovernment revenue sources in the United Kingdom 2023/24\\\\nExpected public sector current receipts in the United Kingdom in 2023/24, by function (in billion GBP)\\\\nBusiness Enterprise\\\\nBusiness Enterprise\\\\nLargest companies in the United Kingdom based on revenue 2022\\\\nLargest companies in the United Kingdom based on revenue in 2022 (in billion US dollars)\\\\nLargest UK companies based on number of global employees 2020\\\\nLargest companies based in the United Kingdom on number of employees worldwide in 2020 (in 1,000s)\\\\nNumber of private sector businesses in the UK 2000-2023\\\\nNumber of private sector businesses in the United Kingdom from 2000 to 2023 (in millions)\\\\nNumber of private sector businesses in the UK 2023, by sector\\\\nNumber of private sector businesses in the United Kingdom in 2023, by sector\\\\nNumber of businesses by enterprise size in the UK 2023\\\\nNumber of private sector businesses in the United Kingdom in 2023, by employment size\\\\nNumber of private sector businesses in the UK 2023, by region\\\\nNumber of private sector businesses in the United Kingdom in 2023, by region\\\\nNumber of local business units in the UK 2012-2023\\\\nNumber of local units in VAT and/or PAYE based enterprises in the United Kingdom from 2012 to 2023 (in millions)\\\\nBusiness investment index in the UK 1997-2023\\\\nBusiness investment index in the United Kingdom from 1st quarter 1997 to 2nd quarter 2023 (Q1 1997=100)\\\\nBusiness confidence Index in the UK 1977-2023\\\\nBusiness confidence Index of the United Kingdom from March 1977 to November 2023 (100 = long-term average)\\\\nRelated topics\\\\nRecommended\\\\nReport on the topic\\\\nKey figures\\\\nThe most important key figures provide you with a compact summary of the topic of \\\\\"The UK economy\\\\\" and take you straight to the corresponding statistics.\\\\n Monthly GDP growth of the UK 2020-2023\\\\nMonthly growth of gross domestic product in the United Kingdom from January 2020 to September 2023\\\\nGDP of the UK 2021, by region\\\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\\\nGDP growth rate in the UK 2021, by region\\\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\\\nGDP per capita of the UK 2021, by region\\\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\\\nGDP growth forecast for the UK 2000-2028\\\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\\\nLabor Market\\\\nLabor Market\\\\nUnemployment rate of the UK 1971-2023\\\\nUnemployment rate in the United Kingdom from March 1971 to September 2023\\\\nEmployment rate in the UK 1971-2022\\\\nEmployment rate in the United Kingdom from March 1971 to July 2023\\\\nNumber of people unemployed in the UK 1971-2023\\\\nNumber of people unemployed in the United Kingdom from March 1971 to July 2023 (in 1,000s)\\\\nNumber of people employed in the UK 1971-2021\\\\nNumber of people employed in the United Kingdom from March 1971 to July 2023 (in 1,000s)\\\\nUnemployment rate in the UK 1971-2023, by gender\\\\nUnemployment rate in the United Kingdom from 1st quarter 1971 to 2nd quarter 2023, by gender\\\\nUnemployment rate in the UK 1992-2023, by age group\\\\nUnemployment rate in the United Kingdom from May 1992 to July 2023, by age group\\\\nYouth unemployment rate in the UK 1992-2023\\\\nYouth unemployment rate in the United Kingdom from May 1992 to July 2023\\\\nAverage annual earnings for full-time employees in the UK 1999-2023\\\\nMedian annual earnings for full-time employees in the United Kingdom from 1999 to 2023 (in nominal GBP)\\\\nAverage weekly earning growth in the UK 2001-2023\\\\nAverage year-on-year growth of weekly earnings (3 month average) in the United Kingdom from March 2001 to October 2023\\\\nNumber of redundancies in the UK 1995-2023\\\\nAverage number of people made redundant in the United Kingdom from May 1995 to July 2023 (in 1,000s)\\\\nOverall weekly hours worked in the UK 1971-2023\\\\nOverall weekly hours worked for all employees in the United Kingdom from 1st quarter 1971 to 2nd quarter 2023 (in million hours worked)\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nThe UK economy - Statistics & Facts\\\\nUK households under pressure in 2023\\\\nCoronavirus devastates UK economy in 2020\\\\nKey insights\\\\nDetailed statistics\\\\nGDP of the UK 1948-2022\\\\nDetailed statistics\\\\nUnemployment rate of the UK 1971-2023\\\\nDetailed statistics\\\\nInflation rate in the UK 1989-2023\\\\nEditor’s Picks\\\\nCurrent statistics on this topic\\\\nCurrent statistics on this topic\\\\nWages & Salaries\\\\nAverage weekly earning growth in the UK 2001-2023\\\\nIncome & Expenditure\\\\nPublic sector spending in the United Kingdom 2023/24\\\\nEmployment\\\\nNumber of people employed in the UK 1971-2021\\\\nRelated topics\\\\nRecommended\\\\nRecommended statistics\\\\nGross domestic product\\\\nGross domestic product\\\\nGDP of the UK 1948-2022\\\\nGross domestic product of the United Kingdom from 1948 to 2022 (in million GBP)\\\\nAnnual GDP growth in the UK 1949-2022\\\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\\\nGDP per capita in the UK 1955-2022\\\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\\\nQuarterly GDP of the UK 1955-2023\\\\nQuarterly gross domestic product in the United Kingdom from 1st quarter 1955 to 3rd quarter 2023 (in million GBP)\\\\nQuarterly GDP growth of the UK 2015-2023\\\\nQuarterly growth of gross domestic product in the United Kingdom from 1st quarter 2015 to 3rd quarter 2023\\\\nQuarterly GDP per capita in the UK 1955-2023\\\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 1955 to 3rd quarter 2023 (in GBP)\\\\nMonthly GDP of the UK 1997-2023\\\\nMonthly index of gross domestic product in the United Kingdom from January 1997 to September 2023 (2019=100)\\\\n GDP\\\\nAnnual GDP growth in the UK 1949-2022\\\\nQuarterly GDP per capita growth in the UK 2015-2023\\\\nMonthly GDP growth of the UK 2020-2023\\\\nGDP per capita in the UK 1955-2022\\\\nLabor market\\\\nNumber of people employed in the UK 1971-2021\\\\nNumber of people unemployed in the UK 1971-2023\\\\nDaily number of jobs furloughed in the UK 2020-2021\\\\nAverage annual earnings for full-time employees in the UK 1999-2023\\\\nForecasts for 2023\\\\nGDP growth forecast for the UK 2000-2028\\\\nAnnual unemployment rate in the UK 2000-2028\\\\nCPI annual inflation rate UK 2000-2028\\\\nRPI annual inflation rate UK 2000-2028\\\\n Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\xa0Mio. facts.\\\\n\"}, {\"url\": \"https://data.worldbank.org/indicator/NY.GDP.MKTP.KD.ZG?locations=GB&most_recent_value_desc=false\", \"content\": \"GDP growth (annual %) - United Kingdom | Data Data GDP growth (annual %)United Kingdom Data Catalog Data Programs International Debt Statistics Other Books and Reports For Developers GDP growth (annual %) - United Kingdom ====================================== Similar values Highest values Lowest values GDP (constant 2015 US$) GDP (current US$) GDP (constant LCU) GDP: linked series (current LCU) GDP, PPP (constant 2021 international $) GDP (current LCU) GDP, PPP (current international $) GDP per capita growth (annual %) Country Most Recent Value All Countries and Economies Country Most Recent Value This site uses cookies to optimize functionality and give you the best possible experience. If you continue to navigate this website beyond this page, cookies will be placed on your browser.\"}]', name='tavily_search_results_json', id='4c88089f-0ac4-4eeb-9141-722f0463b78d', tool_call_id='toolu_01Jd9dxa4Ss2NhzBhCuwUX3E', artifact={'query': 'UK GDP annual data past 5 years 2019-2023', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'title': 'U.K. GDP 1960-2024 - Macrotrends', 'url': 'https://www.macrotrends.net/global-metrics/countries/GBR/united-kingdom/gdp-gross-domestic-product', 'content': 'Dollar figures for GDP are converted from domestic currencies using single year official exchange rates. For a few countries where the official exchange rate does not reflect the rate effectively applied to actual foreign exchange transactions, an alternative conversion factor is used. U.K. gdp for 2023 was $3,340.03B, a 8.13% increase from 2022.', 'score': 0.97675806, 'raw_content': None}, {'title': 'UK GDP - Statistics & Facts | Statista', 'url': 'https://www.statista.com/topics/3795/gdp-of-the-uk/', 'content': 'Monthly growth of gross domestic product in the United Kingdom from January 2019 to November 2023\\nContribution to GDP growth in the UK 2023, by sector\\nContribution to gross domestic product growth in the United Kingdom in January 2023, by sector\\nGDP growth rate in the UK 1999-2021, by country\\nAnnual growth rates of gross domestic product in the United Kingdom from 1999 to 2021, by country\\nGDP growth rate in the UK 2021, by region\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\nGDP growth of Scotland 2021, by local area\\nAnnual growth rates of gross domestic product in Scotland in 2021, by local (ITL 3) area\\nGDP growth of Wales 2021, by local area\\nAnnual growth rates of gross domestic product in Wales in 2021, by local (ITL 3) area\\nGDP growth of Northern Ireland 2021, by local area\\nAnnual growth rates of gross domestic product in Northern Ireland in 2021, by local (ITL 3) area\\nGDP per capita\\nGDP per capita\\nGDP per capita in the UK 1955-2022\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\nAnnual GDP per capita growth in the UK 1956-2022\\nAnnual GDP per capita growth in the United Kingdom from 1956 to 2022\\nQuarterly GDP per capita in the UK 2019-2023\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\nQuarterly GDP per capita growth in the UK 2019-2023\\nQuarterly GDP per capita growth in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\nGDP per capita of the UK 1999-2021, by country\\nGross domestic product per capita of the United Kingdom from 1999 to 2021, by country (in GBP)\\nGDP per capita of the UK 2021, by region\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\nGlobal Comparisons\\nGlobal Comparisons\\nCountries with the largest gross domestic product (GDP) 2022\\n Monthly GDP of the UK 2019-2023\\nMonthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100)\\nGVA of the UK 2022, by sector\\nGross value added of the United Kingdom in 2022, by industry sector (in million GBP)\\nGDP of the UK 2021, by country\\nGross domestic product of the United Kingdom in 2021, by country (in million GBP)\\nGDP of the UK 2021, by region\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\nGDP of Scotland 2021, by local area\\nGross domestic product of Scotland in 2021, by local (ITL 3) area (in million GBP)\\nGDP of Wales 2021, by local area\\nGross domestic product of Wales in 2021, by local (ITL 3) area (in million GBP)\\nGDP of Northern Ireland 2021, by local area\\nGross domestic product of Northern Ireland in 2021, by local (ITL 3) area (in million GBP)\\nGDP growth\\nGDP growth\\nGDP growth forecast for the UK 2000-2028\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\nAnnual GDP growth in the UK 1949-2022\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\nQuarterly GDP growth of the UK 2019-2023\\nQuarterly growth of gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023\\nMonthly GDP growth of the UK 2019-2023\\n Transforming data into design:\\nStatista Content & Design\\nStrategy and business building for the data-driven economy:\\nUK GDP - Statistics & Facts\\nUK economy expected to shrink in 2023\\nCharacteristics of UK GDP\\nKey insights\\nDetailed statistics\\nGDP of the UK 1948-2022\\nDetailed statistics\\nAnnual GDP growth in the UK 1949-2022\\nDetailed statistics\\nGDP per capita in the UK 1955-2022\\nEditor’s Picks\\nCurrent statistics on this topic\\nCurrent statistics on this topic\\nKey Economic Indicators\\nMonthly GDP growth of the UK 2019-2023\\nKey Economic Indicators\\nMonthly GDP of the UK 2019-2023\\nKey Economic Indicators\\nContribution to GDP growth in the UK 2023, by sector\\nRelated topics\\nRecommended\\nRecommended statistics\\nGDP\\nGDP\\nGDP of the UK 1948-2022\\nGross domestic product of the United Kingdom from 1948 to 2022 (in million GBP)\\nQuarterly GDP of the UK 2019-2023\\nQuarterly gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in million GBP)\\n The 20 countries with the largest gross domestic product (GDP) in 2022 (in billion U.S. dollars)\\nGDP of European countries in 2022\\nGross domestic product at current market prices of selected European countries in 2022 (in million euros)\\nReal GDP growth rates in Europe 2023\\nAnnual real gross domestic product (GDP) growth rate in European countries in 2023\\nGross domestic product (GDP) of Europe\\'s largest economies 1980-2028\\nGross domestic product (GDP) at current prices of Europe\\'s largest economies from 1980 to 2028 (in billion U.S dollars)\\nUnited Kingdom\\'s share of global gross domestic product (GDP) 2028\\nUnited Kingdom (UK): Share of global gross domestic product (GDP) adjusted for Purchasing Power Parity (PPP) from 2018 to 2028\\nRelated topics\\nRecommended\\nReport on the topic\\nKey figures\\nThe most important key figures provide you with a compact summary of the topic of \"UK GDP\" and take you straight to the corresponding statistics.\\n Industry Overview\\nDigital & Trend reports\\nOverview and forecasts on trending topics\\nIndustry & Market reports\\nIndustry and market insights and forecasts\\nCompanies & Products reports\\nKey figures and rankings about companies and products\\nConsumer & Brand reports\\nConsumer and brand insights and preferences in various industries\\nPolitics & Society reports\\nDetailed information about political and social topics\\nCountry & Region reports\\nAll key figures about countries and regions\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\nInsights on consumer attitudes and behavior worldwide\\nBusiness information on 100m+ public and private companies\\nExplore Company Insights\\nDetailed information for 39,000+ online stores and marketplaces\\nDirectly accessible data for 170 industries from 150+ countries\\nand over 1\\xa0Mio. facts.\\n', 'score': 0.97057647, 'raw_content': None}, {'title': 'GDP growth (annual %) - United Kingdom | Data - World Bank Data', 'url': 'https://data.worldbank.org/indicator/NY.GDP.MKTP.KD.ZG?locations=GB', 'content': 'GDP growth (annual %) - United Kingdom | Data - World Bank Data', 'score': 0.97052056, 'raw_content': None}, {'title': 'The UK economy - Statistics & Facts | Statista', 'url': 'https://www.statista.com/topics/6500/the-british-economy/', 'content': 'Output per hour worked in the UK 1971 to 2023\\nEconomic output per hour worked in the United Kingdom from 1st quarter 1971 to 2nd quarter 2023 (2019=100)\\nAnnual unemployment rate in the UK 2000-2028\\nAnnual unemployment rate in the United Kingdom from 2000 to 2028\\nInflation\\nInflation\\nInflation rate in the UK 1989-2023\\nInflation rate for the Consumer Price Index (CPI) in the United Kingdom from January 1989 to October 2023\\nRPI inflation rate in the UK 1948-2023\\nInflation rate for the Retail Price Index (RPI) in the United Kingdom from June 1948 to October 2023\\nCPIH inflation rate in the UK 1989-2023\\nInflation rate for the Consumer Price Index including owner occupiers\\' housing costs (CPIH) in the United Kingdom from January 1989 to October 2023\\nPPI in the UK 2010-2023\\nProducer Price Index (PPI) in the United Kingdom from October 2010 to October 2023\\nCPI inflation rate in the UK 2023, by sector\\nInflation rate for the Consumer Price Index (CPI) in the United Kingdom in October 2023, by sector\\nConsumer Price Index in the UK 1988-2023\\nConsumer Price Index (CPI) in the United Kingdom from 1st quarter 1988 to 3rd quarter 2023\\nRetail Price Index in the UK 1987-2023\\nRetail Price Index (RPI) in the United Kingdom from 1st quarter 1987 to 3rd quarter 2023\\nConsumer Price Index including housing in the UK 1988-2023\\nConsumer Price Index including owner occupiers\\' housing costs (CPIH) in the United Kingdom from 1st quarter 1988 to 3rd quarter 2023\\nRPI annual inflation rate UK 2000-2028\\nAnnual inflation rate of the Retail Price Index in the United Kingdom from 2000 to 2028\\nCPI annual inflation rate UK 2000-2028\\nAnnual inflation rate of the Consumer Price Index in the United Kingdom from 2000 to 2028\\nGovernment finances\\nGovernment finances\\nGovernment spending as a percentage of GDP in the UK 1900-2029\\nTotal managed expenditure expressed as a percentage of GDP in the United Kingdom from 1900/01 to 2028/29\\nGovernment revenue as a percentage of GDP in the UK 1900-2029\\nTotal public sector current receipts expressed as a percentage of GDP in the United Kingdom from 1900/01 to 2028/29 (in million GBP)\\nGovernment borrowing as a percentage of GDP in the UK 1900-2029\\nPublic sector borrowing expressed as a percentage of GDP in the United Kingdom from 1900/01 to 2028/29\\nNational debt as a percentage of GDP in the UK 1900-2029\\nPublic sector net debt expressed as a percentage of GDP in the United Kingdom from 1900/01 to 2028/29\\nPublic sector spending in the United Kingdom 2023/24\\nBudgeted public sector expenditure on services in the United Kingdom in 2023/24, by function (in billion GBP)\\nGovernment revenue sources in the United Kingdom 2023/24\\nExpected public sector current receipts in the United Kingdom in 2023/24, by function (in billion GBP)\\nBusiness Enterprise\\nBusiness Enterprise\\nLargest companies in the United Kingdom based on revenue 2022\\nLargest companies in the United Kingdom based on revenue in 2022 (in billion US dollars)\\nLargest UK companies based on number of global employees 2020\\nLargest companies based in the United Kingdom on number of employees worldwide in 2020 (in 1,000s)\\nNumber of private sector businesses in the UK 2000-2023\\nNumber of private sector businesses in the United Kingdom from 2000 to 2023 (in millions)\\nNumber of private sector businesses in the UK 2023, by sector\\nNumber of private sector businesses in the United Kingdom in 2023, by sector\\nNumber of businesses by enterprise size in the UK 2023\\nNumber of private sector businesses in the United Kingdom in 2023, by employment size\\nNumber of private sector businesses in the UK 2023, by region\\nNumber of private sector businesses in the United Kingdom in 2023, by region\\nNumber of local business units in the UK 2012-2023\\nNumber of local units in VAT and/or PAYE based enterprises in the United Kingdom from 2012 to 2023 (in millions)\\nBusiness investment index in the UK 1997-2023\\nBusiness investment index in the United Kingdom from 1st quarter 1997 to 2nd quarter 2023 (Q1 1997=100)\\nBusiness confidence Index in the UK 1977-2023\\nBusiness confidence Index of the United Kingdom from March 1977 to November 2023 (100 = long-term average)\\nRelated topics\\nRecommended\\nReport on the topic\\nKey figures\\nThe most important key figures provide you with a compact summary of the topic of \"The UK economy\" and take you straight to the corresponding statistics.\\n Monthly GDP growth of the UK 2020-2023\\nMonthly growth of gross domestic product in the United Kingdom from January 2020 to September 2023\\nGDP of the UK 2021, by region\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\nGDP growth rate in the UK 2021, by region\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\nGDP per capita of the UK 2021, by region\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\nGDP growth forecast for the UK 2000-2028\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\nLabor Market\\nLabor Market\\nUnemployment rate of the UK 1971-2023\\nUnemployment rate in the United Kingdom from March 1971 to September 2023\\nEmployment rate in the UK 1971-2022\\nEmployment rate in the United Kingdom from March 1971 to July 2023\\nNumber of people unemployed in the UK 1971-2023\\nNumber of people unemployed in the United Kingdom from March 1971 to July 2023 (in 1,000s)\\nNumber of people employed in the UK 1971-2021\\nNumber of people employed in the United Kingdom from March 1971 to July 2023 (in 1,000s)\\nUnemployment rate in the UK 1971-2023, by gender\\nUnemployment rate in the United Kingdom from 1st quarter 1971 to 2nd quarter 2023, by gender\\nUnemployment rate in the UK 1992-2023, by age group\\nUnemployment rate in the United Kingdom from May 1992 to July 2023, by age group\\nYouth unemployment rate in the UK 1992-2023\\nYouth unemployment rate in the United Kingdom from May 1992 to July 2023\\nAverage annual earnings for full-time employees in the UK 1999-2023\\nMedian annual earnings for full-time employees in the United Kingdom from 1999 to 2023 (in nominal GBP)\\nAverage weekly earning growth in the UK 2001-2023\\nAverage year-on-year growth of weekly earnings (3 month average) in the United Kingdom from March 2001 to October 2023\\nNumber of redundancies in the UK 1995-2023\\nAverage number of people made redundant in the United Kingdom from May 1995 to July 2023 (in 1,000s)\\nOverall weekly hours worked in the UK 1971-2023\\nOverall weekly hours worked for all employees in the United Kingdom from 1st quarter 1971 to 2nd quarter 2023 (in million hours worked)\\n Transforming data into design:\\nStatista Content & Design\\nStrategy and business building for the data-driven economy:\\nThe UK economy - Statistics & Facts\\nUK households under pressure in 2023\\nCoronavirus devastates UK economy in 2020\\nKey insights\\nDetailed statistics\\nGDP of the UK 1948-2022\\nDetailed statistics\\nUnemployment rate of the UK 1971-2023\\nDetailed statistics\\nInflation rate in the UK 1989-2023\\nEditor’s Picks\\nCurrent statistics on this topic\\nCurrent statistics on this topic\\nWages & Salaries\\nAverage weekly earning growth in the UK 2001-2023\\nIncome & Expenditure\\nPublic sector spending in the United Kingdom 2023/24\\nEmployment\\nNumber of people employed in the UK 1971-2021\\nRelated topics\\nRecommended\\nRecommended statistics\\nGross domestic product\\nGross domestic product\\nGDP of the UK 1948-2022\\nGross domestic product of the United Kingdom from 1948 to 2022 (in million GBP)\\nAnnual GDP growth in the UK 1949-2022\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\nGDP per capita in the UK 1955-2022\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\nQuarterly GDP of the UK 1955-2023\\nQuarterly gross domestic product in the United Kingdom from 1st quarter 1955 to 3rd quarter 2023 (in million GBP)\\nQuarterly GDP growth of the UK 2015-2023\\nQuarterly growth of gross domestic product in the United Kingdom from 1st quarter 2015 to 3rd quarter 2023\\nQuarterly GDP per capita in the UK 1955-2023\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 1955 to 3rd quarter 2023 (in GBP)\\nMonthly GDP of the UK 1997-2023\\nMonthly index of gross domestic product in the United Kingdom from January 1997 to September 2023 (2019=100)\\n GDP\\nAnnual GDP growth in the UK 1949-2022\\nQuarterly GDP per capita growth in the UK 2015-2023\\nMonthly GDP growth of the UK 2020-2023\\nGDP per capita in the UK 1955-2022\\nLabor market\\nNumber of people employed in the UK 1971-2021\\nNumber of people unemployed in the UK 1971-2023\\nDaily number of jobs furloughed in the UK 2020-2021\\nAverage annual earnings for full-time employees in the UK 1999-2023\\nForecasts for 2023\\nGDP growth forecast for the UK 2000-2028\\nAnnual unemployment rate in the UK 2000-2028\\nCPI annual inflation rate UK 2000-2028\\nRPI annual inflation rate UK 2000-2028\\n Industry Overview\\nDigital & Trend reports\\nOverview and forecasts on trending topics\\nIndustry & Market reports\\nIndustry and market insights and forecasts\\nCompanies & Products reports\\nKey figures and rankings about companies and products\\nConsumer & Brand reports\\nConsumer and brand insights and preferences in various industries\\nPolitics & Society reports\\nDetailed information about political and social topics\\nCountry & Region reports\\nAll key figures about countries and regions\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\nInsights on consumer attitudes and behavior worldwide\\nBusiness information on 100m+ public and private companies\\nExplore Company Insights\\nDetailed information for 39,000+ online stores and marketplaces\\nDirectly accessible data for 170 industries from 150+ countries\\nand over 1\\xa0Mio. facts.\\n', 'score': 0.95998776, 'raw_content': None}, {'title': 'GDP growth (annual %) - United Kingdom | Data - World Bank Data', 'url': 'https://data.worldbank.org/indicator/NY.GDP.MKTP.KD.ZG?locations=GB&most_recent_value_desc=false', 'content': 'GDP growth (annual %) - United Kingdom | Data Data GDP growth (annual %)United Kingdom Data Catalog Data Programs International Debt Statistics Other Books and Reports For Developers GDP growth (annual %) - United Kingdom ====================================== Similar values Highest values Lowest values GDP (constant 2015 US$) GDP (current US$) GDP (constant LCU) GDP: linked series (current LCU) GDP, PPP (constant 2021 international $) GDP (current LCU) GDP, PPP (current international $) GDP per capita growth (annual %) Country Most Recent Value All Countries and Economies Country Most Recent Value This site uses cookies to optimize functionality and give you the best possible experience. If you continue to navigate this website beyond this page, cookies will be placed on your browser.', 'score': 0.7892337, 'raw_content': None}], 'response_time': 2.3}), AIMessage(content=[{'text': 'Let me search for more specific data.', 'type': 'text'}, {'id': 'toolu_019dPRXojLJoVNYFLzzSWw4w', 'input': {'query': 'UK GDP values by year 2019 2020 2021 2022 2023'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01Ac9vcTFneb5dvcEYXJyf1P', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 5890, 'output_tokens': 87}}, id='run-3504417f-c0b5-4908-82e2-89a18abb1b8e-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'UK GDP values by year 2019 2020 2021 2022 2023'}, 'id': 'toolu_019dPRXojLJoVNYFLzzSWw4w', 'type': 'tool_call'}], usage_metadata={'input_tokens': 5890, 'output_tokens': 87, 'total_tokens': 5977, 'input_token_details': {}}), ToolMessage(content='[{\"url\": \"https://www.macrotrends.net/global-metrics/countries/GBR/united-kingdom/gdp-gross-domestic-product\", \"content\": \"U.K. gdp for 2023 was $3,340.03B, a 8.13% increase from 2022. U.K. gdp for 2022 was $3,088.84B, a 1.68% decline from 2021. U.K. gdp for 2021 was $3,141.51B, a 16.45% increase from 2020. U.K. gdp for 2020 was $2,697.81B, a 5.39% decline from 2019.\"}, {\"url\": \"https://countryeconomy.com/gdp/uk?year=2023\", \"content\": \"Gross Domestic Product of United Kingdom grew 0.3% in 2023 compared to last year. This rate is 45 -tenths of one percent less than the figure of 4.8% published in 2022. The GDP figure in 2023 was $3,380,855 million, leaving United Kingdom placed 6th in the ranking of GDP of the 196 countries that we publish.\"}, {\"url\": \"https://www.statista.com/topics/3795/gdp-of-the-uk/\", \"content\": \"Monthly growth of gross domestic product in the United Kingdom from January 2019 to November 2023\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nContribution to gross domestic product growth in the United Kingdom in January 2023, by sector\\\\nGDP growth rate in the UK 1999-2021, by country\\\\nAnnual growth rates of gross domestic product in the United Kingdom from 1999 to 2021, by country\\\\nGDP growth rate in the UK 2021, by region\\\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\\\nGDP growth of Scotland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Scotland in 2021, by local (ITL 3) area\\\\nGDP growth of Wales 2021, by local area\\\\nAnnual growth rates of gross domestic product in Wales in 2021, by local (ITL 3) area\\\\nGDP growth of Northern Ireland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Northern Ireland in 2021, by local (ITL 3) area\\\\nGDP per capita\\\\nGDP per capita\\\\nGDP per capita in the UK 1955-2022\\\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\\\nAnnual GDP per capita growth in the UK 1956-2022\\\\nAnnual GDP per capita growth in the United Kingdom from 1956 to 2022\\\\nQuarterly GDP per capita in the UK 2019-2023\\\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nQuarterly GDP per capita growth in the UK 2019-2023\\\\nQuarterly GDP per capita growth in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nGDP per capita of the UK 1999-2021, by country\\\\nGross domestic product per capita of the United Kingdom from 1999 to 2021, by country (in GBP)\\\\nGDP per capita of the UK 2021, by region\\\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\\\nGlobal Comparisons\\\\nGlobal Comparisons\\\\nCountries with the largest gross domestic product (GDP) 2022\\\\n Monthly GDP of the UK 2019-2023\\\\nMonthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100)\\\\nGVA of the UK 2022, by sector\\\\nGross value added of the United Kingdom in 2022, by industry sector (in million GBP)\\\\nGDP of the UK 2021, by country\\\\nGross domestic product of the United Kingdom in 2021, by country (in million GBP)\\\\nGDP of the UK 2021, by region\\\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\\\nGDP of Scotland 2021, by local area\\\\nGross domestic product of Scotland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Wales 2021, by local area\\\\nGross domestic product of Wales in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Northern Ireland 2021, by local area\\\\nGross domestic product of Northern Ireland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP growth\\\\nGDP growth\\\\nGDP growth forecast for the UK 2000-2028\\\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\\\nAnnual GDP growth in the UK 1949-2022\\\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\\\nQuarterly GDP growth of the UK 2019-2023\\\\nQuarterly growth of gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023\\\\nMonthly GDP growth of the UK 2019-2023\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nUK GDP - Statistics & Facts\\\\nUK economy expected to shrink in 2023\\\\nCharacteristics of UK GDP\\\\nKey insights\\\\nDetailed statistics\\\\nGDP of the UK 1948-2022\\\\nDetailed statistics\\\\nAnnual GDP growth in the UK 1949-2022\\\\nDetailed statistics\\\\nGDP per capita in the UK 1955-2022\\\\nEditor’s Picks\\\\nCurrent statistics on this topic\\\\nCurrent statistics on this topic\\\\nKey Economic Indicators\\\\nMonthly GDP growth of the UK 2019-2023\\\\nKey Economic Indicators\\\\nMonthly GDP of the UK 2019-2023\\\\nKey Economic Indicators\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nRelated topics\\\\nRecommended\\\\nRecommended statistics\\\\nGDP\\\\nGDP\\\\nGDP of the UK 1948-2022\\\\nGross domestic product of the United Kingdom from 1948 to 2022 (in million GBP)\\\\nQuarterly GDP of the UK 2019-2023\\\\nQuarterly gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in million GBP)\\\\n The 20 countries with the largest gross domestic product (GDP) in 2022 (in billion U.S. dollars)\\\\nGDP of European countries in 2022\\\\nGross domestic product at current market prices of selected European countries in 2022 (in million euros)\\\\nReal GDP growth rates in Europe 2023\\\\nAnnual real gross domestic product (GDP) growth rate in European countries in 2023\\\\nGross domestic product (GDP) of Europe\\'s largest economies 1980-2028\\\\nGross domestic product (GDP) at current prices of Europe\\'s largest economies from 1980 to 2028 (in billion U.S dollars)\\\\nUnited Kingdom\\'s share of global gross domestic product (GDP) 2028\\\\nUnited Kingdom (UK): Share of global gross domestic product (GDP) adjusted for Purchasing Power Parity (PPP) from 2018 to 2028\\\\nRelated topics\\\\nRecommended\\\\nReport on the topic\\\\nKey figures\\\\nThe most important key figures provide you with a compact summary of the topic of \\\\\"UK GDP\\\\\" and take you straight to the corresponding statistics.\\\\n Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\xa0Mio. facts.\\\\n\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp/compendium/unitedkingdomnationalaccountsthebluebook/2024/nationalaccountsataglance\", \"content\": \"Real gross domestic product (GDP) is estimated to have increased by 0.3% in 2023, following a recovery from the impacts of the coronavirus (COVID-19) pandemic over the two previous years (Figure 1). Data for the UK are the Office for National Statistics (ONS) measure of real gross domestic product (GDP). Figure 9: Real GDP per head fell in 2023 when compared with 2022 in six G10 economies, including the UK Data for the UK are the Office for National Statistics (ONS) measure of real gross domestic product (GDP) per head. Download this chart Figure 9: Real GDP per head fell in 2023 when compared with 2022 in six G10 economies, including the UK\"}, {\"url\": \"https://data.worldbank.org/indicator/NY.GDP.MKTP.KD.ZG?locations=GB&most_recent_value_desc=false\", \"content\": \"GDP growth (annual %) - United Kingdom | Data Data GDP growth (annual %)United Kingdom Data Catalog Data Programs International Debt Statistics Other Books and Reports For Developers GDP growth (annual %) - United Kingdom ====================================== Similar values Highest values Lowest values GDP (constant 2015 US$) GDP (current US$) GDP (constant LCU) GDP: linked series (current LCU) GDP, PPP (constant 2021 international $) GDP (current LCU) GDP, PPP (current international $) GDP per capita growth (annual %) Country Most Recent Value All Countries and Economies Country Most Recent Value This site uses cookies to optimize functionality and give you the best possible experience. If you continue to navigate this website beyond this page, cookies will be placed on your browser.\"}]', name='tavily_search_results_json', id='84c571ca-27c6-4023-93a2-f0c2e8b6abb0', tool_call_id='toolu_019dPRXojLJoVNYFLzzSWw4w', artifact={'query': 'UK GDP values by year 2019 2020 2021 2022 2023', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'title': 'U.K. GDP 1960-2024 - Macrotrends', 'url': 'https://www.macrotrends.net/global-metrics/countries/GBR/united-kingdom/gdp-gross-domestic-product', 'content': 'U.K. gdp for 2023 was $3,340.03B, a 8.13% increase from 2022. U.K. gdp for 2022 was $3,088.84B, a 1.68% decline from 2021. U.K. gdp for 2021 was $3,141.51B, a 16.45% increase from 2020. U.K. gdp for 2020 was $2,697.81B, a 5.39% decline from 2019.', 'score': 0.9974491, 'raw_content': None}, {'title': 'United Kingdom (UK) GDP - Gross Domestic Product 2023', 'url': 'https://countryeconomy.com/gdp/uk?year=2023', 'content': 'Gross Domestic Product of United Kingdom grew 0.3% in 2023 compared to last year. This rate is 45 -tenths of one percent less than the figure of 4.8% published in 2022. The GDP figure in 2023 was $3,380,855 million, leaving United Kingdom placed 6th in the ranking of GDP of the 196 countries that we publish.', 'score': 0.9964064, 'raw_content': None}, {'title': 'UK GDP - Statistics & Facts | Statista', 'url': 'https://www.statista.com/topics/3795/gdp-of-the-uk/', 'content': 'Monthly growth of gross domestic product in the United Kingdom from January 2019 to November 2023\\nContribution to GDP growth in the UK 2023, by sector\\nContribution to gross domestic product growth in the United Kingdom in January 2023, by sector\\nGDP growth rate in the UK 1999-2021, by country\\nAnnual growth rates of gross domestic product in the United Kingdom from 1999 to 2021, by country\\nGDP growth rate in the UK 2021, by region\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\nGDP growth of Scotland 2021, by local area\\nAnnual growth rates of gross domestic product in Scotland in 2021, by local (ITL 3) area\\nGDP growth of Wales 2021, by local area\\nAnnual growth rates of gross domestic product in Wales in 2021, by local (ITL 3) area\\nGDP growth of Northern Ireland 2021, by local area\\nAnnual growth rates of gross domestic product in Northern Ireland in 2021, by local (ITL 3) area\\nGDP per capita\\nGDP per capita\\nGDP per capita in the UK 1955-2022\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\nAnnual GDP per capita growth in the UK 1956-2022\\nAnnual GDP per capita growth in the United Kingdom from 1956 to 2022\\nQuarterly GDP per capita in the UK 2019-2023\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\nQuarterly GDP per capita growth in the UK 2019-2023\\nQuarterly GDP per capita growth in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\nGDP per capita of the UK 1999-2021, by country\\nGross domestic product per capita of the United Kingdom from 1999 to 2021, by country (in GBP)\\nGDP per capita of the UK 2021, by region\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\nGlobal Comparisons\\nGlobal Comparisons\\nCountries with the largest gross domestic product (GDP) 2022\\n Monthly GDP of the UK 2019-2023\\nMonthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100)\\nGVA of the UK 2022, by sector\\nGross value added of the United Kingdom in 2022, by industry sector (in million GBP)\\nGDP of the UK 2021, by country\\nGross domestic product of the United Kingdom in 2021, by country (in million GBP)\\nGDP of the UK 2021, by region\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\nGDP of Scotland 2021, by local area\\nGross domestic product of Scotland in 2021, by local (ITL 3) area (in million GBP)\\nGDP of Wales 2021, by local area\\nGross domestic product of Wales in 2021, by local (ITL 3) area (in million GBP)\\nGDP of Northern Ireland 2021, by local area\\nGross domestic product of Northern Ireland in 2021, by local (ITL 3) area (in million GBP)\\nGDP growth\\nGDP growth\\nGDP growth forecast for the UK 2000-2028\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\nAnnual GDP growth in the UK 1949-2022\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\nQuarterly GDP growth of the UK 2019-2023\\nQuarterly growth of gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023\\nMonthly GDP growth of the UK 2019-2023\\n Transforming data into design:\\nStatista Content & Design\\nStrategy and business building for the data-driven economy:\\nUK GDP - Statistics & Facts\\nUK economy expected to shrink in 2023\\nCharacteristics of UK GDP\\nKey insights\\nDetailed statistics\\nGDP of the UK 1948-2022\\nDetailed statistics\\nAnnual GDP growth in the UK 1949-2022\\nDetailed statistics\\nGDP per capita in the UK 1955-2022\\nEditor’s Picks\\nCurrent statistics on this topic\\nCurrent statistics on this topic\\nKey Economic Indicators\\nMonthly GDP growth of the UK 2019-2023\\nKey Economic Indicators\\nMonthly GDP of the UK 2019-2023\\nKey Economic Indicators\\nContribution to GDP growth in the UK 2023, by sector\\nRelated topics\\nRecommended\\nRecommended statistics\\nGDP\\nGDP\\nGDP of the UK 1948-2022\\nGross domestic product of the United Kingdom from 1948 to 2022 (in million GBP)\\nQuarterly GDP of the UK 2019-2023\\nQuarterly gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in million GBP)\\n The 20 countries with the largest gross domestic product (GDP) in 2022 (in billion U.S. dollars)\\nGDP of European countries in 2022\\nGross domestic product at current market prices of selected European countries in 2022 (in million euros)\\nReal GDP growth rates in Europe 2023\\nAnnual real gross domestic product (GDP) growth rate in European countries in 2023\\nGross domestic product (GDP) of Europe\\'s largest economies 1980-2028\\nGross domestic product (GDP) at current prices of Europe\\'s largest economies from 1980 to 2028 (in billion U.S dollars)\\nUnited Kingdom\\'s share of global gross domestic product (GDP) 2028\\nUnited Kingdom (UK): Share of global gross domestic product (GDP) adjusted for Purchasing Power Parity (PPP) from 2018 to 2028\\nRelated topics\\nRecommended\\nReport on the topic\\nKey figures\\nThe most important key figures provide you with a compact summary of the topic of \"UK GDP\" and take you straight to the corresponding statistics.\\n Industry Overview\\nDigital & Trend reports\\nOverview and forecasts on trending topics\\nIndustry & Market reports\\nIndustry and market insights and forecasts\\nCompanies & Products reports\\nKey figures and rankings about companies and products\\nConsumer & Brand reports\\nConsumer and brand insights and preferences in various industries\\nPolitics & Society reports\\nDetailed information about political and social topics\\nCountry & Region reports\\nAll key figures about countries and regions\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\nInsights on consumer attitudes and behavior worldwide\\nBusiness information on 100m+ public and private companies\\nExplore Company Insights\\nDetailed information for 39,000+ online stores and marketplaces\\nDirectly accessible data for 170 industries from 150+ countries\\nand over 1\\xa0Mio. facts.\\n', 'score': 0.97943294, 'raw_content': None}, {'title': 'National accounts at a glance - Office for National Statistics', 'url': 'https://www.ons.gov.uk/economy/grossdomesticproductgdp/compendium/unitedkingdomnationalaccountsthebluebook/2024/nationalaccountsataglance', 'content': 'Real gross domestic product (GDP) is estimated to have increased by 0.3% in 2023, following a recovery from the impacts of the coronavirus (COVID-19) pandemic over the two previous years (Figure 1). Data for the UK are the Office for National Statistics (ONS) measure of real gross domestic product (GDP). Figure 9: Real GDP per head fell in 2023 when compared with 2022 in six G10 economies, including the UK Data for the UK are the Office for National Statistics (ONS) measure of real gross domestic product (GDP) per head. Download this chart Figure 9: Real GDP per head fell in 2023 when compared with 2022 in six G10 economies, including the UK', 'score': 0.975249, 'raw_content': None}, {'title': 'GDP growth (annual %) - United Kingdom | Data - World Bank Data', 'url': 'https://data.worldbank.org/indicator/NY.GDP.MKTP.KD.ZG?locations=GB&most_recent_value_desc=false', 'content': 'GDP growth (annual %) - United Kingdom | Data Data GDP growth (annual %)United Kingdom Data Catalog Data Programs International Debt Statistics Other Books and Reports For Developers GDP growth (annual %) - United Kingdom ====================================== Similar values Highest values Lowest values GDP (constant 2015 US$) GDP (current US$) GDP (constant LCU) GDP: linked series (current LCU) GDP, PPP (constant 2021 international $) GDP (current LCU) GDP, PPP (current international $) GDP per capita growth (annual %) Country Most Recent Value All Countries and Economies Country Most Recent Value This site uses cookies to optimize functionality and give you the best possible experience. If you continue to navigate this website beyond this page, cookies will be placed on your browser.', 'score': 0.83775276, 'raw_content': None}], 'response_time': 2.37}), HumanMessage(content='Based on the search results, I can provide the UK\\'s GDP values for the past 5 years (in billions of US dollars):\\n\\n2019: $2,851.54\\n2020: $2,697.81\\n2021: $3,141.51\\n2022: $3,088.84\\n2023: $3,340.03\\n\\nI\\'ll pass this data to my chart generator colleague to create a line chart. They should create a line chart with:\\n- Years 2019-2023 on the x-axis\\n- GDP values in billions USD on the y-axis\\n- Title: \"UK GDP 2019-2023\"\\n- Clear data points showing the values\\n\\nOver to you, chart generator colleague!', additional_kwargs={}, response_metadata={}, name='researcher', id='7e790b7a-7b06-4b45-a595-8736b53db844')]}}\n", "----\n" ] }, { - "data": { - "image/png": "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3bF48eKIiFi4cGFMmzYtGhsbIyJi7ty5cd9998X5558fVVVV8frrr8edd94Zc+fO7Y8jAACAsZJ3FNXV1cW+ffti+fLl0d7eHjNnzozm5ub+D1/YvXv3gDtDd9xxRxQUFMQdd9wRb731VnziE5+IuXPnxk9+8pPhexUAAABDVJCNg/ewdXV1RVlZWXR2dkZpaelYbwcAABgjI9EGI/7pcwAAAB9loggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASNqQoqipqSmmT58eJSUlUVVVFZs2bfrA+e+8804sWbIkpkyZErlcLs4888zYsGHDkDYMAAAwnCbku2DdunVRX18fq1evjqqqqli1alXU1tbGjh07YvLkyYfN7+npia9+9asxefLkeOKJJ2LatGnx5ptvximnnDIc+wcAADgmBVmWZfksqKqqigsuuCDuv//+iIjo6+uLysrKuPHGG2Pp0qWHzV+9enX8/Oc/j+3bt8cJJ5wwpE12dXVFWVlZdHZ2Rmlp6ZCeAwAAGP9Gog3yevtcT09PbN68OWpqav77BIWFUVNTE62trYOueeaZZ6K6ujqWLFkS5eXlcc4558SKFSuit7f3iNc5ePBgdHV1DXgAAACMhLyiaP/+/dHb2xvl5eUDxsvLy6O9vX3QNTt37ownnngient7Y8OGDXHnnXfGvffeGz/+8Y+PeJ3GxsYoKyvrf1RWVuazTQAAgKM24p8+19fXF5MnT44HH3wwZs2aFXV1dXH77bfH6tWrj7hm2bJl0dnZ2f/Ys2fPSG8TAABIVF4ftDBp0qQoKiqKjo6OAeMdHR1RUVEx6JopU6bECSecEEVFRf1jn/vc56K9vT16enqiuLj4sDW5XC5yuVw+WwMAABiSvO4UFRcXx6xZs6KlpaV/rK+vL1paWqK6unrQNRdddFG8/vrr0dfX1z/22muvxZQpUwYNIgAAgNGU99vn6uvrY82aNfHrX/86tm3bFt/5zneiu7s7Fi9eHBERCxcujGXLlvXP/853vhN///vf46abborXXnst1q9fHytWrIglS5YM36sAAAAYory/p6iuri727dsXy5cvj/b29pg5c2Y0Nzf3f/jC7t27o7Dwv61VWVkZzz33XNxyyy1x3nnnxbRp0+Kmm26KW2+9dfheBQAAwBDl/T1FY8H3FAEAABEfge8pAgAAON6IIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaUOKoqamppg+fXqUlJREVVVVbNq06ajWrV27NgoKCmLevHlDuSwAAMCwyzuK1q1bF/X19dHQ0BBbtmyJGTNmRG1tbezdu/cD1+3atSu+973vxcUXXzzkzQIAAAy3vKPovvvui29961uxePHi+PznPx+rV6+Ok046KR555JEjrunt7Y2rr7467rrrrjj99NOPacMAAADDKa8o6unpic2bN0dNTc1/n6CwMGpqaqK1tfWI6370ox/F5MmT45prrjmq6xw8eDC6uroGPAAAAEZCXlG0f//+6O3tjfLy8gHj5eXl0d7ePuiaP/zhD/Hwww/HmjVrjvo6jY2NUVZW1v+orKzMZ5sAAABHbUQ/fe7AgQOxYMGCWLNmTUyaNOmo1y1btiw6Ozv7H3v27BnBXQIAACmbkM/kSZMmRVFRUXR0dAwY7+joiIqKisPm//Wvf41du3bF3Llz+8f6+vr+c+EJE2LHjh1xxhlnHLYul8tFLpfLZ2sAAABDktedouLi4pg1a1a0tLT0j/X19UVLS0tUV1cfNv/ss8+OV155Jdra2vofV1xxRVx66aXR1tbmbXEAAMCYy+tOUUREfX19LFq0KGbPnh1z5syJVatWRXd3dyxevDgiIhYuXBjTpk2LxsbGKCkpiXPOOWfA+lNOOSUi4rBxAACAsZB3FNXV1cW+ffti+fLl0d7eHjNnzozm5ub+D1/YvXt3FBaO6K8qAQAADJuCLMuysd7Eh+nq6oqysrLo7OyM0tLSsd4OAAAwRkaiDdzSAQAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkDSmKmpqaYvr06VFSUhJVVVWxadOmI85ds2ZNXHzxxTFx4sSYOHFi1NTUfOB8AACA0ZR3FK1bty7q6+ujoaEhtmzZEjNmzIja2trYu3fvoPM3btwY8+fPjxdffDFaW1ujsrIyLrvssnjrrbeOefMAAADHqiDLsiyfBVVVVXHBBRfE/fff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8ZnPfCYeffTRvK/7kYiidevWRX19fTQ0NMSWLVtixowZUVtbG3v37h10/ssvvxzz58+Pa665JrZu3Rrz5s2LefPmxauvvjrKO2e8yfesbdy4MebPnx8vvvhitLa2RmVlZVx22WXx1ltvjfLOGW/yPWvv27VrV3zve9+Liy++eJR2yniX71nr6emJr371q7Fr16544oknYseOHbFmzZqYNm3aKO+c8Sbfs/bYY4/F0qVLo6GhIbZt2xYPP/xwrFu3Lm677bZR3jnjSXd3d8yYMSOampqOav4bb7wRl19+eVx66aXR1tYWN998c1x77bXx3HPP5Xfh7CNgzpw52ZIlS/p/7u3tzaZOnZo1NjYOOv/KK6/MLr/88gFjVVVV2be//e0R3SfjX75n7X8dOnQoO/nkk7Nf//rXI7VFjhNDOWuHDh3KLrzwwuyhhx7KFi1alH39618fhZ0y3uV71n75y19mp59+etbT0zNaW+Q4ke9ZW7JkSfaVr3xlwFh9fX120UUXjeg+OX5ERPbUU0994Jwf/OAH2Re+8IUBY3V1dVltbW1e1xrzO0U9PT2xefPmqKmp6R8rLCyMmpqaaG1tHXRNa2vrgPkREbW1tUecDxFDO2v/691334333nsvTj311JHaJseBoZ61H/3oRzF58uS45pprRmObHAeGctaeeeaZqK6ujiVLlkR5eXmcc845sWLFiujt7R2tbTMODeWsXXjhhbF58+b+t9jt3LkzNmzYEF/72tdGZc+kYbi6YMJwbmoo9u/fH729vVFeXj5gvLy8PLZv3z7omvb29kHnt7e3j9g+Gf+Gctb+16233hpTp0497B8++P+Gctb+8Ic/xMMPPxxtbW2jsEOOF0M5azt37ozf//73cfXVV8eGDRvi9ddfjxtuuCHee++9aGhoGI1tMw4N5axdddVVsX///vjyl78cWZbFoUOH4vrrr/f2OYbVkbqgq6sr/vWvf8WJJ554VM8z5neKYLxYuXJlrF27Np566qkoKSkZ6+1wHDlw4EAsWLAg1qxZE5MmTRrr7XCc6+vri8mTJ8eDDz4Ys2bNirq6urj99ttj9erVY701jjMbN26MFStWxAMPPBBbtmyJJ598MtavXx933333WG8NDjPmd4omTZoURUVF0dHRMWC8o6MjKioqBl1TUVGR13yIGNpZe98999wTK1eujBdeeCHOO++8kdwmx4F8z9pf//rX2LVrV8ydO7d/rK+vLyIiJkyYEDt27IgzzjhjZDfNuDSUP9emTJkSJ5xwQhQVFfWPfe5zn4v29vbo6emJ4uLiEd0z49NQztqdd94ZCxYsiGuvvTYiIs4999zo7u6O6667Lm6//fYoLPT/5jl2R+qC0tLSo75LFPERuFNUXFwcs2bNipaWlv6xvr6+aGlpierq6kHXVFdXD5gfEfH8888fcT5EDO2sRUT87Gc/i7vvvjuam5tj9uzZo7FVxrl8z9rZZ58dr7zySrS1tfU/rrjiiv5P0qmsrBzN7TOODOXPtYsuuihef/31/vCOiHjttddiypQpgogjGspZe/fddw8Ln/dj/D+/Qw/Hbti6IL/PgBgZa9euzXK5XPboo49mf/nLX7LrrrsuO+WUU7L29vYsy7JswYIF2dKlS/vn//GPf8wmTJiQ3XPPPdm2bduyhoaG7IQTTsheeeWVsXoJjBP5nrWVK1dmxcXF2RNPPJG9/fbb/Y8DBw6M1UtgnMj3rP0vnz7H0cr3rO3evTs7+eSTs+9+97vZjh07smeffTabPHly9uMf/3isXgLjRL5nraGhITv55JOz3/zmN9nOnTuz3/3ud9kZZ5yRXXnllWP1EhgHDhw4kG3dujXbunVrFhHZfffdl23dujV78803syzLsqVLl2YLFizon79z587spJNOyr7//e9n27Zty5qamrKioqKsubk5r+t+JKIoy7LsF7/4RXbaaadlxcXF2Zw5c7I//elP/X/tkksuyRYtWjRg/uOPP56deeaZWXFxcfaFL3whW79+/SjvmPEqn7P2qU99KouIwx4NDQ2jv3HGnXz/XPv/RBH5yPesvfzyy1lVVVWWy+Wy008/PfvJT36SHTp0aJR3zXiUz1l77733sh/+8IfZGWeckZWUlGSVlZXZDTfckP3jH/8Y/Y0zbrz44ouD/rfX+2dr0aJF2SWXXHLYmpkzZ2bFxcXZ6aefnv3qV7/K+7oFWeb+JQAAkK4x/50iAACAsSSKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASNr/AUOP/hLIsQ49AAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" + "name": "stderr", + "output_type": "stream", + "text": [ + "Python REPL can execute arbitrary code. Use with caution.\n" + ] }, { "data": { - "image/png": "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", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA2YAAAI5CAYAAADKeiloAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAADeV0lEQVR4nOzdeVxUZfsG8Gtm2LcBRFDEBURFUBR3WtyX1FJ729RyyzLN6tU2s7LUStvT91dppqmZZtlqaiq57yuoiDu4IIsgssjOnPP7gzg6AjrgPMyc4fp+PmU8c2bmfq45ITfnnOdoZFmWQURERERERBajtXQBREREREREtR0bMyIiIiIiIgtjY0ZERERERGRhbMyIiIiIiIgsjI0ZERERERGRhbExIyIiIiIisjA2ZkRERERERBbGxoyIiIiIiMjC2JgRERERERFZGBszIiIiIiIiC2NjRkRk47Zu3QqNRoPRo0dXeZvu3btDo9EgJSWl3HPi4uIQEBAArVaLr776yuR6kpOTMW3aNHTu3Bl16tSBvb09vL290aVLF7zxxhuIi4sr95yyOsr+sbe3R506ddC2bVuMHTsW69evhyRJFb5fkyZNjJ6r0+ng4+ODvn374s8//zSp5qtXr2LBggUYNGgQgoKC4OjoCB8fH/Tv3x8bNmyo9HmFhYWYOXMmmjVrBicnJ/j7+2PcuHG4cuVKuW3z8vLw2WefYfjw4QgJCYFWq4VGo8H58+dvW9uKFStw7733ws3NDa6urujYsSOWLFli0rxutnPnTrzyyito37496tSpAycnJ4SEhGDKlCnIzMys9HkHDhzAgAED4OnpCVdXV3Tp0gU///xzue1yc3Pxww8/4PHHH0fz5s3h7OwMT09PdOvWDT/++GOFr71v3z6MGjUKrVq1gre3N5ycnBAcHIwnnngCBw8erPIciYismZ2lCyAiIvXZv38/BgwYgOzsbPzwww8YPny4Sc9buXIlxo4di7y8PISHh+Oxxx5DnTp1kJ2djZiYGHz22Wf4+OOP8euvv+Lhhx8u9/xXXnkFbm5ukCQJmZmZOHHiBJYvX47vvvsO99xzD3788Uc0atSo3PN0Oh3efvttAEBRURFOnjyJ1atXIyoqCp9++ileeeWV29a9atUqTJgwAf7+/ujVqxcaNGiAxMRE/Prrr1i/fj0+/vhjvPbaa0bPkSQJgwcPxoYNG9ClSxc88sgjOHPmDBYuXIhNmzZh7969qFu3rrL9lStX8OqrrwIAGjduDC8vL2RkZNy2rldeeQWff/456tWrhyeffBL29vZYt24dxowZg9jYWHz66ae3ff7NHn30UaSnp+O+++7DyJEjodFosHXrVnz88cf45ZdfsHv3bvj5+Rk9Z8uWLejXrx+cnJwwdOhQuLu749dff8UTTzyBS5cuGeW6Y8cOjBgxAnXq1EGvXr3wyCOP4MqVK/jtt98wfPhw7Nq1C19++aXR6+/YsQNRUVHo0qULevbsCRcXF8THx2P16tVYtWoVli5dihEjRpg8RyIiqyYTEZFN27JliwxAHjVqVJW36datmwxATk5OVsb++ecf2c3NTXZxcZHXrVtnch3r1q2TtVqt7OPjI69fv77CbRITE+WJEyfKixYtumMdZdLS0uRhw4bJAOSQkBD5+vXrRo83btxYdnR0LPe8DRs2yBqNRnZxcZFzc3NvW/umTZvk1atXywaDwWj85MmTsl6vl+3t7eXLly8bPfbdd9/JAORhw4bJkiQp4/PmzZMByOPGjTPaPicnR964caN89epVWZZluV+/fjIAOSEhocKaDhw4IAOQg4ODlefIsixfv35d7tixowxA3r17923ndbMPP/yw3BwkSZInTJggA5Cff/55o8eKi4vlpk2byo6OjnJ0dLQynpmZKTdv3lx2cHCQz58/r4xHR0fLy5YtkwsLC41eJyUlRW7cuLEMQN63b5/RY/n5+RXWeuzYMdnJyUn29fU1ypaISM3YmBER2ThzNma//vqr7OjoKHt6eso7d+40uYbi4mI5MDBQBiBv2bLFpO1vV8etDAaD3LNnTxmA/NFHHxk9VlljJsuyHBISIgOQ9+/fb9pEKjBu3DgZgLxq1Sqj8cjISBmAUXMiy6XNTlBQkOzq6irn5eVV+rp3aszefvttGYD81VdflXvsjz/+kAHII0eOrPqEbpGUlCQDkMPCwozGN2zYIAOQx4wZU+45S5YskQHIM2bMMOk9Zs2aJQOQP/nkE5PrioiIkAHImZmZJj+HiMia8RozIiIyyaJFi/D444/Dy8sL27Ztw7333mvyc7ds2YKEhATcd9996N69+x23t7Or2pn2Wq0Wb731FgDgp59+qtJzAUCj0VT5OWXs7e0BGNdcUFCAffv2oUWLFmjcuHG59+rTpw9yc3Pv6jqpsuv+AgMDyz1WNrZ58+Zqv36ZiuYHlF6XCAB9+/Yt95x+/foBALZt23ZX71GZc+fO4dSpU2jYsCH0er1JzyEisna8xoyIiO7os88+w6efforAwEBERUWhadOmVXr+nj17AAA9evQQUR4A4N5774WdnR1iYmJQUlJyxx/yN23ahFOnTsHV1RVhYWHVes/s7Gz88ssvcHJywv3336+Mnzt3DpIkoVmzZhU+r2z8zJkzRs+rCh8fHwBAQkJCucfKxhITE5GXlwcXF5dqvQcAfPfddwDKN2BnzpwBgArnWK9ePbi5uSnb3I7BYMD3338PjUaD3r17V7jN/v37sW7dOhQXF+PChQtYvXo1AGD+/PlVmgsRkTVjY0ZERHf06aefQqvVYs2aNVVuyoAbR3f8/f3LPXb+/Plyqwg2adLktqtIVsTR0RF16tRBamoqMjIy4OvrqzxWUlKC6dOnAwCKi4tx6tQp/Pnnn5BlGe+99x6cnZ2r9F5lxo8fj9TUVMycORN16tRRxrOysgCg0qM5Hh4eRttVR//+/fHhhx9izpw5GD58ODw9PQGUru44e/Zso1qq25jFxMRgxowZ8PX1xeuvv270mClzNGV+06ZNw7Fjx/D000+jVatWFW6zf/9+zJgxQ/naz88P33//fYVH64iI1IqNGRER3VGfPn0QFRWFkSNHIioqCl5eXmZ77fPnzxv90A0A3bp1q3JjdjsGg0F5D61WCy8vL/Ts2RMTJ07EoEGDqvWaU6dOxY8//ogHHngAb775ptlqNVXXrl0xYsQILFu2DKGhoRg0aJCyKmNJSQn0ej2ysrKg1ZZetbB161bl9MMybdu2xZAhQyp8/fj4eAwcOBAGgwErV65UjtCZ0/z58zF79mxERERg7ty5lW73wgsv4IUXXkB+fj7OnDmDzz//HP3798dHH32krGRJRKR2bMyIiGxc2Q/mld3n6+bHyra91eLFizFlyhQsX74cvXr1wj///ANvb2+TayhbZj0pKancY927d4csywBKj6zVr1/f5Ne9WWFhIa5evQqdTleuNkdHRxQUFFTrdSsybdo0fPjhh+jZsyd+++036HQ6o8fLjiJVdsQoOzvbaLvqWrJkCTp06IBFixZhyZIlcHZ2Rr9+/fDxxx8jLCwMdnZ2ShZbt24t1wCPGjWqwsYsISEBPXr0QHp6On799dcKT0E1ZY63a+AXLlyI559/Hq1bt0ZUVBTc3NzuOF9nZ2eEh4djyZIlSEtLw5QpU/DAAw9UeqSNiEhNuPgHEZGNK/sB+urVq5Vuk56ebrTtrXQ6Hb7//nuMHDkS0dHR6Nmzp/IcU9xzzz0AShcBEWXXrl0oKSlB27Ztq7x4SFVMmzYN77//Prp3746//vqrwtMgg4KCoNVqK73G6nbXZ1WFVqvFSy+9hCNHjqCgoADXrl3DypUrIUkSrl+/jvDwcGVhjenTp0MuXY1Z+aeiG1HHx8eje/fuSE5Oxs8//4wHH3ywwve++Tq5W6WkpOD69euVzu/bb7/FuHHjEBoaik2bNhmdBmqqvn37QpIk7Nixo8rPJSKyRmzMiIhsXIsWLeDg4IADBw6gpKSkwm3KFucIDw+v9HW0Wi0WL16MMWPG4MiRI+jZsyfS0tJMqqFHjx4IDAzEzp07sX379qpP4g4kScIHH3wAABg2bJjZX79MWVPWrVs3rF27ttJrt5ydndGpUyecOnUKFy5cMHpMlmVERUXB1dUVHTp0EFLn8uXLAQBDhw6t0vPi4+PRo0cPJCcn46effsLgwYMr3bZbt24AgI0bN5Z7bMOGDUbb3Ozbb7/Fc889h5YtW2Lz5s1GN9muirKjr2WNJxGR6llwqX4iIqohTz31lAxAfvfdd8s9dvToUdnNzU12d3eXr127ZvRYRfcPkyRJfuaZZ5R7W6WmpppUw9q1a2WtVivXrVtX3rhxY4XbnDhxQgYgd+vW7Y51lLn5BtOhoaHlbhZ9u/uYVcW0adNkAPL9999f7ibWFanqDaZvdaf7mMmyLGdlZZUb2759u+zq6io3btxYzs7OvmOdZeLj4+VGjRrJdnZ28q+//nrH7YuLi+WgoKDb3mD61tq//fZbWaPRyC1btpRTUlLu+B4HDhyocDw6Olr28PCQ7e3tb5sPEZGaaGT53xP7iYjIZl25cgX33Xcfzpw5g/bt26Nbt25wcnLC6dOnsXr1asiyjOXLl+Oxxx4zel737t2xbds2JCcno169esq4LMuYMGECvvnmG+XIx82PV+bHH3/EM888g7y8PLRp0waRkZHw9vZGZmYm4uPjsWnTJpSUlGDq1KnKEbCb63jllVfg5uYGSZKQnZ2NuLg47NixAwUFBbj33nvx448/omHDhkbv2aRJE6SkpNzVNWZLlizBmDFjYGdnh//+978VXg/VvXt3o3u0SZKEAQMGYMOGDejSpQu6deuGs2fP4rfffkOTJk2wb9++ckeLXn31VeUU0aioKCQlJeGRRx5R3u+ZZ57Bfffdp2zft29f5OfnIzw8HB4eHjh27Bj+/vtveHt7IyoqCm3btjV5jk2aNMGFCxfQpUsX5T5ktypb2bLMli1b0K9fPzg5OWHo0KFwd3fHr7/+igsXLuDTTz/FK6+8omy7efNm9O7dG7Is47nnnqtwf7l1MZImTZrAzs4O7du3R6NGjVBUVIRTp04hKioKsixj7ty5ePHFF02eIxGRVbNkV0hERDUnMzNTfvfdd+U2bdrIrq6usr29vdywYUN5+PDh8uHDhyt8zu2OVEmSJD///PMyALlFixZyUlKSSXUkJSXJb731ltyxY0fZ09NT1ul0sqenp9yxY0f5tddek48fP15pHWX/2NnZyV5eXnKbNm3kp59+Wl6/fr1sMBgqfD9zHDF79913jd6/on8qOhpZUFAgT58+XW7atKns4OAg16tXT37mmWcqPVrUuHHj277H4sWLjbb/6quv5I4dO8p6vV52cHCQmzZtKv/3v/816WjUre40v8p+ZNi3b5/8wAMPyB4eHrKzs7PcqVMneeXKleW2W7x48R1ff9SoUUbP+eabb+SHHnpIbtSokezs7Cw7OjrKTZo0kZ966il57969VZ4jEZE14xEzIiIiIiIiC+PiH0RERERERBbGxoyIiIiIiMjC2JgRERERERFZGBszIiIiIiIiC2NjRkREREREZGFszIiIiIiIiCyMjRkREREREZGFsTEjIiIiIiKyMDZmREREREREFsbGjIiIiIiIyMLYmBERERER0V0ZPXq0pUtQPTZmRERERERkdtOnT0dISAhcXV3h5eWF3r17Y9++fUbbDBo0CI0aNYKTkxPq16+PESNGICkpqcLXO3v2LNzd3eHp6VnusVWrViEkJAROTk5o3bo11q1bd8f6tm7dinbt2sHR0RHBwcFYsmSJ0ePz5s1DeHg4PDw84OHhgcjISPz9999G2zRp0gQajQYajQY6nQ7+/v4YO3Ysrl27dsf3vxUbMyIiIiIiqrL09HSMGjUKjRo1wo8//ojg4GA89thjKCoqAgA0b94cX375JY4dO4adO3eiSZMm6Nu3L9LS0pTX6NGjB37++WecOnUKv/76K86dO4dH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ZGZmyorD4pSc7/79+0WrVq2Eh4eHcHR0FCEhIWLq1Kmq+Wu4EJbJd9KkScXu479/zdqyZYto0qSJsLe3F0FBQWbvUbCf/77mwIEDol27dsLBwUE89NBD4uOPP7ZwAnIpOd8FCxaIkJAQ4ezsLNzd3UXLli3NljVWg/LM917bREdHi/9ewHL69GnRs2dP4eTkJLy9vcVrr70m8vLyZEQhhZLzXbt2rWjSpIlwdXUVLi4uonHjxmLu3LnCYDDIisPiyivfkv7/7/nnnzfbD8+/5Zcvz7/5SpPvzJkzRVhYmCmr8PBwMXv2bLOfdaUdvxohVLw+LBERERERkQ3g4h9ERERERERWxsKMiIiIiIjIyliYERERERERWRkLMyIiIiIiIitjYUZERERERGRlLMyIiIiIiIisjIUZERERERGRlbEwIyIiIiIisjIWZkRERERERFbGwoyIiIiIiMjKWJgRERERERFZGQszIiIiIiIiK2NhRkREREREZGUszIiIiIiIiKyMhRkREREREZGVsTAjIiIiIiKyMhZmRERE/yGEQLdu3dC9e/ciz82ePRuenp44f/68FXpGRES2ioUZERHRf2g0GkRHR2PPnj345ptvTO0pKSl48803MWvWLAQEBFj0PfPy8iy6PyIiUhcWZkRERMUIDAzEl19+iddffx0pKSkQQmDo0KGIiIhAeHg4evbsCVdXV/j5+WHQoEG4du2a6bXr1q1Du3bt4OnpCS8vLzz66KNITk42PX/69GloNBosXboUHTt2hKOjI3766SdrDJOIiBRCI4QQ1u4EERGRUvXr1w/p6el44okn8P777+Pw4cMICwvDiy++iMGDByMrKwvjxo2DXq/H5s2bAQC//vorNBoNGjVqhJs3b2LixIk4ffo0EhMTodVqcfr0adSqVQs1a9bE9OnTER4eDkdHR1StWtXKoyUiImthYUZERHQXV65cQVhYGNLS0vDrr78iKSkJ27Ztw/r1603bnD9/HoGBgTh+/Djq1q1bZB/Xrl2Dj48PDh06hAYNGpgKsy+++AKvvvpqeQ6HiIgUipcyEhER3YWvry+GDx+OkJAQ9OvXDwcOHMCWLVvg6upq+le/fn0AMF2uePLkSQwYMABBQUFwd3dHzZo1AQBnz54123fz5s3LdSxERKRcOmt3gIiISOl0Oh10uvz/y7x58yb69OmDTz75pMh2BZci9unTBzVq1MD8+fNRrVo1GI1GNGjQALm5uWbbu7i4yO88ERGpAgszIiKiMmjatCl+/fVX1KxZ01SsFXb9+nUcP34c8+fPR/v27QEA27dvL+9uEhGRyvBSRiIiojKIjIxEWloaBgwYgH379iE5ORnr16/HCy+8AIPBgMqVK8PLywvz5s3D33//jc2bNyMqKsra3SYiIoVjYUZERFQG1apVw44dO2AwGBAREYGGDRtizJgx8PT0hFarhVarxZIlS7B//340aNAAY8eOxbRp06zdbSIiUjiuykhERERERGRl/MaMiIiIiIjIyliYERERERERWRkLMyIiIiIiIitjYUZERERERGRlLMyIiIiIiIisjIUZERERERGRlbEwIyIiIiIisjIWZkRERERERFbGwoyIiIiIiMjKWJgRERERERFZGQszIiIiIiIiK2NhRkREREREZGX/D2BLLaftGBGFAAAAAElFTkSuQmCC", "text/plain": [ - "
" + "
" ] }, "metadata": {}, @@ -490,11 +360,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'chart_generator': {'messages': [AIMessage(content=\"Unfortunately, I made an error in the provided code. There was a mismatch in the dimensions of the 'Year' and 'GDP (Billion USD)' data arrays, which caused a ValueError. Additionally, I mistakenly included placeholders for the years 2022 and 2023 without having the actual GDP data for those years.\\n\\nLet's correct this and generate a line graph with the data we have up to 2021. I will revise the code to only include the years for which we have data, and then we can create the graph accordingly.\", additional_kwargs={'tool_calls': [{'id': 'call_1zGQMGouC0oFQJRUkNPvs9zX', 'function': {'arguments': '{\"code\":\"import matplotlib.pyplot as plt\\\\n\\\\n# UK GDP data obtained from search results for 2018-2021 (in billion USD)\\\\n# No data for 2022 and 2023 are available\\\\n# Note: 2021 data is used as a placeholder and should be updated when actual figures are available\\\\ngdp_data = {\\\\n \\'Year\\': [2018, 2019, 2020, 2021],\\\\n \\'GDP (Billion USD)\\': [2851.41, 2851.41, 2697.81, 3141.51]\\\\n}\\\\n\\\\n# Plot a line graph\\\\nplt.figure(figsize=(10, 5))\\\\nplt.plot(gdp_data[\\'Year\\'], gdp_data[\\'GDP (Billion USD)\\'], marker=\\'o\\')\\\\n\\\\n# Title and labels\\\\nplt.title(\\'UK GDP from 2018 to 2021\\')\\\\nplt.xlabel(\\'Year\\')\\\\nplt.ylabel(\\'GDP (Billion USD)\\')\\\\n\\\\n# Show grid\\\\nplt.grid(True)\\\\n\\\\n# Display the graph\\\\nplt.show()\"}', 'name': 'python_repl'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 359, 'prompt_tokens': 12796, 'total_tokens': 13155}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, name='chart_generator', id='run-0d4a67d2-696a-4955-990b-9a9d775b7635-0', tool_calls=[{'name': 'python_repl', 'args': {'code': \"import matplotlib.pyplot as plt\\n\\n# UK GDP data obtained from search results for 2018-2021 (in billion USD)\\n# No data for 2022 and 2023 are available\\n# Note: 2021 data is used as a placeholder and should be updated when actual figures are available\\ngdp_data = {\\n 'Year': [2018, 2019, 2020, 2021],\\n 'GDP (Billion USD)': [2851.41, 2851.41, 2697.81, 3141.51]\\n}\\n\\n# Plot a line graph\\nplt.figure(figsize=(10, 5))\\nplt.plot(gdp_data['Year'], gdp_data['GDP (Billion USD)'], marker='o')\\n\\n# Title and labels\\nplt.title('UK GDP from 2018 to 2021')\\nplt.xlabel('Year')\\nplt.ylabel('GDP (Billion USD)')\\n\\n# Show grid\\nplt.grid(True)\\n\\n# Display the graph\\nplt.show()\"}, 'id': 'call_1zGQMGouC0oFQJRUkNPvs9zX'}])], 'sender': 'chart_generator'}}\n", - "----\n", - "{'call_tool': {'messages': [ToolMessage(content=\"Successfully executed:\\n```python\\nimport matplotlib.pyplot as plt\\n\\n# UK GDP data obtained from search results for 2018-2021 (in billion USD)\\n# No data for 2022 and 2023 are available\\n# Note: 2021 data is used as a placeholder and should be updated when actual figures are available\\ngdp_data = {\\n 'Year': [2018, 2019, 2020, 2021],\\n 'GDP (Billion USD)': [2851.41, 2851.41, 2697.81, 3141.51]\\n}\\n\\n# Plot a line graph\\nplt.figure(figsize=(10, 5))\\nplt.plot(gdp_data['Year'], gdp_data['GDP (Billion USD)'], marker='o')\\n\\n# Title and labels\\nplt.title('UK GDP from 2018 to 2021')\\nplt.xlabel('Year')\\nplt.ylabel('GDP (Billion USD)')\\n\\n# Show grid\\nplt.grid(True)\\n\\n# Display the graph\\nplt.show()\\n```\\nStdout: \\n\\nIf you have completed all tasks, respond with FINAL ANSWER.\", name='python_repl', tool_call_id='call_1zGQMGouC0oFQJRUkNPvs9zX')]}}\n", - "----\n", - "{'chart_generator': {'messages': [AIMessage(content=\"FINAL ANSWER\\n\\nI have generated a line graph for the UK's GDP from 2018 to 2021 using the available data. Unfortunately, due to the lack of data for 2022 and 2023, the graph only includes figures up to 2021. Here is the graph:\\n\\n[Graph Image]\\n\\nPlease note that the data for 2022 and 2023 should be added to this graph once it becomes available to complete the analysis for the past five years.\", response_metadata={'token_usage': {'completion_tokens': 99, 'prompt_tokens': 13412, 'total_tokens': 13511}, 'model_name': 'gpt-4o', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-3474a61c-0773-4e44-bd6e-2e88cf56bb90-0')], 'sender': 'chart_generator'}}\n", + "{'chart_generator': {'messages': [HumanMessage(content=\"First, get the UK's GDP over the past 5 years, then make a line chart of it. Once you make the chart, finish.\", additional_kwargs={}, response_metadata={}, id='fa1f5e95-9e1a-47d4-b4b6-e93f345e339d'), AIMessage(content=[{'text': \"I'll help search for the UK's GDP data over the past 5 years. Then my colleague can help create the line chart.\", 'type': 'text'}, {'id': 'toolu_01Jd9dxa4Ss2NhzBhCuwUX3E', 'input': {'query': 'UK GDP annual data past 5 years 2019-2023'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_014nCkfVHnG6LAsiS6pY7zcd', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 555, 'output_tokens': 101}}, id='run-e2297529-9972-4de6-835d-23d920b0e29b-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'UK GDP annual data past 5 years 2019-2023'}, 'id': 'toolu_01Jd9dxa4Ss2NhzBhCuwUX3E', 'type': 'tool_call'}], usage_metadata={'input_tokens': 555, 'output_tokens': 101, 'total_tokens': 656, 'input_token_details': {}}), ToolMessage(content='[{\"url\": \"https://www.macrotrends.net/global-metrics/countries/GBR/united-kingdom/gdp-gross-domestic-product\", \"content\": \"Dollar figures for GDP are converted from domestic currencies using single year official exchange rates. For a few countries where the official exchange rate does not reflect the rate effectively applied to actual foreign exchange transactions, an alternative conversion factor is used. U.K. gdp for 2023 was $3,340.03B, a 8.13% increase from 2022.\"}, {\"url\": \"https://www.statista.com/topics/3795/gdp-of-the-uk/\", \"content\": \"Monthly growth of gross domestic product in the United Kingdom from January 2019 to November 2023\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nContribution to gross domestic product growth in the United Kingdom in January 2023, by sector\\\\nGDP growth rate in the UK 1999-2021, by country\\\\nAnnual growth rates of gross domestic product in the United Kingdom from 1999 to 2021, by country\\\\nGDP growth rate in the UK 2021, by region\\\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\\\nGDP growth of Scotland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Scotland in 2021, by local (ITL 3) area\\\\nGDP growth of Wales 2021, by local area\\\\nAnnual growth rates of gross domestic product in Wales in 2021, by local (ITL 3) area\\\\nGDP growth of Northern Ireland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Northern Ireland in 2021, by local (ITL 3) area\\\\nGDP per capita\\\\nGDP per capita\\\\nGDP per capita in the UK 1955-2022\\\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\\\nAnnual GDP per capita growth in the UK 1956-2022\\\\nAnnual GDP per capita growth in the United Kingdom from 1956 to 2022\\\\nQuarterly GDP per capita in the UK 2019-2023\\\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nQuarterly GDP per capita growth in the UK 2019-2023\\\\nQuarterly GDP per capita growth in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nGDP per capita of the UK 1999-2021, by country\\\\nGross domestic product per capita of the United Kingdom from 1999 to 2021, by country (in GBP)\\\\nGDP per capita of the UK 2021, by region\\\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\\\nGlobal Comparisons\\\\nGlobal Comparisons\\\\nCountries with the largest gross domestic product (GDP) 2022\\\\n Monthly GDP of the UK 2019-2023\\\\nMonthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100)\\\\nGVA of the UK 2022, by sector\\\\nGross value added of the United Kingdom in 2022, by industry sector (in million GBP)\\\\nGDP of the UK 2021, by country\\\\nGross domestic product of the United Kingdom in 2021, by country (in million GBP)\\\\nGDP of the UK 2021, by region\\\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\\\nGDP of Scotland 2021, by local area\\\\nGross domestic product of Scotland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Wales 2021, by local area\\\\nGross domestic product of Wales in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Northern Ireland 2021, by local area\\\\nGross domestic product of Northern Ireland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP growth\\\\nGDP growth\\\\nGDP growth forecast for the UK 2000-2028\\\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\\\nAnnual GDP growth in the UK 1949-2022\\\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\\\nQuarterly GDP growth of the UK 2019-2023\\\\nQuarterly growth of gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023\\\\nMonthly GDP growth of the UK 2019-2023\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nUK GDP - Statistics & Facts\\\\nUK economy expected to shrink in 2023\\\\nCharacteristics of UK GDP\\\\nKey insights\\\\nDetailed statistics\\\\nGDP of the UK 1948-2022\\\\nDetailed statistics\\\\nAnnual GDP growth in the UK 1949-2022\\\\nDetailed statistics\\\\nGDP per capita in the UK 1955-2022\\\\nEditor’s Picks\\\\nCurrent statistics on this topic\\\\nCurrent statistics on this topic\\\\nKey Economic Indicators\\\\nMonthly GDP growth of the UK 2019-2023\\\\nKey Economic Indicators\\\\nMonthly GDP of the UK 2019-2023\\\\nKey Economic Indicators\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nRelated topics\\\\nRecommended\\\\nRecommended statistics\\\\nGDP\\\\nGDP\\\\nGDP of the UK 1948-2022\\\\nGross domestic product of the United Kingdom from 1948 to 2022 (in million GBP)\\\\nQuarterly GDP of the UK 2019-2023\\\\nQuarterly gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in million GBP)\\\\n The 20 countries with the largest gross domestic product (GDP) in 2022 (in billion U.S. dollars)\\\\nGDP of European countries in 2022\\\\nGross domestic product at current market prices of selected European countries in 2022 (in million euros)\\\\nReal GDP growth rates in Europe 2023\\\\nAnnual real gross domestic product (GDP) growth rate in European countries in 2023\\\\nGross domestic product (GDP) of Europe\\'s largest economies 1980-2028\\\\nGross domestic product (GDP) at current prices of Europe\\'s largest economies from 1980 to 2028 (in billion U.S dollars)\\\\nUnited Kingdom\\'s share of global gross domestic product (GDP) 2028\\\\nUnited Kingdom (UK): Share of global gross domestic product (GDP) adjusted for Purchasing Power Parity (PPP) from 2018 to 2028\\\\nRelated topics\\\\nRecommended\\\\nReport on the topic\\\\nKey figures\\\\nThe most important key figures provide you with a compact summary of the topic of \\\\\"UK GDP\\\\\" and take you straight to the corresponding statistics.\\\\n Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\xa0Mio. facts.\\\\n\"}, {\"url\": \"https://data.worldbank.org/indicator/NY.GDP.MKTP.KD.ZG?locations=GB\", \"content\": \"GDP growth (annual %) - United Kingdom | Data - World Bank Data\"}, {\"url\": \"https://www.statista.com/topics/6500/the-british-economy/\", \"content\": \"Output per hour worked in the UK 1971 to 2023\\\\nEconomic output per hour worked in the United Kingdom from 1st quarter 1971 to 2nd quarter 2023 (2019=100)\\\\nAnnual unemployment rate in the UK 2000-2028\\\\nAnnual unemployment rate in the United Kingdom from 2000 to 2028\\\\nInflation\\\\nInflation\\\\nInflation rate in the UK 1989-2023\\\\nInflation rate for the Consumer Price Index (CPI) in the United Kingdom from January 1989 to October 2023\\\\nRPI inflation rate in the UK 1948-2023\\\\nInflation rate for the Retail Price Index (RPI) in the United Kingdom from June 1948 to October 2023\\\\nCPIH inflation rate in the UK 1989-2023\\\\nInflation rate for the Consumer Price Index including owner occupiers\\' housing costs (CPIH) in the United Kingdom from January 1989 to October 2023\\\\nPPI in the UK 2010-2023\\\\nProducer Price Index (PPI) in the United Kingdom from October 2010 to October 2023\\\\nCPI inflation rate in the UK 2023, by sector\\\\nInflation rate for the Consumer Price Index (CPI) in the United Kingdom in October 2023, by sector\\\\nConsumer Price Index in the UK 1988-2023\\\\nConsumer Price Index (CPI) in the United Kingdom from 1st quarter 1988 to 3rd quarter 2023\\\\nRetail Price Index in the UK 1987-2023\\\\nRetail Price Index (RPI) in the United Kingdom from 1st quarter 1987 to 3rd quarter 2023\\\\nConsumer Price Index including housing in the UK 1988-2023\\\\nConsumer Price Index including owner occupiers\\' housing costs (CPIH) in the United Kingdom from 1st quarter 1988 to 3rd quarter 2023\\\\nRPI annual inflation rate UK 2000-2028\\\\nAnnual inflation rate of the Retail Price Index in the United Kingdom from 2000 to 2028\\\\nCPI annual inflation rate UK 2000-2028\\\\nAnnual inflation rate of the Consumer Price Index in the United Kingdom from 2000 to 2028\\\\nGovernment finances\\\\nGovernment finances\\\\nGovernment spending as a percentage of GDP in the UK 1900-2029\\\\nTotal managed expenditure expressed as a percentage of GDP in the United Kingdom from 1900/01 to 2028/29\\\\nGovernment revenue as a percentage of GDP in the UK 1900-2029\\\\nTotal public sector current receipts expressed as a percentage of GDP in the United Kingdom from 1900/01 to 2028/29 (in million GBP)\\\\nGovernment borrowing as a percentage of GDP in the UK 1900-2029\\\\nPublic sector borrowing expressed as a percentage of GDP in the United Kingdom from 1900/01 to 2028/29\\\\nNational debt as a percentage of GDP in the UK 1900-2029\\\\nPublic sector net debt expressed as a percentage of GDP in the United Kingdom from 1900/01 to 2028/29\\\\nPublic sector spending in the United Kingdom 2023/24\\\\nBudgeted public sector expenditure on services in the United Kingdom in 2023/24, by function (in billion GBP)\\\\nGovernment revenue sources in the United Kingdom 2023/24\\\\nExpected public sector current receipts in the United Kingdom in 2023/24, by function (in billion GBP)\\\\nBusiness Enterprise\\\\nBusiness Enterprise\\\\nLargest companies in the United Kingdom based on revenue 2022\\\\nLargest companies in the United Kingdom based on revenue in 2022 (in billion US dollars)\\\\nLargest UK companies based on number of global employees 2020\\\\nLargest companies based in the United Kingdom on number of employees worldwide in 2020 (in 1,000s)\\\\nNumber of private sector businesses in the UK 2000-2023\\\\nNumber of private sector businesses in the United Kingdom from 2000 to 2023 (in millions)\\\\nNumber of private sector businesses in the UK 2023, by sector\\\\nNumber of private sector businesses in the United Kingdom in 2023, by sector\\\\nNumber of businesses by enterprise size in the UK 2023\\\\nNumber of private sector businesses in the United Kingdom in 2023, by employment size\\\\nNumber of private sector businesses in the UK 2023, by region\\\\nNumber of private sector businesses in the United Kingdom in 2023, by region\\\\nNumber of local business units in the UK 2012-2023\\\\nNumber of local units in VAT and/or PAYE based enterprises in the United Kingdom from 2012 to 2023 (in millions)\\\\nBusiness investment index in the UK 1997-2023\\\\nBusiness investment index in the United Kingdom from 1st quarter 1997 to 2nd quarter 2023 (Q1 1997=100)\\\\nBusiness confidence Index in the UK 1977-2023\\\\nBusiness confidence Index of the United Kingdom from March 1977 to November 2023 (100 = long-term average)\\\\nRelated topics\\\\nRecommended\\\\nReport on the topic\\\\nKey figures\\\\nThe most important key figures provide you with a compact summary of the topic of \\\\\"The UK economy\\\\\" and take you straight to the corresponding statistics.\\\\n Monthly GDP growth of the UK 2020-2023\\\\nMonthly growth of gross domestic product in the United Kingdom from January 2020 to September 2023\\\\nGDP of the UK 2021, by region\\\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\\\nGDP growth rate in the UK 2021, by region\\\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\\\nGDP per capita of the UK 2021, by region\\\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\\\nGDP growth forecast for the UK 2000-2028\\\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\\\nLabor Market\\\\nLabor Market\\\\nUnemployment rate of the UK 1971-2023\\\\nUnemployment rate in the United Kingdom from March 1971 to September 2023\\\\nEmployment rate in the UK 1971-2022\\\\nEmployment rate in the United Kingdom from March 1971 to July 2023\\\\nNumber of people unemployed in the UK 1971-2023\\\\nNumber of people unemployed in the United Kingdom from March 1971 to July 2023 (in 1,000s)\\\\nNumber of people employed in the UK 1971-2021\\\\nNumber of people employed in the United Kingdom from March 1971 to July 2023 (in 1,000s)\\\\nUnemployment rate in the UK 1971-2023, by gender\\\\nUnemployment rate in the United Kingdom from 1st quarter 1971 to 2nd quarter 2023, by gender\\\\nUnemployment rate in the UK 1992-2023, by age group\\\\nUnemployment rate in the United Kingdom from May 1992 to July 2023, by age group\\\\nYouth unemployment rate in the UK 1992-2023\\\\nYouth unemployment rate in the United Kingdom from May 1992 to July 2023\\\\nAverage annual earnings for full-time employees in the UK 1999-2023\\\\nMedian annual earnings for full-time employees in the United Kingdom from 1999 to 2023 (in nominal GBP)\\\\nAverage weekly earning growth in the UK 2001-2023\\\\nAverage year-on-year growth of weekly earnings (3 month average) in the United Kingdom from March 2001 to October 2023\\\\nNumber of redundancies in the UK 1995-2023\\\\nAverage number of people made redundant in the United Kingdom from May 1995 to July 2023 (in 1,000s)\\\\nOverall weekly hours worked in the UK 1971-2023\\\\nOverall weekly hours worked for all employees in the United Kingdom from 1st quarter 1971 to 2nd quarter 2023 (in million hours worked)\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nThe UK economy - Statistics & Facts\\\\nUK households under pressure in 2023\\\\nCoronavirus devastates UK economy in 2020\\\\nKey insights\\\\nDetailed statistics\\\\nGDP of the UK 1948-2022\\\\nDetailed statistics\\\\nUnemployment rate of the UK 1971-2023\\\\nDetailed statistics\\\\nInflation rate in the UK 1989-2023\\\\nEditor’s Picks\\\\nCurrent statistics on this topic\\\\nCurrent statistics on this topic\\\\nWages & Salaries\\\\nAverage weekly earning growth in the UK 2001-2023\\\\nIncome & Expenditure\\\\nPublic sector spending in the United Kingdom 2023/24\\\\nEmployment\\\\nNumber of people employed in the UK 1971-2021\\\\nRelated topics\\\\nRecommended\\\\nRecommended statistics\\\\nGross domestic product\\\\nGross domestic product\\\\nGDP of the UK 1948-2022\\\\nGross domestic product of the United Kingdom from 1948 to 2022 (in million GBP)\\\\nAnnual GDP growth in the UK 1949-2022\\\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\\\nGDP per capita in the UK 1955-2022\\\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\\\nQuarterly GDP of the UK 1955-2023\\\\nQuarterly gross domestic product in the United Kingdom from 1st quarter 1955 to 3rd quarter 2023 (in million GBP)\\\\nQuarterly GDP growth of the UK 2015-2023\\\\nQuarterly growth of gross domestic product in the United Kingdom from 1st quarter 2015 to 3rd quarter 2023\\\\nQuarterly GDP per capita in the UK 1955-2023\\\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 1955 to 3rd quarter 2023 (in GBP)\\\\nMonthly GDP of the UK 1997-2023\\\\nMonthly index of gross domestic product in the United Kingdom from January 1997 to September 2023 (2019=100)\\\\n GDP\\\\nAnnual GDP growth in the UK 1949-2022\\\\nQuarterly GDP per capita growth in the UK 2015-2023\\\\nMonthly GDP growth of the UK 2020-2023\\\\nGDP per capita in the UK 1955-2022\\\\nLabor market\\\\nNumber of people employed in the UK 1971-2021\\\\nNumber of people unemployed in the UK 1971-2023\\\\nDaily number of jobs furloughed in the UK 2020-2021\\\\nAverage annual earnings for full-time employees in the UK 1999-2023\\\\nForecasts for 2023\\\\nGDP growth forecast for the UK 2000-2028\\\\nAnnual unemployment rate in the UK 2000-2028\\\\nCPI annual inflation rate UK 2000-2028\\\\nRPI annual inflation rate UK 2000-2028\\\\n Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\xa0Mio. facts.\\\\n\"}, {\"url\": \"https://data.worldbank.org/indicator/NY.GDP.MKTP.KD.ZG?locations=GB&most_recent_value_desc=false\", \"content\": \"GDP growth (annual %) - United Kingdom | Data Data GDP growth (annual %)United Kingdom Data Catalog Data Programs International Debt Statistics Other Books and Reports For Developers GDP growth (annual %) - United Kingdom ====================================== Similar values Highest values Lowest values GDP (constant 2015 US$) GDP (current US$) GDP (constant LCU) GDP: linked series (current LCU) GDP, PPP (constant 2021 international $) GDP (current LCU) GDP, PPP (current international $) GDP per capita growth (annual %) Country Most Recent Value All Countries and Economies Country Most Recent Value This site uses cookies to optimize functionality and give you the best possible experience. If you continue to navigate this website beyond this page, cookies will be placed on your browser.\"}]', name='tavily_search_results_json', id='4c88089f-0ac4-4eeb-9141-722f0463b78d', tool_call_id='toolu_01Jd9dxa4Ss2NhzBhCuwUX3E', artifact={'query': 'UK GDP annual data past 5 years 2019-2023', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'title': 'U.K. GDP 1960-2024 - Macrotrends', 'url': 'https://www.macrotrends.net/global-metrics/countries/GBR/united-kingdom/gdp-gross-domestic-product', 'content': 'Dollar figures for GDP are converted from domestic currencies using single year official exchange rates. For a few countries where the official exchange rate does not reflect the rate effectively applied to actual foreign exchange transactions, an alternative conversion factor is used. U.K. gdp for 2023 was $3,340.03B, a 8.13% increase from 2022.', 'score': 0.97675806, 'raw_content': None}, {'title': 'UK GDP - Statistics & Facts | Statista', 'url': 'https://www.statista.com/topics/3795/gdp-of-the-uk/', 'content': 'Monthly growth of gross domestic product in the United Kingdom from January 2019 to November 2023\\nContribution to GDP growth in the UK 2023, by sector\\nContribution to gross domestic product growth in the United Kingdom in January 2023, by sector\\nGDP growth rate in the UK 1999-2021, by country\\nAnnual growth rates of gross domestic product in the United Kingdom from 1999 to 2021, by country\\nGDP growth rate in the UK 2021, by region\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\nGDP growth of Scotland 2021, by local area\\nAnnual growth rates of gross domestic product in Scotland in 2021, by local (ITL 3) area\\nGDP growth of Wales 2021, by local area\\nAnnual growth rates of gross domestic product in Wales in 2021, by local (ITL 3) area\\nGDP growth of Northern Ireland 2021, by local area\\nAnnual growth rates of gross domestic product in Northern Ireland in 2021, by local (ITL 3) area\\nGDP per capita\\nGDP per capita\\nGDP per capita in the UK 1955-2022\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\nAnnual GDP per capita growth in the UK 1956-2022\\nAnnual GDP per capita growth in the United Kingdom from 1956 to 2022\\nQuarterly GDP per capita in the UK 2019-2023\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\nQuarterly GDP per capita growth in the UK 2019-2023\\nQuarterly GDP per capita growth in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\nGDP per capita of the UK 1999-2021, by country\\nGross domestic product per capita of the United Kingdom from 1999 to 2021, by country (in GBP)\\nGDP per capita of the UK 2021, by region\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\nGlobal Comparisons\\nGlobal Comparisons\\nCountries with the largest gross domestic product (GDP) 2022\\n Monthly GDP of the UK 2019-2023\\nMonthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100)\\nGVA of the UK 2022, by sector\\nGross value added of the United Kingdom in 2022, by industry sector (in million GBP)\\nGDP of the UK 2021, by country\\nGross domestic product of the United Kingdom in 2021, by country (in million GBP)\\nGDP of the UK 2021, by region\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\nGDP of Scotland 2021, by local area\\nGross domestic product of Scotland in 2021, by local (ITL 3) area (in million GBP)\\nGDP of Wales 2021, by local area\\nGross domestic product of Wales in 2021, by local (ITL 3) area (in million GBP)\\nGDP of Northern Ireland 2021, by local area\\nGross domestic product of Northern Ireland in 2021, by local (ITL 3) area (in million GBP)\\nGDP growth\\nGDP growth\\nGDP growth forecast for the UK 2000-2028\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\nAnnual GDP growth in the UK 1949-2022\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\nQuarterly GDP growth of the UK 2019-2023\\nQuarterly growth of gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023\\nMonthly GDP growth of the UK 2019-2023\\n Transforming data into design:\\nStatista Content & Design\\nStrategy and business building for the data-driven economy:\\nUK GDP - Statistics & Facts\\nUK economy expected to shrink in 2023\\nCharacteristics of UK GDP\\nKey insights\\nDetailed statistics\\nGDP of the UK 1948-2022\\nDetailed statistics\\nAnnual GDP growth in the UK 1949-2022\\nDetailed statistics\\nGDP per capita in the UK 1955-2022\\nEditor’s Picks\\nCurrent statistics on this topic\\nCurrent statistics on this topic\\nKey Economic Indicators\\nMonthly GDP growth of the UK 2019-2023\\nKey Economic Indicators\\nMonthly GDP of the UK 2019-2023\\nKey Economic Indicators\\nContribution to GDP growth in the UK 2023, by sector\\nRelated topics\\nRecommended\\nRecommended statistics\\nGDP\\nGDP\\nGDP of the UK 1948-2022\\nGross domestic product of the United Kingdom from 1948 to 2022 (in million GBP)\\nQuarterly GDP of the UK 2019-2023\\nQuarterly gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in million GBP)\\n The 20 countries with the largest gross domestic product (GDP) in 2022 (in billion U.S. dollars)\\nGDP of European countries in 2022\\nGross domestic product at current market prices of selected European countries in 2022 (in million euros)\\nReal GDP growth rates in Europe 2023\\nAnnual real gross domestic product (GDP) growth rate in European countries in 2023\\nGross domestic product (GDP) of Europe\\'s largest economies 1980-2028\\nGross domestic product (GDP) at current prices of Europe\\'s largest economies from 1980 to 2028 (in billion U.S dollars)\\nUnited Kingdom\\'s share of global gross domestic product (GDP) 2028\\nUnited Kingdom (UK): Share of global gross domestic product (GDP) adjusted for Purchasing Power Parity (PPP) from 2018 to 2028\\nRelated topics\\nRecommended\\nReport on the topic\\nKey figures\\nThe most important key figures provide you with a compact summary of the topic of \"UK GDP\" and take you straight to the corresponding statistics.\\n Industry Overview\\nDigital & Trend reports\\nOverview and forecasts on trending topics\\nIndustry & Market reports\\nIndustry and market insights and forecasts\\nCompanies & Products reports\\nKey figures and rankings about companies and products\\nConsumer & Brand reports\\nConsumer and brand insights and preferences in various industries\\nPolitics & Society reports\\nDetailed information about political and social topics\\nCountry & Region reports\\nAll key figures about countries and regions\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\nInsights on consumer attitudes and behavior worldwide\\nBusiness information on 100m+ public and private companies\\nExplore Company Insights\\nDetailed information for 39,000+ online stores and marketplaces\\nDirectly accessible data for 170 industries from 150+ countries\\nand over 1\\xa0Mio. facts.\\n', 'score': 0.97057647, 'raw_content': None}, {'title': 'GDP growth (annual %) - United Kingdom | Data - World Bank Data', 'url': 'https://data.worldbank.org/indicator/NY.GDP.MKTP.KD.ZG?locations=GB', 'content': 'GDP growth (annual %) - United Kingdom | Data - World Bank Data', 'score': 0.97052056, 'raw_content': None}, {'title': 'The UK economy - Statistics & Facts | Statista', 'url': 'https://www.statista.com/topics/6500/the-british-economy/', 'content': 'Output per hour worked in the UK 1971 to 2023\\nEconomic output per hour worked in the United Kingdom from 1st quarter 1971 to 2nd quarter 2023 (2019=100)\\nAnnual unemployment rate in the UK 2000-2028\\nAnnual unemployment rate in the United Kingdom from 2000 to 2028\\nInflation\\nInflation\\nInflation rate in the UK 1989-2023\\nInflation rate for the Consumer Price Index (CPI) in the United Kingdom from January 1989 to October 2023\\nRPI inflation rate in the UK 1948-2023\\nInflation rate for the Retail Price Index (RPI) in the United Kingdom from June 1948 to October 2023\\nCPIH inflation rate in the UK 1989-2023\\nInflation rate for the Consumer Price Index including owner occupiers\\' housing costs (CPIH) in the United Kingdom from January 1989 to October 2023\\nPPI in the UK 2010-2023\\nProducer Price Index (PPI) in the United Kingdom from October 2010 to October 2023\\nCPI inflation rate in the UK 2023, by sector\\nInflation rate for the Consumer Price Index (CPI) in the United Kingdom in October 2023, by sector\\nConsumer Price Index in the UK 1988-2023\\nConsumer Price Index (CPI) in the United Kingdom from 1st quarter 1988 to 3rd quarter 2023\\nRetail Price Index in the UK 1987-2023\\nRetail Price Index (RPI) in the United Kingdom from 1st quarter 1987 to 3rd quarter 2023\\nConsumer Price Index including housing in the UK 1988-2023\\nConsumer Price Index including owner occupiers\\' housing costs (CPIH) in the United Kingdom from 1st quarter 1988 to 3rd quarter 2023\\nRPI annual inflation rate UK 2000-2028\\nAnnual inflation rate of the Retail Price Index in the United Kingdom from 2000 to 2028\\nCPI annual inflation rate UK 2000-2028\\nAnnual inflation rate of the Consumer Price Index in the United Kingdom from 2000 to 2028\\nGovernment finances\\nGovernment finances\\nGovernment spending as a percentage of GDP in the UK 1900-2029\\nTotal managed expenditure expressed as a percentage of GDP in the United Kingdom from 1900/01 to 2028/29\\nGovernment revenue as a percentage of GDP in the UK 1900-2029\\nTotal public sector current receipts expressed as a percentage of GDP in the United Kingdom from 1900/01 to 2028/29 (in million GBP)\\nGovernment borrowing as a percentage of GDP in the UK 1900-2029\\nPublic sector borrowing expressed as a percentage of GDP in the United Kingdom from 1900/01 to 2028/29\\nNational debt as a percentage of GDP in the UK 1900-2029\\nPublic sector net debt expressed as a percentage of GDP in the United Kingdom from 1900/01 to 2028/29\\nPublic sector spending in the United Kingdom 2023/24\\nBudgeted public sector expenditure on services in the United Kingdom in 2023/24, by function (in billion GBP)\\nGovernment revenue sources in the United Kingdom 2023/24\\nExpected public sector current receipts in the United Kingdom in 2023/24, by function (in billion GBP)\\nBusiness Enterprise\\nBusiness Enterprise\\nLargest companies in the United Kingdom based on revenue 2022\\nLargest companies in the United Kingdom based on revenue in 2022 (in billion US dollars)\\nLargest UK companies based on number of global employees 2020\\nLargest companies based in the United Kingdom on number of employees worldwide in 2020 (in 1,000s)\\nNumber of private sector businesses in the UK 2000-2023\\nNumber of private sector businesses in the United Kingdom from 2000 to 2023 (in millions)\\nNumber of private sector businesses in the UK 2023, by sector\\nNumber of private sector businesses in the United Kingdom in 2023, by sector\\nNumber of businesses by enterprise size in the UK 2023\\nNumber of private sector businesses in the United Kingdom in 2023, by employment size\\nNumber of private sector businesses in the UK 2023, by region\\nNumber of private sector businesses in the United Kingdom in 2023, by region\\nNumber of local business units in the UK 2012-2023\\nNumber of local units in VAT and/or PAYE based enterprises in the United Kingdom from 2012 to 2023 (in millions)\\nBusiness investment index in the UK 1997-2023\\nBusiness investment index in the United Kingdom from 1st quarter 1997 to 2nd quarter 2023 (Q1 1997=100)\\nBusiness confidence Index in the UK 1977-2023\\nBusiness confidence Index of the United Kingdom from March 1977 to November 2023 (100 = long-term average)\\nRelated topics\\nRecommended\\nReport on the topic\\nKey figures\\nThe most important key figures provide you with a compact summary of the topic of \"The UK economy\" and take you straight to the corresponding statistics.\\n Monthly GDP growth of the UK 2020-2023\\nMonthly growth of gross domestic product in the United Kingdom from January 2020 to September 2023\\nGDP of the UK 2021, by region\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\nGDP growth rate in the UK 2021, by region\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\nGDP per capita of the UK 2021, by region\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\nGDP growth forecast for the UK 2000-2028\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\nLabor Market\\nLabor Market\\nUnemployment rate of the UK 1971-2023\\nUnemployment rate in the United Kingdom from March 1971 to September 2023\\nEmployment rate in the UK 1971-2022\\nEmployment rate in the United Kingdom from March 1971 to July 2023\\nNumber of people unemployed in the UK 1971-2023\\nNumber of people unemployed in the United Kingdom from March 1971 to July 2023 (in 1,000s)\\nNumber of people employed in the UK 1971-2021\\nNumber of people employed in the United Kingdom from March 1971 to July 2023 (in 1,000s)\\nUnemployment rate in the UK 1971-2023, by gender\\nUnemployment rate in the United Kingdom from 1st quarter 1971 to 2nd quarter 2023, by gender\\nUnemployment rate in the UK 1992-2023, by age group\\nUnemployment rate in the United Kingdom from May 1992 to July 2023, by age group\\nYouth unemployment rate in the UK 1992-2023\\nYouth unemployment rate in the United Kingdom from May 1992 to July 2023\\nAverage annual earnings for full-time employees in the UK 1999-2023\\nMedian annual earnings for full-time employees in the United Kingdom from 1999 to 2023 (in nominal GBP)\\nAverage weekly earning growth in the UK 2001-2023\\nAverage year-on-year growth of weekly earnings (3 month average) in the United Kingdom from March 2001 to October 2023\\nNumber of redundancies in the UK 1995-2023\\nAverage number of people made redundant in the United Kingdom from May 1995 to July 2023 (in 1,000s)\\nOverall weekly hours worked in the UK 1971-2023\\nOverall weekly hours worked for all employees in the United Kingdom from 1st quarter 1971 to 2nd quarter 2023 (in million hours worked)\\n Transforming data into design:\\nStatista Content & Design\\nStrategy and business building for the data-driven economy:\\nThe UK economy - Statistics & Facts\\nUK households under pressure in 2023\\nCoronavirus devastates UK economy in 2020\\nKey insights\\nDetailed statistics\\nGDP of the UK 1948-2022\\nDetailed statistics\\nUnemployment rate of the UK 1971-2023\\nDetailed statistics\\nInflation rate in the UK 1989-2023\\nEditor’s Picks\\nCurrent statistics on this topic\\nCurrent statistics on this topic\\nWages & Salaries\\nAverage weekly earning growth in the UK 2001-2023\\nIncome & Expenditure\\nPublic sector spending in the United Kingdom 2023/24\\nEmployment\\nNumber of people employed in the UK 1971-2021\\nRelated topics\\nRecommended\\nRecommended statistics\\nGross domestic product\\nGross domestic product\\nGDP of the UK 1948-2022\\nGross domestic product of the United Kingdom from 1948 to 2022 (in million GBP)\\nAnnual GDP growth in the UK 1949-2022\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\nGDP per capita in the UK 1955-2022\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\nQuarterly GDP of the UK 1955-2023\\nQuarterly gross domestic product in the United Kingdom from 1st quarter 1955 to 3rd quarter 2023 (in million GBP)\\nQuarterly GDP growth of the UK 2015-2023\\nQuarterly growth of gross domestic product in the United Kingdom from 1st quarter 2015 to 3rd quarter 2023\\nQuarterly GDP per capita in the UK 1955-2023\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 1955 to 3rd quarter 2023 (in GBP)\\nMonthly GDP of the UK 1997-2023\\nMonthly index of gross domestic product in the United Kingdom from January 1997 to September 2023 (2019=100)\\n GDP\\nAnnual GDP growth in the UK 1949-2022\\nQuarterly GDP per capita growth in the UK 2015-2023\\nMonthly GDP growth of the UK 2020-2023\\nGDP per capita in the UK 1955-2022\\nLabor market\\nNumber of people employed in the UK 1971-2021\\nNumber of people unemployed in the UK 1971-2023\\nDaily number of jobs furloughed in the UK 2020-2021\\nAverage annual earnings for full-time employees in the UK 1999-2023\\nForecasts for 2023\\nGDP growth forecast for the UK 2000-2028\\nAnnual unemployment rate in the UK 2000-2028\\nCPI annual inflation rate UK 2000-2028\\nRPI annual inflation rate UK 2000-2028\\n Industry Overview\\nDigital & Trend reports\\nOverview and forecasts on trending topics\\nIndustry & Market reports\\nIndustry and market insights and forecasts\\nCompanies & Products reports\\nKey figures and rankings about companies and products\\nConsumer & Brand reports\\nConsumer and brand insights and preferences in various industries\\nPolitics & Society reports\\nDetailed information about political and social topics\\nCountry & Region reports\\nAll key figures about countries and regions\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\nInsights on consumer attitudes and behavior worldwide\\nBusiness information on 100m+ public and private companies\\nExplore Company Insights\\nDetailed information for 39,000+ online stores and marketplaces\\nDirectly accessible data for 170 industries from 150+ countries\\nand over 1\\xa0Mio. facts.\\n', 'score': 0.95998776, 'raw_content': None}, {'title': 'GDP growth (annual %) - United Kingdom | Data - World Bank Data', 'url': 'https://data.worldbank.org/indicator/NY.GDP.MKTP.KD.ZG?locations=GB&most_recent_value_desc=false', 'content': 'GDP growth (annual %) - United Kingdom | Data Data GDP growth (annual %)United Kingdom Data Catalog Data Programs International Debt Statistics Other Books and Reports For Developers GDP growth (annual %) - United Kingdom ====================================== Similar values Highest values Lowest values GDP (constant 2015 US$) GDP (current US$) GDP (constant LCU) GDP: linked series (current LCU) GDP, PPP (constant 2021 international $) GDP (current LCU) GDP, PPP (current international $) GDP per capita growth (annual %) Country Most Recent Value All Countries and Economies Country Most Recent Value This site uses cookies to optimize functionality and give you the best possible experience. If you continue to navigate this website beyond this page, cookies will be placed on your browser.', 'score': 0.7892337, 'raw_content': None}], 'response_time': 2.3}), AIMessage(content=[{'text': 'Let me search for more specific data.', 'type': 'text'}, {'id': 'toolu_019dPRXojLJoVNYFLzzSWw4w', 'input': {'query': 'UK GDP values by year 2019 2020 2021 2022 2023'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01Ac9vcTFneb5dvcEYXJyf1P', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 5890, 'output_tokens': 87}}, id='run-3504417f-c0b5-4908-82e2-89a18abb1b8e-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'UK GDP values by year 2019 2020 2021 2022 2023'}, 'id': 'toolu_019dPRXojLJoVNYFLzzSWw4w', 'type': 'tool_call'}], usage_metadata={'input_tokens': 5890, 'output_tokens': 87, 'total_tokens': 5977, 'input_token_details': {}}), ToolMessage(content='[{\"url\": \"https://www.macrotrends.net/global-metrics/countries/GBR/united-kingdom/gdp-gross-domestic-product\", \"content\": \"U.K. gdp for 2023 was $3,340.03B, a 8.13% increase from 2022. U.K. gdp for 2022 was $3,088.84B, a 1.68% decline from 2021. U.K. gdp for 2021 was $3,141.51B, a 16.45% increase from 2020. U.K. gdp for 2020 was $2,697.81B, a 5.39% decline from 2019.\"}, {\"url\": \"https://countryeconomy.com/gdp/uk?year=2023\", \"content\": \"Gross Domestic Product of United Kingdom grew 0.3% in 2023 compared to last year. This rate is 45 -tenths of one percent less than the figure of 4.8% published in 2022. The GDP figure in 2023 was $3,380,855 million, leaving United Kingdom placed 6th in the ranking of GDP of the 196 countries that we publish.\"}, {\"url\": \"https://www.statista.com/topics/3795/gdp-of-the-uk/\", \"content\": \"Monthly growth of gross domestic product in the United Kingdom from January 2019 to November 2023\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nContribution to gross domestic product growth in the United Kingdom in January 2023, by sector\\\\nGDP growth rate in the UK 1999-2021, by country\\\\nAnnual growth rates of gross domestic product in the United Kingdom from 1999 to 2021, by country\\\\nGDP growth rate in the UK 2021, by region\\\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\\\nGDP growth of Scotland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Scotland in 2021, by local (ITL 3) area\\\\nGDP growth of Wales 2021, by local area\\\\nAnnual growth rates of gross domestic product in Wales in 2021, by local (ITL 3) area\\\\nGDP growth of Northern Ireland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Northern Ireland in 2021, by local (ITL 3) area\\\\nGDP per capita\\\\nGDP per capita\\\\nGDP per capita in the UK 1955-2022\\\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\\\nAnnual GDP per capita growth in the UK 1956-2022\\\\nAnnual GDP per capita growth in the United Kingdom from 1956 to 2022\\\\nQuarterly GDP per capita in the UK 2019-2023\\\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nQuarterly GDP per capita growth in the UK 2019-2023\\\\nQuarterly GDP per capita growth in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nGDP per capita of the UK 1999-2021, by country\\\\nGross domestic product per capita of the United Kingdom from 1999 to 2021, by country (in GBP)\\\\nGDP per capita of the UK 2021, by region\\\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\\\nGlobal Comparisons\\\\nGlobal Comparisons\\\\nCountries with the largest gross domestic product (GDP) 2022\\\\n Monthly GDP of the UK 2019-2023\\\\nMonthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100)\\\\nGVA of the UK 2022, by sector\\\\nGross value added of the United Kingdom in 2022, by industry sector (in million GBP)\\\\nGDP of the UK 2021, by country\\\\nGross domestic product of the United Kingdom in 2021, by country (in million GBP)\\\\nGDP of the UK 2021, by region\\\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\\\nGDP of Scotland 2021, by local area\\\\nGross domestic product of Scotland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Wales 2021, by local area\\\\nGross domestic product of Wales in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Northern Ireland 2021, by local area\\\\nGross domestic product of Northern Ireland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP growth\\\\nGDP growth\\\\nGDP growth forecast for the UK 2000-2028\\\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\\\nAnnual GDP growth in the UK 1949-2022\\\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\\\nQuarterly GDP growth of the UK 2019-2023\\\\nQuarterly growth of gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023\\\\nMonthly GDP growth of the UK 2019-2023\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nUK GDP - Statistics & Facts\\\\nUK economy expected to shrink in 2023\\\\nCharacteristics of UK GDP\\\\nKey insights\\\\nDetailed statistics\\\\nGDP of the UK 1948-2022\\\\nDetailed statistics\\\\nAnnual GDP growth in the UK 1949-2022\\\\nDetailed statistics\\\\nGDP per capita in the UK 1955-2022\\\\nEditor’s Picks\\\\nCurrent statistics on this topic\\\\nCurrent statistics on this topic\\\\nKey Economic Indicators\\\\nMonthly GDP growth of the UK 2019-2023\\\\nKey Economic Indicators\\\\nMonthly GDP of the UK 2019-2023\\\\nKey Economic Indicators\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nRelated topics\\\\nRecommended\\\\nRecommended statistics\\\\nGDP\\\\nGDP\\\\nGDP of the UK 1948-2022\\\\nGross domestic product of the United Kingdom from 1948 to 2022 (in million GBP)\\\\nQuarterly GDP of the UK 2019-2023\\\\nQuarterly gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in million GBP)\\\\n The 20 countries with the largest gross domestic product (GDP) in 2022 (in billion U.S. dollars)\\\\nGDP of European countries in 2022\\\\nGross domestic product at current market prices of selected European countries in 2022 (in million euros)\\\\nReal GDP growth rates in Europe 2023\\\\nAnnual real gross domestic product (GDP) growth rate in European countries in 2023\\\\nGross domestic product (GDP) of Europe\\'s largest economies 1980-2028\\\\nGross domestic product (GDP) at current prices of Europe\\'s largest economies from 1980 to 2028 (in billion U.S dollars)\\\\nUnited Kingdom\\'s share of global gross domestic product (GDP) 2028\\\\nUnited Kingdom (UK): Share of global gross domestic product (GDP) adjusted for Purchasing Power Parity (PPP) from 2018 to 2028\\\\nRelated topics\\\\nRecommended\\\\nReport on the topic\\\\nKey figures\\\\nThe most important key figures provide you with a compact summary of the topic of \\\\\"UK GDP\\\\\" and take you straight to the corresponding statistics.\\\\n Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\xa0Mio. facts.\\\\n\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp/compendium/unitedkingdomnationalaccountsthebluebook/2024/nationalaccountsataglance\", \"content\": \"Real gross domestic product (GDP) is estimated to have increased by 0.3% in 2023, following a recovery from the impacts of the coronavirus (COVID-19) pandemic over the two previous years (Figure 1). Data for the UK are the Office for National Statistics (ONS) measure of real gross domestic product (GDP). Figure 9: Real GDP per head fell in 2023 when compared with 2022 in six G10 economies, including the UK Data for the UK are the Office for National Statistics (ONS) measure of real gross domestic product (GDP) per head. Download this chart Figure 9: Real GDP per head fell in 2023 when compared with 2022 in six G10 economies, including the UK\"}, {\"url\": \"https://data.worldbank.org/indicator/NY.GDP.MKTP.KD.ZG?locations=GB&most_recent_value_desc=false\", \"content\": \"GDP growth (annual %) - United Kingdom | Data Data GDP growth (annual %)United Kingdom Data Catalog Data Programs International Debt Statistics Other Books and Reports For Developers GDP growth (annual %) - United Kingdom ====================================== Similar values Highest values Lowest values GDP (constant 2015 US$) GDP (current US$) GDP (constant LCU) GDP: linked series (current LCU) GDP, PPP (constant 2021 international $) GDP (current LCU) GDP, PPP (current international $) GDP per capita growth (annual %) Country Most Recent Value All Countries and Economies Country Most Recent Value This site uses cookies to optimize functionality and give you the best possible experience. If you continue to navigate this website beyond this page, cookies will be placed on your browser.\"}]', name='tavily_search_results_json', id='84c571ca-27c6-4023-93a2-f0c2e8b6abb0', tool_call_id='toolu_019dPRXojLJoVNYFLzzSWw4w', artifact={'query': 'UK GDP values by year 2019 2020 2021 2022 2023', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'title': 'U.K. GDP 1960-2024 - Macrotrends', 'url': 'https://www.macrotrends.net/global-metrics/countries/GBR/united-kingdom/gdp-gross-domestic-product', 'content': 'U.K. gdp for 2023 was $3,340.03B, a 8.13% increase from 2022. U.K. gdp for 2022 was $3,088.84B, a 1.68% decline from 2021. U.K. gdp for 2021 was $3,141.51B, a 16.45% increase from 2020. U.K. gdp for 2020 was $2,697.81B, a 5.39% decline from 2019.', 'score': 0.9974491, 'raw_content': None}, {'title': 'United Kingdom (UK) GDP - Gross Domestic Product 2023', 'url': 'https://countryeconomy.com/gdp/uk?year=2023', 'content': 'Gross Domestic Product of United Kingdom grew 0.3% in 2023 compared to last year. This rate is 45 -tenths of one percent less than the figure of 4.8% published in 2022. The GDP figure in 2023 was $3,380,855 million, leaving United Kingdom placed 6th in the ranking of GDP of the 196 countries that we publish.', 'score': 0.9964064, 'raw_content': None}, {'title': 'UK GDP - Statistics & Facts | Statista', 'url': 'https://www.statista.com/topics/3795/gdp-of-the-uk/', 'content': 'Monthly growth of gross domestic product in the United Kingdom from January 2019 to November 2023\\nContribution to GDP growth in the UK 2023, by sector\\nContribution to gross domestic product growth in the United Kingdom in January 2023, by sector\\nGDP growth rate in the UK 1999-2021, by country\\nAnnual growth rates of gross domestic product in the United Kingdom from 1999 to 2021, by country\\nGDP growth rate in the UK 2021, by region\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\nGDP growth of Scotland 2021, by local area\\nAnnual growth rates of gross domestic product in Scotland in 2021, by local (ITL 3) area\\nGDP growth of Wales 2021, by local area\\nAnnual growth rates of gross domestic product in Wales in 2021, by local (ITL 3) area\\nGDP growth of Northern Ireland 2021, by local area\\nAnnual growth rates of gross domestic product in Northern Ireland in 2021, by local (ITL 3) area\\nGDP per capita\\nGDP per capita\\nGDP per capita in the UK 1955-2022\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\nAnnual GDP per capita growth in the UK 1956-2022\\nAnnual GDP per capita growth in the United Kingdom from 1956 to 2022\\nQuarterly GDP per capita in the UK 2019-2023\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\nQuarterly GDP per capita growth in the UK 2019-2023\\nQuarterly GDP per capita growth in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\nGDP per capita of the UK 1999-2021, by country\\nGross domestic product per capita of the United Kingdom from 1999 to 2021, by country (in GBP)\\nGDP per capita of the UK 2021, by region\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\nGlobal Comparisons\\nGlobal Comparisons\\nCountries with the largest gross domestic product (GDP) 2022\\n Monthly GDP of the UK 2019-2023\\nMonthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100)\\nGVA of the UK 2022, by sector\\nGross value added of the United Kingdom in 2022, by industry sector (in million GBP)\\nGDP of the UK 2021, by country\\nGross domestic product of the United Kingdom in 2021, by country (in million GBP)\\nGDP of the UK 2021, by region\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\nGDP of Scotland 2021, by local area\\nGross domestic product of Scotland in 2021, by local (ITL 3) area (in million GBP)\\nGDP of Wales 2021, by local area\\nGross domestic product of Wales in 2021, by local (ITL 3) area (in million GBP)\\nGDP of Northern Ireland 2021, by local area\\nGross domestic product of Northern Ireland in 2021, by local (ITL 3) area (in million GBP)\\nGDP growth\\nGDP growth\\nGDP growth forecast for the UK 2000-2028\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\nAnnual GDP growth in the UK 1949-2022\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\nQuarterly GDP growth of the UK 2019-2023\\nQuarterly growth of gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023\\nMonthly GDP growth of the UK 2019-2023\\n Transforming data into design:\\nStatista Content & Design\\nStrategy and business building for the data-driven economy:\\nUK GDP - Statistics & Facts\\nUK economy expected to shrink in 2023\\nCharacteristics of UK GDP\\nKey insights\\nDetailed statistics\\nGDP of the UK 1948-2022\\nDetailed statistics\\nAnnual GDP growth in the UK 1949-2022\\nDetailed statistics\\nGDP per capita in the UK 1955-2022\\nEditor’s Picks\\nCurrent statistics on this topic\\nCurrent statistics on this topic\\nKey Economic Indicators\\nMonthly GDP growth of the UK 2019-2023\\nKey Economic Indicators\\nMonthly GDP of the UK 2019-2023\\nKey Economic Indicators\\nContribution to GDP growth in the UK 2023, by sector\\nRelated topics\\nRecommended\\nRecommended statistics\\nGDP\\nGDP\\nGDP of the UK 1948-2022\\nGross domestic product of the United Kingdom from 1948 to 2022 (in million GBP)\\nQuarterly GDP of the UK 2019-2023\\nQuarterly gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in million GBP)\\n The 20 countries with the largest gross domestic product (GDP) in 2022 (in billion U.S. dollars)\\nGDP of European countries in 2022\\nGross domestic product at current market prices of selected European countries in 2022 (in million euros)\\nReal GDP growth rates in Europe 2023\\nAnnual real gross domestic product (GDP) growth rate in European countries in 2023\\nGross domestic product (GDP) of Europe\\'s largest economies 1980-2028\\nGross domestic product (GDP) at current prices of Europe\\'s largest economies from 1980 to 2028 (in billion U.S dollars)\\nUnited Kingdom\\'s share of global gross domestic product (GDP) 2028\\nUnited Kingdom (UK): Share of global gross domestic product (GDP) adjusted for Purchasing Power Parity (PPP) from 2018 to 2028\\nRelated topics\\nRecommended\\nReport on the topic\\nKey figures\\nThe most important key figures provide you with a compact summary of the topic of \"UK GDP\" and take you straight to the corresponding statistics.\\n Industry Overview\\nDigital & Trend reports\\nOverview and forecasts on trending topics\\nIndustry & Market reports\\nIndustry and market insights and forecasts\\nCompanies & Products reports\\nKey figures and rankings about companies and products\\nConsumer & Brand reports\\nConsumer and brand insights and preferences in various industries\\nPolitics & Society reports\\nDetailed information about political and social topics\\nCountry & Region reports\\nAll key figures about countries and regions\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\nInsights on consumer attitudes and behavior worldwide\\nBusiness information on 100m+ public and private companies\\nExplore Company Insights\\nDetailed information for 39,000+ online stores and marketplaces\\nDirectly accessible data for 170 industries from 150+ countries\\nand over 1\\xa0Mio. facts.\\n', 'score': 0.97943294, 'raw_content': None}, {'title': 'National accounts at a glance - Office for National Statistics', 'url': 'https://www.ons.gov.uk/economy/grossdomesticproductgdp/compendium/unitedkingdomnationalaccountsthebluebook/2024/nationalaccountsataglance', 'content': 'Real gross domestic product (GDP) is estimated to have increased by 0.3% in 2023, following a recovery from the impacts of the coronavirus (COVID-19) pandemic over the two previous years (Figure 1). Data for the UK are the Office for National Statistics (ONS) measure of real gross domestic product (GDP). Figure 9: Real GDP per head fell in 2023 when compared with 2022 in six G10 economies, including the UK Data for the UK are the Office for National Statistics (ONS) measure of real gross domestic product (GDP) per head. Download this chart Figure 9: Real GDP per head fell in 2023 when compared with 2022 in six G10 economies, including the UK', 'score': 0.975249, 'raw_content': None}, {'title': 'GDP growth (annual %) - United Kingdom | Data - World Bank Data', 'url': 'https://data.worldbank.org/indicator/NY.GDP.MKTP.KD.ZG?locations=GB&most_recent_value_desc=false', 'content': 'GDP growth (annual %) - United Kingdom | Data Data GDP growth (annual %)United Kingdom Data Catalog Data Programs International Debt Statistics Other Books and Reports For Developers GDP growth (annual %) - United Kingdom ====================================== Similar values Highest values Lowest values GDP (constant 2015 US$) GDP (current US$) GDP (constant LCU) GDP: linked series (current LCU) GDP, PPP (constant 2021 international $) GDP (current LCU) GDP, PPP (current international $) GDP per capita growth (annual %) Country Most Recent Value All Countries and Economies Country Most Recent Value This site uses cookies to optimize functionality and give you the best possible experience. If you continue to navigate this website beyond this page, cookies will be placed on your browser.', 'score': 0.83775276, 'raw_content': None}], 'response_time': 2.37}), HumanMessage(content='Based on the search results, I can provide the UK\\'s GDP values for the past 5 years (in billions of US dollars):\\n\\n2019: $2,851.54\\n2020: $2,697.81\\n2021: $3,141.51\\n2022: $3,088.84\\n2023: $3,340.03\\n\\nI\\'ll pass this data to my chart generator colleague to create a line chart. They should create a line chart with:\\n- Years 2019-2023 on the x-axis\\n- GDP values in billions USD on the y-axis\\n- Title: \"UK GDP 2019-2023\"\\n- Clear data points showing the values\\n\\nOver to you, chart generator colleague!', additional_kwargs={}, response_metadata={}, name='researcher', id='7e790b7a-7b06-4b45-a595-8736b53db844'), AIMessage(content=[{'text': \"I'll create a line chart with the specified GDP data and requirements using Python and matplotlib.\", 'type': 'text'}, {'id': 'toolu_017HmYWRMpnhPaw3SamZCQua', 'input': {'code': \"import matplotlib.pyplot as plt\\n\\nyears = [2019, 2020, 2021, 2022, 2023]\\ngdp = [2851.54, 2697.81, 3141.51, 3088.84, 3340.03]\\n\\nplt.figure(figsize=(10, 6))\\nplt.plot(years, gdp, marker='o', linewidth=2, markersize=8)\\n\\nplt.title('UK GDP 2019-2023', pad=15, size=14)\\nplt.xlabel('Year', labelpad=10)\\nplt.ylabel('GDP (Billions USD)', labelpad=10)\\n\\n# Add value labels above each point\\nfor i, value in enumerate(gdp):\\n plt.text(years[i], value + 30, f'${value}B', ha='center')\\n\\nplt.grid(True, linestyle='--', alpha=0.7)\\nplt.show()\"}, 'name': 'python_repl_tool', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01Y29F46KJQzTmefwQL6s9Dp', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 8744, 'output_tokens': 295}}, id='run-e0ee838e-1c18-46d9-bed7-459330376276-0', tool_calls=[{'name': 'python_repl_tool', 'args': {'code': \"import matplotlib.pyplot as plt\\n\\nyears = [2019, 2020, 2021, 2022, 2023]\\ngdp = [2851.54, 2697.81, 3141.51, 3088.84, 3340.03]\\n\\nplt.figure(figsize=(10, 6))\\nplt.plot(years, gdp, marker='o', linewidth=2, markersize=8)\\n\\nplt.title('UK GDP 2019-2023', pad=15, size=14)\\nplt.xlabel('Year', labelpad=10)\\nplt.ylabel('GDP (Billions USD)', labelpad=10)\\n\\n# Add value labels above each point\\nfor i, value in enumerate(gdp):\\n plt.text(years[i], value + 30, f'${value}B', ha='center')\\n\\nplt.grid(True, linestyle='--', alpha=0.7)\\nplt.show()\"}, 'id': 'toolu_017HmYWRMpnhPaw3SamZCQua', 'type': 'tool_call'}], usage_metadata={'input_tokens': 8744, 'output_tokens': 295, 'total_tokens': 9039, 'input_token_details': {}}), ToolMessage(content=\"Successfully executed:\\n```python\\nimport matplotlib.pyplot as plt\\n\\nyears = [2019, 2020, 2021, 2022, 2023]\\ngdp = [2851.54, 2697.81, 3141.51, 3088.84, 3340.03]\\n\\nplt.figure(figsize=(10, 6))\\nplt.plot(years, gdp, marker='o', linewidth=2, markersize=8)\\n\\nplt.title('UK GDP 2019-2023', pad=15, size=14)\\nplt.xlabel('Year', labelpad=10)\\nplt.ylabel('GDP (Billions USD)', labelpad=10)\\n\\n# Add value labels above each point\\nfor i, value in enumerate(gdp):\\n plt.text(years[i], value + 30, f'${value}B', ha='center')\\n\\nplt.grid(True, linestyle='--', alpha=0.7)\\nplt.show()\\n```\\nStdout: \\n\\nIf you have completed all tasks, respond with FINAL ANSWER.\", name='python_repl_tool', id='5421128d-9996-4dc5-b14c-77b862912d94', tool_call_id='toolu_017HmYWRMpnhPaw3SamZCQua'), HumanMessage(content='FINAL ANSWER: I have created a line chart showing the UK\\'s GDP from 2019 to 2023. The chart includes:\\n- A clear line with marked data points\\n- Years on the x-axis\\n- GDP values in billions USD on the y-axis\\n- Value labels above each data point\\n- A grid for better readability\\n- The title \"UK GDP 2019-2023\"\\n\\nThe chart clearly shows the GDP drop in 2020 due to the pandemic, followed by recovery and growth through 2023, with the most recent value reaching $3,340.03 billion.', additional_kwargs={}, response_metadata={}, name='chart_generator', id='4a649455-eed8-4b4f-a19f-c172140430c3')]}}\n", "----\n" ] } @@ -503,10 +369,10 @@ "events = graph.stream(\n", " {\n", " \"messages\": [\n", - " HumanMessage(\n", - " content=\"Fetch the UK's GDP over the past 5 years,\"\n", - " \" then draw a line graph of it.\"\n", - " \" Once you code it up, finish.\"\n", + " (\n", + " \"user\",\n", + " \"First, get the UK's GDP over the past 5 years, then make a line chart of it. \"\n", + " \"Once you make the chart, finish.\",\n", " )\n", " ],\n", " },\n", @@ -535,7 +401,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/docs/docs/tutorials/rag/langgraph_adaptive_rag_local.ipynb b/docs/docs/tutorials/rag/langgraph_adaptive_rag_local.ipynb index ff2dad931..44ba3e449 100644 --- a/docs/docs/tutorials/rag/langgraph_adaptive_rag_local.ipynb +++ b/docs/docs/tutorials/rag/langgraph_adaptive_rag_local.ipynb @@ -112,7 +112,7 @@ "metadata": {}, "outputs": [], "source": [ - "_set_env(\"LANGCHAIN_API_KEY\")\n", + "_set_env(\"LANGSMITH_API_KEY\")\n", "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", "os.environ[\"LANGCHAIN_PROJECT\"] = \"local-llama32-rag\"" ] diff --git a/docs/docs/tutorials/reflexion/reflexion.ipynb b/docs/docs/tutorials/reflexion/reflexion.ipynb index 102f48175..a9a1ea87d 100644 --- a/docs/docs/tutorials/reflexion/reflexion.ipynb +++ b/docs/docs/tutorials/reflexion/reflexion.ipynb @@ -46,8 +46,7 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install -U --quiet langgraph langchain_anthropic\n", - "%pip install -U --quiet tavily-python" + "%pip install -U --quiet langgraph langchain_anthropic tavily-python" ] }, { @@ -189,7 +188,7 @@ " self.runnable = runnable\n", " self.validator = validator\n", "\n", - " def respond(self, state: list):\n", + " def respond(self, state: dict):\n", " response = []\n", " for attempt in range(3):\n", " response = self.runnable.invoke(\n", @@ -622,12 +621,6 @@ "2. The 'reflections' can be paired with additional external feedback (such as validators), to further guide the actor.\n", "3. In the paper, 1 environment (AlfWorld) uses external memory. It does this by storing summaries of the reflections to an external store and using them in subsequent trials/invocations." ] - }, - { - "cell_type": "markdown", - "id": "39e44dd6", - "metadata": {}, - "source": [] } ], "metadata": { @@ -646,7 +639,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/docs/docs/tutorials/tot/img/tot.png b/docs/docs/tutorials/tot/img/tot.png new file mode 100644 index 000000000..519937d11 Binary files /dev/null and b/docs/docs/tutorials/tot/img/tot.png differ diff --git a/docs/docs/tutorials/tot/tot.ipynb b/docs/docs/tutorials/tot/tot.ipynb new file mode 100644 index 000000000..29fe435bc --- /dev/null +++ b/docs/docs/tutorials/tot/tot.ipynb @@ -0,0 +1,527 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tree of Thoughts\n", + "\n", + "[Tree of Thoughts](https://arxiv.org/abs/2305.10601) (ToT), by Yao, et. al, is a general LLM agent search algorithm that combines reflection/evaluation and simple search (in this case BFS, though you can apply DFS or other algorithms if you'd like).\n", + "\n", + "![LATS diagram](./img/tot.png)\n", + "\n", + "It has three main steps:\n", + "\n", + "1. Expand: generate 1 or more candidate solutions to the problem.\n", + "2. Score: measure the quality of the responses.\n", + "3. Prune: retain the top K best candidates\n", + "\n", + "Then return to \"Expand\" if no solution is found (or if the solution is of insufficient quality).\n", + "\n", + "\n", + "## Prerequisites\n", + "\n", + "We'll install the tutorial's dependent packages and set our API key for the LLM provider of choice." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install -U langgraph langchain-openai" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "\n", + "def _set_env(var: str):\n", + " if not os.environ.get(var):\n", + " os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "\n", + "\n", + "_set_env(\"OPENAI_API_KEY\")\n", + "# To visualize the algorithm\n", + "trace = True\n", + "if trace:\n", + " _set_env(\"LANGSMITH_API_KEY\")\n", + " os.environ[\"LANGSMITH_PROJECT\"] = \"ToT Tutorial\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Task Definition\n", + "\n", + "Our agent will try to play the \"Game of 24\". Given 4 numbers, it must generate a math equation that uses each of these numbers exactly one time to evaluate to a value of `24`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import operator\n", + "from typing import List, Literal, Union, NamedTuple, Optional\n", + "from pydantic import BaseModel, Field\n", + "\n", + "OperatorType = Literal[\"+\", \"-\", \"*\", \"/\"]\n", + "TokenType = Union[float, OperatorType]\n", + "\n", + "## We use these schemas to prompt the LLM to generate equations that evaluate to 24.\n", + "\n", + "\n", + "class Equation(BaseModel):\n", + " \"\"\"The formula combining the provided numbers to reach the target of 24.\"\"\"\n", + "\n", + " tokens: List[TokenType] = Field(\n", + " description=\"The stack of tokens and operators in reverse-polish notation. Example: [3, 4, '+', -1, '*'] would evaluate to (3 + 4) * -1 = -7.\",\n", + " )\n", + "\n", + " def compute(self) -> float:\n", + " op_funcs = {\n", + " \"+\": operator.add,\n", + " \"-\": operator.sub,\n", + " \"*\": operator.mul,\n", + " \"/\": operator.truediv,\n", + " }\n", + " stack = []\n", + " for token in self.tokens:\n", + " if isinstance(token, float):\n", + " stack.append(token)\n", + " else:\n", + " b, a = stack.pop(), stack.pop()\n", + " stack.append(op_funcs[token](a, b))\n", + "\n", + " return stack[0]\n", + "\n", + "\n", + "class GuessEquations(BaseModel):\n", + " \"\"\"Submit multiple equations as guesses.\"\"\"\n", + "\n", + " reasoning: str = Field(\n", + " description=\"The reasoning behind the submitted guesses. Explain how you arrived at these equations.\"\n", + " )\n", + "\n", + " equations: List[Equation] = Field(\n", + " description=\"The list of equations to submit as guesses.\"\n", + " )\n", + "\n", + "\n", + "## These objects will represent a single \"candidate\" (or scored candidate) within our agent's state.\n", + "# You can update the candidate object to match your own task.\n", + "\n", + "\n", + "class Candidate(NamedTuple):\n", + " candidate: Equation\n", + " score: Optional[float] = None\n", + " feedback: Optional[str] = None\n", + "\n", + " def __str__(self):\n", + " try:\n", + " computed = self.candidate.compute()\n", + " except Exception as e:\n", + " computed = f\"Invalid equation: {self.candidate.tokens}; Error: {repr(e)}\"\n", + "\n", + " return f\"Equation({self.candidate.tokens}) = {computed} (Reward: {self.score})\"\n", + "\n", + "\n", + "class ScoredCandidate(Candidate):\n", + " candidate: Equation\n", + " score: float\n", + " feedback: str" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Fetch data\n", + "\n", + "We'll use an example from the [Game of 24](https://github.com/princeton-nlp/tree-of-thought-llm) dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Example puzzles: ['1 1 4 6', '1 1 11 11', '1 1 3 8']\n" + ] + } + ], + "source": [ + "import requests\n", + "import csv\n", + "\n", + "csv_data = requests.get(\n", + " \"https://storage.googleapis.com/benchmarks-artifacts/game-of-24/24.csv\"\n", + ").content.decode(\"utf-8\")\n", + "# Get just the Puzzles column (column index 1)\n", + "puzzles = [row[1].strip() for row in csv.reader(csv_data.splitlines()[1:])]\n", + "\n", + "print(f\"Example puzzles: {puzzles[:3]}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Expander\n", + "\n", + "The \"tree of thoughts\" algorithm is relatively generic. The primary two task-specific components are the **expander** and the **scorer**.\n", + "The expander (the augmented LLM) tries to generate 1 or more solutions to the problem. On subsequent attempts, it is given a seed/candidate value from \n", + "the previous search.\n", + "\n", + "You can update this section to match your own task requirements. The expander can be arbitrarily complex. All that's required is that it accepts the problem and an optional previous attempt (or attempts) and returns a new result." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\n", + " \"system\",\n", + " \"You are playing the Game of 24. Using the provide numbers, create an equation that evaluates to 24.\\n\"\n", + " \"Submit exactly {k} guesses for this round.\",\n", + " ),\n", + " (\"user\", \"Solve the 24 game for these numbers: {problem}.{candidate}\"),\n", + " ],\n", + ").partial(candidate=\"\")\n", + "llm = ChatOpenAI(model=\"gpt-4o-mini\")\n", + "\n", + "bound_llm = llm.with_structured_output(GuessEquations)\n", + "solver = prompt | bound_llm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Scorer\n", + "\n", + "In this game, the scorer is easy. We need to assert two things:\n", + "\n", + "1. The LLM has generated a valid equation using each number exactly one time.\n", + "2. The equation evaluates to 24.\n", + "\n", + "You can update this function to match your own task requirements." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "def compute_score(problem: str, candidate: Candidate) -> ScoredCandidate:\n", + " numbers = list(map(int, problem.split()))\n", + " # Check that the candidate equation uses all 4 numbers exactly once\n", + " used_numbers = [\n", + " token for token in candidate.candidate.tokens if isinstance(token, float)\n", + " ]\n", + " if sorted(used_numbers) != sorted(numbers):\n", + " score = 0\n", + " feedback = \"The equation must use all 4 numbers exactly once.\"\n", + " return ScoredCandidate(\n", + " candidate=candidate.candidate, score=score, feedback=feedback\n", + " )\n", + " try:\n", + " result = candidate.candidate.compute()\n", + " score = 1 / (1 + abs(24 - result))\n", + " feedback = f\"Result: {result}\"\n", + " except Exception as e:\n", + " score = 0\n", + " feedback = f\"Invalid equation. Error: {repr(e)}\"\n", + " return ScoredCandidate(\n", + " candidate=candidate.candidate, score=score, feedback=feedback\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Graph\n", + "\n", + "Now it's time to create our graph." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "import operator\n", + "from typing import Optional, Dict, Any\n", + "from typing_extensions import Annotated, TypedDict\n", + "from langgraph.graph import StateGraph\n", + "\n", + "from langchain_core.runnables import RunnableConfig\n", + "from langgraph.constants import Send\n", + "from langgraph.checkpoint.memory import MemorySaver\n", + "\n", + "\n", + "def update_candidates(\n", + " existing: Optional[list] = None,\n", + " updates: Optional[Union[list, Literal[\"clear\"]]] = None,\n", + ") -> List[str]:\n", + " if existing is None:\n", + " existing = []\n", + " if updates is None:\n", + " return existing\n", + " if updates == \"clear\":\n", + " return []\n", + " # Concatenate the lists\n", + " return existing + updates\n", + "\n", + "\n", + "class ToTState(TypedDict):\n", + " problem: str\n", + " candidates: Annotated[List[Candidate], update_candidates]\n", + " scored_candidates: Annotated[List[ScoredCandidate], update_candidates]\n", + " depth: Annotated[int, operator.add]\n", + "\n", + "\n", + "class Configuration(TypedDict, total=False):\n", + " max_depth: int\n", + " threshold: float\n", + " k: int\n", + " beam_size: int\n", + "\n", + "\n", + "def _ensure_configurable(config: RunnableConfig) -> Configuration:\n", + " \"\"\"Get params that configure the search algorithm.\"\"\"\n", + " configurable = config.get(\"configurable\", {})\n", + " return {\n", + " **configurable,\n", + " \"max_depth\": configurable.get(\"max_depth\", 10),\n", + " \"threshold\": config.get(\"threshold\", 0.9),\n", + " \"k\": configurable.get(\"k\", 5),\n", + " \"beam_size\": configurable.get(\"beam_size\", 3),\n", + " }\n", + "\n", + "\n", + "class ExpansionState(ToTState):\n", + " seed: Optional[Candidate]\n", + "\n", + "\n", + "def expand(state: ExpansionState, *, config: RunnableConfig) -> Dict[str, List[str]]:\n", + " \"\"\"Generate the next state.\"\"\"\n", + " configurable = _ensure_configurable(config)\n", + " if not state.get(\"seed\"):\n", + " candidate_str = \"\"\n", + " else:\n", + " candidate_str = \"\\n\\n\" + str(state[\"seed\"])\n", + " try:\n", + " equation_submission = solver.invoke(\n", + " {\n", + " \"problem\": state[\"problem\"],\n", + " \"candidate\": candidate_str,\n", + " \"k\": configurable[\"k\"],\n", + " },\n", + " config=config,\n", + " )\n", + " except Exception:\n", + " return {\"candidates\": []}\n", + " new_candidates = [\n", + " Candidate(candidate=equation) for equation in equation_submission.equations\n", + " ]\n", + " return {\"candidates\": new_candidates}\n", + "\n", + "\n", + "def score(state: ToTState) -> Dict[str, List[float]]:\n", + " \"\"\"Evaluate the candidate generations.\"\"\"\n", + " candidates = state[\"candidates\"]\n", + " scored = []\n", + " for candidate in candidates:\n", + " scored.append(compute_score(state[\"problem\"], candidate))\n", + " return {\"scored_candidates\": scored, \"candidates\": \"clear\"}\n", + "\n", + "\n", + "def prune(\n", + " state: ToTState, *, config: RunnableConfig\n", + ") -> Dict[str, List[Dict[str, Any]]]:\n", + " scored_candidates = state[\"scored_candidates\"]\n", + " beam_size = _ensure_configurable(config)[\"beam_size\"]\n", + " organized = sorted(\n", + " scored_candidates, key=lambda candidate: candidate[1], reverse=True\n", + " )\n", + " pruned = organized[:beam_size]\n", + " return {\n", + " # Update the starting point for the next iteration\n", + " \"candidates\": pruned,\n", + " # Clear the old memory\n", + " \"scored_candidates\": \"clear\",\n", + " # Increment the depth by 1\n", + " \"depth\": 1,\n", + " }\n", + "\n", + "\n", + "def should_terminate(\n", + " state: ToTState, config: RunnableConfig\n", + ") -> Union[Literal[\"__end__\"], Send]:\n", + " configurable = _ensure_configurable(config)\n", + " solved = state[\"candidates\"][0].score >= configurable[\"threshold\"]\n", + " if solved or state[\"depth\"] >= configurable[\"max_depth\"]:\n", + " return \"__end__\"\n", + " return [\n", + " Send(\"expand\", {**state, \"somevalseed\": candidate})\n", + " for candidate in state[\"candidates\"]\n", + " ]\n", + "\n", + "\n", + "# Create the graph\n", + "builder = StateGraph(state_schema=ToTState, config_schema=Configuration)\n", + "\n", + "# Add nodes\n", + "builder.add_node(expand)\n", + "builder.add_node(score)\n", + "builder.add_node(prune)\n", + "\n", + "# Add edges\n", + "builder.add_edge(\"expand\", \"score\")\n", + "builder.add_edge(\"score\", \"prune\")\n", + "builder.add_conditional_edges(\"prune\", should_terminate, path_map=[\"expand\", \"__end__\"])\n", + "\n", + "# Set entry point\n", + "builder.add_edge(\"__start__\", \"expand\")\n", + "\n", + "# Compile the graph\n", + "graph = builder.compile(checkpointer=MemorySaver())" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Run\n", + "\n", + "Now let's try it on one of the puzzles!" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'expand': {'candidates': [Candidate(candidate=Equation(tokens=[12.0, 5.0, '/', 7.0, '*']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[12.0, 1.0, '+', 5.0, '*']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[12.0, 7.0, '*', 1.0, '/']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[5.0, 7.0, '*', 1.0, '*']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[7.0, 5.0, '*', 12.0, '/']), score=None, feedback=None)]}}\n", + "{'score': {'candidates': 'clear', 'scored_candidates': [ScoredCandidate(candidate=Equation(tokens=[12.0, 5.0, '/', 7.0, '*']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[12.0, 1.0, '+', 5.0, '*']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[12.0, 7.0, '*', 1.0, '/']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[5.0, 7.0, '*', 1.0, '*']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[7.0, 5.0, '*', 12.0, '/']), score=0, feedback='The equation must use all 4 numbers exactly once.')]}}\n", + "{'prune': {'candidates': [ScoredCandidate(candidate=Equation(tokens=[12.0, 5.0, '/', 7.0, '*']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[12.0, 1.0, '+', 5.0, '*']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[12.0, 7.0, '*', 1.0, '/']), score=0, feedback='The equation must use all 4 numbers exactly once.')], 'scored_candidates': 'clear', 'depth': 1}}\n", + "{'expand': {'candidates': [Candidate(candidate=Equation(tokens=[12.0, 5.0, '-', 1.0, '*']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[1.0, 7.0, '*', 5.0, '+']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[12.0, 1.0, '+', 5.0, '*']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[7.0, 5.0, '*', 1.0, '-']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[12.0, 1.0, '*', 5.0, '-']), score=None, feedback=None)]}}\n", + "{'expand': {'candidates': []}}\n", + "{'expand': {'candidates': [Candidate(candidate=Equation(tokens=[5.0, 7.0, '*', 12.0, '-']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[1.0, 5.0, 7.0, '*', 12.0, '-', '+']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[12.0, 1.0, '*', 5.0, 7.0, '/']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[12.0, 5.0, '*', 7.0, '/', 1.0, '-']), score=None, feedback=None), Candidate(candidate=Equation(tokens=[5.0, 7.0, '*', 1.0, '-', 12.0, '+']), score=None, feedback=None)]}}\n", + "{'score': {'candidates': 'clear', 'scored_candidates': [ScoredCandidate(candidate=Equation(tokens=[12.0, 5.0, '/', 7.0, '*']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[12.0, 1.0, '+', 5.0, '*']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[12.0, 7.0, '*', 1.0, '/']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[5.0, 7.0, '*', 12.0, '-']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[1.0, 5.0, 7.0, '*', 12.0, '-', '+']), score=1.0, feedback='Result: 24.0'), ScoredCandidate(candidate=Equation(tokens=[12.0, 1.0, '*', 5.0, 7.0, '/']), score=0.07692307692307693, feedback='Result: 12.0'), ScoredCandidate(candidate=Equation(tokens=[12.0, 5.0, '*', 7.0, '/', 1.0, '-']), score=0.05737704918032786, feedback='Result: 7.571428571428571'), ScoredCandidate(candidate=Equation(tokens=[5.0, 7.0, '*', 1.0, '-', 12.0, '+']), score=0.043478260869565216, feedback='Result: 46.0'), ScoredCandidate(candidate=Equation(tokens=[12.0, 5.0, '-', 1.0, '*']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[1.0, 7.0, '*', 5.0, '+']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[12.0, 1.0, '+', 5.0, '*']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[7.0, 5.0, '*', 1.0, '-']), score=0, feedback='The equation must use all 4 numbers exactly once.'), ScoredCandidate(candidate=Equation(tokens=[12.0, 1.0, '*', 5.0, '-']), score=0, feedback='The equation must use all 4 numbers exactly once.')]}}\n", + "{'prune': {'candidates': [ScoredCandidate(candidate=Equation(tokens=[1.0, 5.0, 7.0, '*', 12.0, '-', '+']), score=1.0, feedback='Result: 24.0'), ScoredCandidate(candidate=Equation(tokens=[12.0, 1.0, '*', 5.0, 7.0, '/']), score=0.07692307692307693, feedback='Result: 12.0'), ScoredCandidate(candidate=Equation(tokens=[12.0, 5.0, '*', 7.0, '/', 1.0, '-']), score=0.05737704918032786, feedback='Result: 7.571428571428571')], 'scored_candidates': 'clear', 'depth': 1}}\n" + ] + } + ], + "source": [ + "config = {\n", + " \"configurable\": {\n", + " \"thread_id\": \"test_1\",\n", + " \"depth\": 10,\n", + " }\n", + "}\n", + "for step in graph.stream({\"problem\": puzzles[42]}, config):\n", + " print(step)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found a winning solution in 2 steps: [Equation(tokens=[1.0, 5.0, 7.0, '*', 12.0, '-', '+']), 1.0, 'Result: 24.0']\n" + ] + } + ], + "source": [ + "final_state = graph.get_state(config)\n", + "winning_solution = final_state.values[\"candidates\"][0]\n", + "search_depth = final_state.values[\"depth\"]\n", + "if winning_solution[1] == 1:\n", + " print(f\"Found a winning solution in {search_depth} steps: {winning_solution}\")\n", + "else:\n", + " print(\n", + " f\"Failed to find a winning solution in {search_depth} steps. Best guess: {winning_solution}\"\n", + " )" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 539cf5ea7..648f8618e 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -22,10 +22,13 @@ theme: - navigation.footer - navigation.indexes - navigation.instant + - navigation.sections - navigation.instant.prefetch - navigation.instant.progress + - navigation.path - navigation.prune - navigation.tabs + - navigation.tabs.sticky - navigation.top - navigation.tracking - search.highlight @@ -52,6 +55,13 @@ plugins: - search: separator: '[\s\u200b\-_,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])' - autorefs + - redirects: + redirect_maps: + 'cloud/index.md': 'concepts/index.md#langgraph-platform' + 'cloud/how-tos/index.md': 'how-tos/index.md#langgraph-platform' + 'cloud/concepts/api.md': 'concepts/langgraph_server.md' + 'cloud/concepts/cloud.md': 'concepts/langgraph_cloud.md' + 'cloud/faq/studio.md': 'concepts/langgraph_studio.md#studio-faqs' - mkdocstrings: handlers: python: @@ -78,192 +88,260 @@ plugins: filters: - "!^_" nav: - - "index.md" + - Home: index.md - Tutorials: - - "tutorials/index.md" - - Quick Start: tutorials/introduction.ipynb + - tutorials/index.md + - Quick Start: + - Quick Start: tutorials#quick-start + - tutorials/introduction.ipynb + - cloud/quick_start.md - Chatbots: - - Customer Support: tutorials/customer-support/customer-support.ipynb - - Prompt Generation from User Requirements: tutorials/chatbots/information-gather-prompting.ipynb - - Code Assistant: tutorials/code_assistant/langgraph_code_assistant.ipynb + - Chatbots: tutorials#chatbots + - tutorials/customer-support/customer-support.ipynb + - tutorials/chatbots/information-gather-prompting.ipynb + - tutorials/code_assistant/langgraph_code_assistant.ipynb - RAG: - - Adaptive RAG: tutorials/rag/langgraph_adaptive_rag.ipynb - - Adaptive RAG using local LLMs: tutorials/rag/langgraph_adaptive_rag_local.ipynb - - Agentic RAG: tutorials/rag/langgraph_agentic_rag.ipynb - - Corrective RAG (CRAG): tutorials/rag/langgraph_crag.ipynb - - Corrective RAG (CRAG) using local LLMs: tutorials/rag/langgraph_crag_local.ipynb - - Self-RAG: tutorials/rag/langgraph_self_rag.ipynb - - Self-RAG using local LLMs: tutorials/rag/langgraph_self_rag_local.ipynb - - SQL Agent: tutorials/sql-agent.ipynb + - RAG: tutorials#rag + - tutorials/rag/langgraph_adaptive_rag.ipynb + - tutorials/rag/langgraph_adaptive_rag_local.ipynb + - tutorials/rag/langgraph_agentic_rag.ipynb + - tutorials/rag/langgraph_crag.ipynb + - tutorials/rag/langgraph_crag_local.ipynb + - tutorials/rag/langgraph_self_rag.ipynb + - tutorials/rag/langgraph_self_rag_local.ipynb + - tutorials/sql-agent.ipynb - Agent Architectures: + - Agent Architectures: tutorials#agent-architectures - Multi-Agent Systems: - - Collaboration: tutorials/multi_agent/multi-agent-collaboration.ipynb - - Supervision: tutorials/multi_agent/agent_supervisor.ipynb - - Hierarchical Teams: tutorials/multi_agent/hierarchical_agent_teams.ipynb + - Multi-Agent Systems: tutorials#multi-agent-systems + - tutorials/multi_agent/multi-agent-collaboration.ipynb + - tutorials/multi_agent/agent_supervisor.ipynb + - tutorials/multi_agent/hierarchical_agent_teams.ipynb - Planning Agents: - - Plan-and-Execute: tutorials/plan-and-execute/plan-and-execute.ipynb - - Reasoning without Observation: tutorials/rewoo/rewoo.ipynb - - LLMCompiler: tutorials/llm-compiler/LLMCompiler.ipynb + - Planning Agents: tutorials#planning-agents + - tutorials/plan-and-execute/plan-and-execute.ipynb + - tutorials/rewoo/rewoo.ipynb + - tutorials/llm-compiler/LLMCompiler.ipynb - Reflection & Critique: - - Basic Reflection: tutorials/reflection/reflection.ipynb - - Reflexion: tutorials/reflexion/reflexion.ipynb - - Language Agent Tree Search: tutorials/lats/lats.ipynb - - Self-Discover Agent: tutorials/self-discover/self-discover.ipynb + - Reflection & Critique: tutorials#reflection-critique + - tutorials/reflection/reflection.ipynb + - tutorials/reflexion/reflexion.ipynb + - tutorials/tot/tot.ipynb + - tutorials/lats/lats.ipynb + - tutorials/self-discover/self-discover.ipynb - Evaluation & Analysis: - - Chatbot Evaluation via Simulation: - - Agent-based: tutorials/chatbot-simulation-evaluation/agent-simulation-evaluation.ipynb - - In LangSmith: tutorials/chatbot-simulation-evaluation/langsmith-agent-simulation-evaluation.ipynb + - Evaluation & Analysis: tutorials#evaluation + - tutorials/chatbot-simulation-evaluation/agent-simulation-evaluation.ipynb + - tutorials/chatbot-simulation-evaluation/langsmith-agent-simulation-evaluation.ipynb - Experimental: - - Web Research (STORM): tutorials/storm/storm.ipynb - - TNT-LLM: tutorials/tnt-llm/tnt-llm.ipynb - - Web Navigation: tutorials/web-navigation/web_voyager.ipynb - - Competitive Programming: tutorials/usaco/usaco.ipynb - - Extract structured output: tutorials/extraction/retries.ipynb + - Experimental: tutorials#experimental + - tutorials/storm/storm.ipynb + - tutorials/tnt-llm/tnt-llm.ipynb + - tutorials/web-navigation/web_voyager.ipynb + - tutorials/usaco/usaco.ipynb + - tutorials/extraction/retries.ipynb - - "How-to Guides": - - "how-tos/index.md" - - Controllability: - - Create branches for parallel execution: how-tos/branching.ipynb - - Create map-reduce branches for parallel execution: how-tos/map-reduce.ipynb - - Control graph recursion limit: how-tos/recursion-limit.ipynb - - Persistence: - - Add thread-level persistence: how-tos/persistence.ipynb - - Add cross-thread persistence: how-tos/cross-thread-persistence.ipynb - - Use Postgres checkpointer for persistence: how-tos/persistence_postgres.ipynb - - Create custom checkpointer using MongoDB: how-tos/persistence_mongodb.ipynb - - Create custom checkpointer using Redis: how-tos/persistence_redis.ipynb - - Memory: - - Manage conversation history: how-tos/memory/manage-conversation-history.ipynb - - Delete messages: how-tos/memory/delete-messages.ipynb - - Add summary of the conversation history: how-tos/memory/add-summary-conversation-history.ipynb - - Human-in-the-loop: - - Add breakpoints: how-tos/human_in_the_loop/breakpoints.ipynb - - Add dynamic breakpoints: how-tos/human_in_the_loop/dynamic_breakpoints.ipynb - - Wait for user input: how-tos/human_in_the_loop/wait-user-input.ipynb - - View and update past graph state: how-tos/human_in_the_loop/time-travel.ipynb - - Edit graph state: how-tos/human_in_the_loop/edit-graph-state.ipynb - - Review tool calls: how-tos/human_in_the_loop/review-tool-calls.ipynb - - Streaming: - - Stream full state: how-tos/stream-values.ipynb - - Stream state updates: how-tos/stream-updates.ipynb - - Stream LLM tokens: how-tos/streaming-tokens.ipynb - - Stream LLM tokens without LangChain models: how-tos/streaming-tokens-without-langchain.ipynb - - Stream custom data: how-tos/streaming-content.ipynb - - Configure multiple streaming modes: how-tos/stream-multiple.ipynb - - Stream events from within tools: how-tos/streaming-events-from-within-tools.ipynb - - Stream events from within tools without LangChain models: how-tos/streaming-events-from-within-tools-without-langchain.ipynb - - Stream events from the final node: how-tos/streaming-from-final-node.ipynb - - Stream from subgraphs: how-tos/streaming-subgraphs.ipynb - - Disable streaming for models that don't support it: how-tos/disable-streaming.ipynb - - Tool calling: - - Call tools using ToolNode: how-tos/tool-calling.ipynb - - Handle tool calling errors: how-tos/tool-calling-errors.ipynb - - Pass runtime values to tools: how-tos/pass-run-time-values-to-tools.ipynb - - Pass config to tools: how-tos/pass-config-to-tools.ipynb - - Handle many tools: how-tos/many-tools.ipynb - - Subgraphs: - - Create subgraphs: how-tos/subgraph.ipynb - - Manage state in subgraphs: how-tos/subgraphs-manage-state.ipynb - - Transform inputs and outputs of a subgraph: how-tos/subgraph-transform-state.ipynb - - State Management: - - Use Pydantic model as state: how-tos/state-model.ipynb - - Have a separate input and output schema: how-tos/input_output_schema.ipynb - - Pass private state between nodes inside the graph: how-tos/pass_private_state.ipynb - - Other: - - Run graph asynchronously: how-tos/async.ipynb - - Visualize your graph: how-tos/visualization.ipynb - - Add runtime configuration: how-tos/configuration.ipynb - - Add node retries: how-tos/node-retries.ipynb - - Return structured output from a ReAct agent: how-tos/react-agent-structured-output.ipynb - - Pass custom LangSmith run ID for graph runs: how-tos/run-id-langsmith.ipynb - - Return state before hitting recursion limit: how-tos/return-when-recursion-limit-hits.ipynb - - Prebuilt ReAct Agent: - - Create a ReAct agent: how-tos/create-react-agent.ipynb - - Add memory to a ReAct agent: how-tos/create-react-agent-memory.ipynb - - Add a system prompt to a ReAct agent: how-tos/create-react-agent-system-prompt.ipynb - - Add Human-in-the-loop to a ReAct agent: how-tos/create-react-agent-hitl.ipynb - - Create prebuilt ReAct agent from scratch: how-tos/react-agent-from-scratch.ipynb - - "Conceptual Guides": - - Why LangGraph?: concepts/high_level.md - - LangGraph Glossary: concepts/low_level.md - - Common Agentic Patterns: concepts/agentic_concepts.md - - Human-in-the-Loop: concepts/human_in_the_loop.md - - Memory: concepts/memory.md - - Multi-Agent Systems: concepts/multi_agent.md - - Persistence: concepts/persistence.md - - Streaming: concepts/streaming.md - - FAQ: concepts/faq.md - - Reference: - - Graphs: reference/graphs.md - - Checkpointing: reference/checkpoints.md - - Storage: reference/store.md - - Prebuilt Components: reference/prebuilt.md - - Channels: reference/channels.md - - Errors: reference/errors.md - - Types: reference/types.md - - Constants: reference/constants.md - - "Cloud (beta)": - - "cloud/index.md" - - Tutorials: - - Quick Start: "cloud/quick_start.md" - - How-to Guides: - - "cloud/how-tos/index.md" - - Setup: - - Setup App: "cloud/deployment/setup.md" - - Setup App (pyproject.toml): "cloud/deployment/setup_pyproject.md" - - Setup App (JavaScript): "cloud/deployment/setup_javascript.md" - - Rebuild Graph at Runtime: "cloud/deployment/graph_rebuild.md" - - Customize Dockerfile: "cloud/deployment/custom_docker.md" - - Test App Locally: "cloud/deployment/test_locally.md" + - How-to Guides: + - how-tos/index.md + - LangGraph: + - LangGraph: how-tos#langgraph + - Controllability: + - Controllability: how-tos#controllability + - how-tos/branching.ipynb + - how-tos/map-reduce.ipynb + - how-tos/recursion-limit.ipynb + - Persistence: + - Persistence: how-tos#persistence + - how-tos/persistence.ipynb + - how-tos/subgraph-persistence.ipynb + - how-tos/cross-thread-persistence.ipynb + - how-tos/persistence_postgres.ipynb + - how-tos/persistence_mongodb.ipynb + - how-tos/persistence_redis.ipynb + - Memory: + - Memory: how-tos#memory + - how-tos/memory/manage-conversation-history.ipynb + - how-tos/memory/delete-messages.ipynb + - how-tos/memory/add-summary-conversation-history.ipynb + - Human-in-the-loop: + - Human-in-the-loop: how-tos#human-in-the-loop + - how-tos/human_in_the_loop/breakpoints.ipynb + - how-tos/human_in_the_loop/dynamic_breakpoints.ipynb + - how-tos/human_in_the_loop/edit-graph-state.ipynb + - how-tos/human_in_the_loop/wait-user-input.ipynb + - how-tos/human_in_the_loop/time-travel.ipynb + - how-tos/human_in_the_loop/review-tool-calls.ipynb + - Streaming: + - Streaming: how-tos#streaming + - how-tos/stream-values.ipynb + - how-tos/stream-updates.ipynb + - how-tos/streaming-tokens.ipynb + - how-tos/streaming-tokens-without-langchain.ipynb + - how-tos/streaming-content.ipynb + - how-tos/stream-multiple.ipynb + - how-tos/streaming-events-from-within-tools.ipynb + - how-tos/streaming-events-from-within-tools-without-langchain.ipynb + - how-tos/streaming-from-final-node.ipynb + - how-tos/streaming-subgraphs.ipynb + - how-tos/disable-streaming.ipynb + - Tool calling: + - Tool calling: how-tos#tool-calling + - how-tos/tool-calling.ipynb + - how-tos/tool-calling-errors.ipynb + - how-tos/pass-run-time-values-to-tools.ipynb + - how-tos/pass-config-to-tools.ipynb + - how-tos/many-tools.ipynb + - Subgraphs: + - Subgraphs: how-tos#subgraphs + - how-tos/subgraph.ipynb + - how-tos/subgraphs-manage-state.ipynb + - how-tos/subgraph-transform-state.ipynb + - State Management: + - State Management: how-tos#state-management + - how-tos/state-model.ipynb + - how-tos/input_output_schema.ipynb + - how-tos/pass_private_state.ipynb + - Other: + - Other: how-tos#other + - how-tos/async.ipynb + - how-tos/visualization.ipynb + - how-tos/configuration.ipynb + - how-tos/node-retries.ipynb + - how-tos/react-agent-structured-output.ipynb + - how-tos/run-id-langsmith.ipynb + - how-tos/return-when-recursion-limit-hits.ipynb + - Prebuilt ReAct Agent: + - Prebuilt ReAct Agent: how-tos#prebuilt-react-agent + - how-tos/create-react-agent.ipynb + - how-tos/create-react-agent-memory.ipynb + - how-tos/create-react-agent-system-prompt.ipynb + - how-tos/create-react-agent-hitl.ipynb + - how-tos/react-agent-from-scratch.ipynb + - LangGraph Platform: + - LangGraph Platform: how-tos#langgraph-platform + - Application Structure: + - Application Structure: how-tos#application-structure + - cloud/deployment/setup.md + - cloud/deployment/setup_pyproject.md + - cloud/deployment/setup_javascript.md + - cloud/deployment/custom_docker.md + - cloud/deployment/test_locally.md + - cloud/deployment/graph_rebuild.md - Deployment: - - Deploy to Cloud: "cloud/deployment/cloud.md" + - Deployment: how-tos#deployment + - cloud/deployment/cloud.md + - how-tos/deploy-self-hosted.md + - how-tos/use-remote-graph.md + - Assistants: + - Assistants: how-tos#assistants + - cloud/how-tos/configuration_cloud.md + - cloud/how-tos/assistant_versioning.md + - Threads: + - Threads: how-tos#threads + - cloud/how-tos/copy_threads.md + - cloud/how-tos/check_thread_status.md + - Runs: + - Runs: how-tos#runs + - cloud/how-tos/background_run.md + - cloud/how-tos/same-thread.md + - cloud/how-tos/cron_jobs.md + - cloud/how-tos/stateless_runs.md - Streaming: - - Stream Values: "cloud/how-tos/stream_values.md" - - Stream Updates: "cloud/how-tos/stream_updates.md" - - Stream Messages: "cloud/how-tos/stream_messages.md" - - Stream Events: "cloud/how-tos/stream_events.md" - - Stream Debug: "cloud/how-tos/stream_debug.md" - - Multiple Modes: "cloud/how-tos/stream_multiple.md" - - Double Texting: - - Interrupt: "cloud/how-tos/interrupt_concurrent.md" - - Rollback: "cloud/how-tos/rollback_concurrent.md" - - Reject: "cloud/how-tos/reject_concurrent.md" - - Enqueue: "cloud/how-tos/enqueue_concurrent.md" - - Human-in-the-Loop: - - Add Breakpoint: "cloud/how-tos/human_in_the_loop_breakpoint.md" - - Wait for User Input: "cloud/how-tos/human_in_the_loop_user_input.md" - - Edit Graph State: "cloud/how-tos/human_in_the_loop_edit_state.md" - - Replay and Branch from Prior States: "cloud/how-tos/human_in_the_loop_time_travel.md" - - Review Tool Calls: "cloud/how-tos/human_in_the_loop_review_tool_calls.md" + - Streaming: how-tos#streaming_1 + - cloud/how-tos/stream_values.md + - cloud/how-tos/stream_updates.md + - cloud/how-tos/stream_messages.md + - cloud/how-tos/stream_events.md + - cloud/how-tos/stream_debug.md + - cloud/how-tos/stream_multiple.md + - Human-in-the-loop: + - Human-in-the-loop: how-tos#human-in-the-loop_1 + - cloud/how-tos/human_in_the_loop_breakpoint.md + - cloud/how-tos/human_in_the_loop_user_input.md + - cloud/how-tos/human_in_the_loop_edit_state.md + - cloud/how-tos/human_in_the_loop_time_travel.md + - cloud/how-tos/human_in_the_loop_review_tool_calls.md + - Double-texting: + - Double-texting: how-tos#double-texting + - cloud/how-tos/interrupt_concurrent.md + - cloud/how-tos/rollback_concurrent.md + - cloud/how-tos/reject_concurrent.md + - cloud/how-tos/enqueue_concurrent.md + - Webhooks: + - cloud/how-tos/webhooks.md + - Cron Jobs: + - cloud/how-tos/cron_jobs.md - LangGraph Studio: - - Test Cloud Deployment: "cloud/how-tos/test_deployment.md" - - Test Local Deployment: "cloud/how-tos/test_local_deployment.md" - - Invoke graph in LangGraph Studio: "cloud/how-tos/invoke_studio.md" - - Interact with threads in LangGraph Studio: "cloud/how-tos/threads_studio.md" - - Different Types of Runs: - - Run an Agent in the Background: "cloud/how-tos/background_run.md" - - Run Multiple Agents in Same Thread: "cloud/how-tos/same-thread.md" - - Create Cron Jobs: "cloud/how-tos/cron_jobs.md" - - Create Stateless Runs: "cloud/how-tos/stateless_runs.md" - - Other: - - Configure Agents: "cloud/how-tos/configuration_cloud.md" - - Versioning Assistants: "cloud/how-tos/assistant_versioning.md" - - Convert LangGraph calls to LangGraph Cloud calls: "cloud/how-tos/langgraph_to_langgraph_cloud.ipynb" - - Integrate Webhooks: 'cloud/how-tos/webhooks.md' - - Copy Threads: 'cloud/how-tos/copy_threads.md' - - Check Status of Threads: "cloud/how-tos/check_thread_status.md" - - Conceptual Guides: - - API Concepts: "cloud/concepts/api.md" - - Cloud Concepts: "cloud/concepts/cloud.md" - - Reference: - - API: "cloud/reference/api/api_ref.md" - - SDK: - - Python: "cloud/reference/sdk/python_sdk_ref.md" - - JS/TS: "cloud/reference/sdk/js_ts_sdk_ref.md" - - CLI: "cloud/reference/cli.md" - - Environment Variables: "cloud/reference/env_var.md" - - FAQ: - - Studio: "cloud/faq/studio.md" + - LangGraph Studio: how-tos#langgraph-studio + - cloud/how-tos/test_deployment.md + - cloud/how-tos/test_local_deployment.md + - cloud/how-tos/invoke_studio.md + - cloud/how-tos/threads_studio.md + - Troubleshooting: + - Troubleshooting: how-tos#troubleshooting + - troubleshooting/errors/index.md + - troubleshooting/errors/GRAPH_RECURSION_LIMIT.md + - troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE.md + - troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE.md + - troubleshooting/errors/MULTIPLE_SUBGRAPHS.md + + - Conceptual Guides: + - concepts/index.md + - LangGraph: + - LangGraph: concepts#langgraph + - concepts/high_level.md + - concepts/low_level.md + - concepts/agentic_concepts.md + - concepts/multi_agent.md + - concepts/human_in_the_loop.md + - concepts/persistence.md + - concepts/memory.md + - concepts/streaming.md + - concepts/faq.md + - LangGraph Platform: + - LangGraph Platform: concepts#langgraph-platform + - High Level: + - High Level: concepts#high-level + - concepts/langgraph_platform.md + - concepts/deployment_options.md + - concepts/plans.md + - concepts/template_applications.md + - Components: + - Components: concepts#components + - concepts/langgraph_server.md + - concepts/langgraph_studio.md + - concepts/langgraph_cli.md + - concepts/sdk.md + - how-tos/use-remote-graph.md + - LangGraph Server: + - LangGraph Server: concepts#langgraph-server + - concepts/application_structure.md + - concepts/assistants.md + - concepts/double_texting.md + - Deployment Options: + - Deployment Options: concepts#deployment-options + - concepts/self_hosted.md + - concepts/langgraph_cloud.md + - concepts/bring_your_own_cloud.md + + - Reference: + - "reference/index.md" + - Library: + - Graphs: reference/graphs.md + - Checkpointing: reference/checkpoints.md + - Storage: reference/store.md + - Prebuilt Components: reference/prebuilt.md + - Channels: reference/channels.md + - Errors: reference/errors.md + - Types: reference/types.md + - Constants: reference/constants.md + - LangGraph Platform: + - Server API: "cloud/reference/api/api_ref.md" + - CLI: "cloud/reference/cli.md" + - SDK (Python): "cloud/reference/sdk/python_sdk_ref.md" + - SDK (JS/TS): "cloud/reference/sdk/js_ts_sdk_ref.md" + - RemoteGraph: reference/remote_graph.md + - Environment Variables: "cloud/reference/env_var.md" markdown_extensions: - abbr @@ -344,11 +422,20 @@ extra: note: >- Thanks for your feedback! Please help us improve this page by adding to the discussion below. validation: - omitted_files: warn + # https://www.mkdocs.org/user-guide/configuration/ + # We're `ignoring` nav.omitted_files because we are going to rely + # on files being properly links to from the index pages of: + # - tutorials + # - concepts + # - how-tos + # - reference + omitted_files: ignore absolute_links: warn unrecognized_links: warn # TODO: figure out how to enable 'warn' for this # it's only an issue for tutorials/storm/storm.ipynb # because it creates anchors in the generated report # and those anchors are not available in the actual doc - anchors: info \ No newline at end of file + anchors: info + # this is needed to handle headers with anchors for nav + not_found: info \ No newline at end of file diff --git a/docs/overrides/main.html b/docs/overrides/main.html index 7616fba79..e72b9d8bb 100644 --- a/docs/overrides/main.html +++ b/docs/overrides/main.html @@ -34,6 +34,17 @@ color: #1E88E5; } + .md-sidebar { + display: none; + } + + /* Show sidebar on mobile */ + @media screen and (max-width: 1220px) { + .md-sidebar--primary { + display: block; + } + } + .md-typeset a:hover { color: #1565C0; } diff --git a/libs/checkpoint-duckdb/Makefile b/libs/checkpoint-duckdb/Makefile new file mode 100644 index 000000000..ddf087ef5 --- /dev/null +++ b/libs/checkpoint-duckdb/Makefile @@ -0,0 +1,35 @@ +.PHONY: test test_watch lint format + +###################### +# TESTING AND COVERAGE +###################### + +test: + poetry run pytest tests + +test_watch: + poetry run ptw . + +###################### +# LINTING AND FORMATTING +###################### + +# Define a variable for Python and notebook files. +PYTHON_FILES=. +MYPY_CACHE=.mypy_cache +lint format: PYTHON_FILES=. +lint_diff format_diff: PYTHON_FILES=$(shell git diff --name-only --relative --diff-filter=d main . | grep -E '\.py$$|\.ipynb$$') +lint_package: PYTHON_FILES=langgraph +lint_tests: PYTHON_FILES=tests +lint_tests: MYPY_CACHE=.mypy_cache_test + +lint lint_diff lint_package lint_tests: + 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)" = "" ] || poetry run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE) + +format format_diff: + poetry run ruff format $(PYTHON_FILES) + poetry run ruff check --select I --fix $(PYTHON_FILES) diff --git a/libs/checkpoint-duckdb/README.md b/libs/checkpoint-duckdb/README.md new file mode 100644 index 000000000..36ce673d9 --- /dev/null +++ b/libs/checkpoint-duckdb/README.md @@ -0,0 +1,95 @@ +# LangGraph Checkpoint DuckDB + +Implementation of LangGraph CheckpointSaver that uses DuckDB. + +## Usage + +> [!IMPORTANT] +> When using DuckDB checkpointers for the first time, make sure to call `.setup()` method on them to create required tables. See example below. + +```python +from langgraph.checkpoint.duckdb import DuckDBSaver + +write_config = {"configurable": {"thread_id": "1", "checkpoint_ns": ""}} +read_config = {"configurable": {"thread_id": "1"}} + +with DuckDBSaver.from_conn_string(":memory:") as checkpointer: + # call .setup() the first time you're using the checkpointer + checkpointer.setup() + checkpoint = { + "v": 1, + "ts": "2024-07-31T20:14:19.804150+00:00", + "id": "1ef4f797-8335-6428-8001-8a1503f9b875", + "channel_values": { + "my_key": "meow", + "node": "node" + }, + "channel_versions": { + "__start__": 2, + "my_key": 3, + "start:node": 3, + "node": 3 + }, + "versions_seen": { + "__input__": {}, + "__start__": { + "__start__": 1 + }, + "node": { + "start:node": 2 + } + }, + "pending_sends": [], + } + + # store checkpoint + checkpointer.put(write_config, checkpoint, {}, {}) + + # load checkpoint + checkpointer.get(read_config) + + # list checkpoints + list(checkpointer.list(read_config)) +``` + +### Async + +```python +from langgraph.checkpoint.duckdb.aio import AsyncDuckDBSaver + +async with AsyncDuckDBSaver.from_conn_string(":memory:") as checkpointer: + checkpoint = { + "v": 1, + "ts": "2024-07-31T20:14:19.804150+00:00", + "id": "1ef4f797-8335-6428-8001-8a1503f9b875", + "channel_values": { + "my_key": "meow", + "node": "node" + }, + "channel_versions": { + "__start__": 2, + "my_key": 3, + "start:node": 3, + "node": 3 + }, + "versions_seen": { + "__input__": {}, + "__start__": { + "__start__": 1 + }, + "node": { + "start:node": 2 + } + }, + "pending_sends": [], + } + + # store checkpoint + await checkpointer.aput(write_config, checkpoint, {}, {}) + + # load checkpoint + await checkpointer.aget(read_config) + + # list checkpoints + [c async for c in checkpointer.alist(read_config)] +``` diff --git a/libs/checkpoint-duckdb/langgraph/checkpoint/duckdb/__init__.py b/libs/checkpoint-duckdb/langgraph/checkpoint/duckdb/__init__.py new file mode 100644 index 000000000..1002eebe8 --- /dev/null +++ b/libs/checkpoint-duckdb/langgraph/checkpoint/duckdb/__init__.py @@ -0,0 +1,356 @@ +import threading +from contextlib import contextmanager +from typing import Any, Iterator, Optional, Sequence + +from langchain_core.runnables import RunnableConfig + +import duckdb +from langgraph.checkpoint.base import ( + WRITES_IDX_MAP, + ChannelVersions, + Checkpoint, + CheckpointMetadata, + CheckpointTuple, + get_checkpoint_id, +) +from langgraph.checkpoint.duckdb.base import BaseDuckDBSaver +from langgraph.checkpoint.serde.base import SerializerProtocol + + +class DuckDBSaver(BaseDuckDBSaver): + lock: threading.Lock + + def __init__( + self, + conn: duckdb.DuckDBPyConnection, + serde: Optional[SerializerProtocol] = None, + ) -> None: + super().__init__(serde=serde) + + self.conn = conn + self.lock = threading.Lock() + + @classmethod + @contextmanager + def from_conn_string(cls, conn_string: str) -> Iterator["DuckDBSaver"]: + """Create a new DuckDBSaver instance from a connection string. + + Args: + conn_string (str): The DuckDB connection info string. + + Returns: + DuckDBSaver: A new DuckDBSaver instance. + """ + with duckdb.connect(conn_string) as conn: + yield DuckDBSaver(conn) + + def setup(self) -> None: + """Set up the checkpoint database asynchronously. + + This method creates the necessary tables in the DuckDB database if they don't + already exist and runs database migrations. It MUST be called directly by the user + the first time checkpointer is used. + """ + with self.lock, self.conn.cursor() as cur: + try: + row = cur.execute( + "SELECT v FROM checkpoint_migrations ORDER BY v DESC LIMIT 1" + ).fetchone() + if row is None: + version = -1 + else: + version = row[0] + except duckdb.CatalogException: + version = -1 + for v, migration in zip( + range(version + 1, len(self.MIGRATIONS)), + self.MIGRATIONS[version + 1 :], + ): + cur.execute(migration) + cur.execute("INSERT INTO checkpoint_migrations (v) VALUES (?)", [v]) + + def list( + self, + config: Optional[RunnableConfig], + *, + filter: Optional[dict[str, Any]] = None, + before: Optional[RunnableConfig] = None, + limit: Optional[int] = None, + ) -> Iterator[CheckpointTuple]: + """List checkpoints from the database. + + This method retrieves a list of checkpoint tuples from the DuckDB database based + on the provided config. The checkpoints are ordered by checkpoint ID in descending order (newest first). + + Args: + config (RunnableConfig): The config to use for listing the checkpoints. + filter (Optional[Dict[str, Any]]): Additional filtering criteria for metadata. Defaults to None. + before (Optional[RunnableConfig]): If provided, only checkpoints before the specified checkpoint ID are returned. Defaults to None. + limit (Optional[int]): The maximum number of checkpoints to return. Defaults to None. + + Yields: + Iterator[CheckpointTuple]: An iterator of checkpoint tuples. + + Examples: + >>> from langgraph.checkpoint.duckdb import DuckDBSaver + >>> with DuckDBSaver.from_conn_string(":memory:") as memory: + ... # Run a graph, then list the checkpoints + >>> config = {"configurable": {"thread_id": "1"}} + >>> checkpoints = list(memory.list(config, limit=2)) + >>> print(checkpoints) + [CheckpointTuple(...), CheckpointTuple(...)] + + >>> config = {"configurable": {"thread_id": "1"}} + >>> before = {"configurable": {"checkpoint_id": "1ef4f797-8335-6428-8001-8a1503f9b875"}} + >>> with DuckDBSaver.from_conn_string(":memory:") as memory: + ... # Run a graph, then list the checkpoints + >>> checkpoints = list(memory.list(config, before=before)) + >>> print(checkpoints) + [CheckpointTuple(...), ...] + """ + where, args = self._search_where(config, filter, before) + query = self.SELECT_SQL + where + " ORDER BY checkpoint_id DESC" + if limit: + query += f" LIMIT {limit}" + # if we change this to use .stream() we need to make sure to close the cursor + with self._cursor() as cur: + cur.execute(query, args) + for value in cur.fetchall(): + ( + thread_id, + checkpoint, + checkpoint_ns, + checkpoint_id, + parent_checkpoint_id, + metadata, + channel_values, + pending_writes, + pending_sends, + ) = value + yield CheckpointTuple( + { + "configurable": { + "thread_id": thread_id, + "checkpoint_ns": checkpoint_ns, + "checkpoint_id": checkpoint_id, + } + }, + self._load_checkpoint( + checkpoint, + channel_values, + pending_sends, + ), + self._load_metadata(metadata), + ( + { + "configurable": { + "thread_id": thread_id, + "checkpoint_ns": checkpoint_ns, + "checkpoint_id": parent_checkpoint_id, + } + } + if parent_checkpoint_id + else None + ), + self._load_writes(pending_writes), + ) + + def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]: + """Get a checkpoint tuple from the database. + + This method retrieves a checkpoint tuple from the DuckDB database based on the + provided config. If the config contains a "checkpoint_id" key, the checkpoint with + the matching thread ID and timestamp is retrieved. Otherwise, the latest checkpoint + for the given thread ID is retrieved. + + Args: + config (RunnableConfig): The config to use for retrieving the checkpoint. + + Returns: + Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found. + + Examples: + + Basic: + >>> config = {"configurable": {"thread_id": "1"}} + >>> checkpoint_tuple = memory.get_tuple(config) + >>> print(checkpoint_tuple) + CheckpointTuple(...) + + With timestamp: + + >>> config = { + ... "configurable": { + ... "thread_id": "1", + ... "checkpoint_ns": "", + ... "checkpoint_id": "1ef4f797-8335-6428-8001-8a1503f9b875", + ... } + ... } + >>> checkpoint_tuple = memory.get_tuple(config) + >>> print(checkpoint_tuple) + CheckpointTuple(...) + """ # noqa + thread_id = config["configurable"]["thread_id"] + checkpoint_id = get_checkpoint_id(config) + checkpoint_ns = config["configurable"].get("checkpoint_ns", "") + if checkpoint_id: + args: tuple[Any, ...] = (thread_id, checkpoint_ns, checkpoint_id) + where = "WHERE thread_id = ? AND checkpoint_ns = ? AND checkpoint_id = ?" + else: + args = (thread_id, checkpoint_ns) + where = "WHERE thread_id = ? AND checkpoint_ns = ? ORDER BY checkpoint_id DESC LIMIT 1" + + with self._cursor() as cur: + cur.execute( + self.SELECT_SQL + where, + args, + ) + + value = cur.fetchone() + if value: + ( + thread_id, + checkpoint, + checkpoint_ns, + checkpoint_id, + parent_checkpoint_id, + metadata, + channel_values, + pending_writes, + pending_sends, + ) = value + return CheckpointTuple( + { + "configurable": { + "thread_id": thread_id, + "checkpoint_ns": checkpoint_ns, + "checkpoint_id": checkpoint_id, + } + }, + self._load_checkpoint( + checkpoint, + channel_values, + pending_sends, + ), + self._load_metadata(metadata), + ( + { + "configurable": { + "thread_id": thread_id, + "checkpoint_ns": checkpoint_ns, + "checkpoint_id": parent_checkpoint_id, + } + } + if parent_checkpoint_id + else None + ), + self._load_writes(pending_writes), + ) + + def put( + self, + config: RunnableConfig, + checkpoint: Checkpoint, + metadata: CheckpointMetadata, + new_versions: ChannelVersions, + ) -> RunnableConfig: + """Save a checkpoint to the database. + + This method saves a checkpoint to the DuckDB database. The checkpoint is associated + with the provided config and its parent config (if any). + + Args: + config (RunnableConfig): The config to associate with the checkpoint. + checkpoint (Checkpoint): The checkpoint to save. + metadata (CheckpointMetadata): Additional metadata to save with the checkpoint. + new_versions (ChannelVersions): New channel versions as of this write. + + Returns: + RunnableConfig: Updated configuration after storing the checkpoint. + + Examples: + + >>> from langgraph.checkpoint.duckdb import DuckDBSaver + >>> with DuckDBSaver.from_conn_string(":memory:") as memory: + >>> config = {"configurable": {"thread_id": "1", "checkpoint_ns": ""}} + >>> checkpoint = {"ts": "2024-05-04T06:32:42.235444+00:00", "id": "1ef4f797-8335-6428-8001-8a1503f9b875", "channel_values": {"key": "value"}} + >>> saved_config = memory.put(config, checkpoint, {"source": "input", "step": 1, "writes": {"key": "value"}}, {}) + >>> print(saved_config) + {'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef4f797-8335-6428-8001-8a1503f9b875'}} + """ + configurable = config["configurable"].copy() + thread_id = configurable.pop("thread_id") + checkpoint_ns = configurable.pop("checkpoint_ns") + checkpoint_id = configurable.pop( + "checkpoint_id", configurable.pop("thread_ts", None) + ) + + copy = checkpoint.copy() + next_config = { + "configurable": { + "thread_id": thread_id, + "checkpoint_ns": checkpoint_ns, + "checkpoint_id": checkpoint["id"], + } + } + checkpoint_blobs = self._dump_blobs( + thread_id, + checkpoint_ns, + copy.pop("channel_values"), # type: ignore[misc] + new_versions, + ) + with self._cursor() as cur: + if checkpoint_blobs: + cur.executemany(self.UPSERT_CHECKPOINT_BLOBS_SQL, checkpoint_blobs) + cur.execute( + self.UPSERT_CHECKPOINTS_SQL, + ( + thread_id, + checkpoint_ns, + checkpoint["id"], + checkpoint_id, + self._dump_checkpoint(copy), + self._dump_metadata(metadata), + ), + ) + return next_config + + def put_writes( + self, + config: RunnableConfig, + writes: Sequence[tuple[str, Any]], + task_id: str, + ) -> None: + """Store intermediate writes linked to a checkpoint. + + This method saves intermediate writes associated with a checkpoint to the DuckDB database. + + Args: + config (RunnableConfig): Configuration of the related checkpoint. + writes (List[Tuple[str, Any]]): List of writes to store. + task_id (str): Identifier for the task creating the writes. + """ + query = ( + self.UPSERT_CHECKPOINT_WRITES_SQL + if all(w[0] in WRITES_IDX_MAP for w in writes) + else self.INSERT_CHECKPOINT_WRITES_SQL + ) + with self._cursor() as cur: + cur.executemany( + query, + self._dump_writes( + config["configurable"]["thread_id"], + config["configurable"]["checkpoint_ns"], + config["configurable"]["checkpoint_id"], + task_id, + writes, + ), + ) + + @contextmanager + def _cursor(self) -> Iterator[duckdb.DuckDBPyConnection]: + with self.lock, self.conn.cursor() as cur: + yield cur + + +__all__ = ["DuckDBSaver", "Conn"] diff --git a/libs/checkpoint-duckdb/langgraph/checkpoint/duckdb/aio.py b/libs/checkpoint-duckdb/langgraph/checkpoint/duckdb/aio.py new file mode 100644 index 000000000..aa52feb18 --- /dev/null +++ b/libs/checkpoint-duckdb/langgraph/checkpoint/duckdb/aio.py @@ -0,0 +1,431 @@ +import asyncio +from contextlib import asynccontextmanager +from typing import Any, AsyncIterator, Iterator, Optional, Sequence + +from langchain_core.runnables import RunnableConfig + +import duckdb +from langgraph.checkpoint.base import ( + WRITES_IDX_MAP, + ChannelVersions, + Checkpoint, + CheckpointMetadata, + CheckpointTuple, + get_checkpoint_id, +) +from langgraph.checkpoint.duckdb.base import BaseDuckDBSaver +from langgraph.checkpoint.serde.base import SerializerProtocol + + +class AsyncDuckDBSaver(BaseDuckDBSaver): + lock: asyncio.Lock + + def __init__( + self, + conn: duckdb.DuckDBPyConnection, + serde: Optional[SerializerProtocol] = None, + ) -> None: + super().__init__(serde=serde) + self.conn = conn + self.lock = asyncio.Lock() + self.loop = asyncio.get_running_loop() + + @classmethod + @asynccontextmanager + async def from_conn_string( + cls, + conn_string: str, + ) -> AsyncIterator["AsyncDuckDBSaver"]: + """Create a new AsyncDuckDBSaver instance from a connection string. + + Args: + conn_string (str): The DuckDB connection info string. + + Returns: + AsyncDuckDBSaver: A new AsyncDuckDBSaver instance. + """ + with duckdb.connect(conn_string) as conn: + yield AsyncDuckDBSaver(conn) + + async def setup(self) -> None: + """Set up the checkpoint database asynchronously. + + This method creates the necessary tables in the DuckDB database if they don't + already exist and runs database migrations. It MUST be called directly by the user + the first time checkpointer is used. + """ + async with self.lock: + with self.conn.cursor() as cur: + try: + await asyncio.to_thread( + cur.execute, + "SELECT v FROM checkpoint_migrations ORDER BY v DESC LIMIT 1", + ) + row = await asyncio.to_thread(cur.fetchone) + if row is None: + version = -1 + else: + version = row[0] + except duckdb.CatalogException: + version = -1 + for v, migration in zip( + range(version + 1, len(self.MIGRATIONS)), + self.MIGRATIONS[version + 1 :], + ): + await asyncio.to_thread(cur.execute, migration) + await asyncio.to_thread( + cur.execute, + "INSERT INTO checkpoint_migrations (v) VALUES (?)", + [v], + ) + + async def alist( + self, + config: Optional[RunnableConfig], + *, + filter: Optional[dict[str, Any]] = None, + before: Optional[RunnableConfig] = None, + limit: Optional[int] = None, + ) -> AsyncIterator[CheckpointTuple]: + """List checkpoints from the database asynchronously. + + This method retrieves a list of checkpoint tuples from the DuckDB database based + on the provided config. The checkpoints are ordered by checkpoint ID in descending order (newest first). + + Args: + config (Optional[RunnableConfig]): Base configuration for filtering checkpoints. + filter (Optional[Dict[str, Any]]): Additional filtering criteria for metadata. + before (Optional[RunnableConfig]): If provided, only checkpoints before the specified checkpoint ID are returned. Defaults to None. + limit (Optional[int]): Maximum number of checkpoints to return. + + Yields: + AsyncIterator[CheckpointTuple]: An asynchronous iterator of matching checkpoint tuples. + """ + where, args = self._search_where(config, filter, before) + query = self.SELECT_SQL + where + " ORDER BY checkpoint_id DESC" + if limit: + query += f" LIMIT {limit}" + # if we change this to use .stream() we need to make sure to close the cursor + async with self._cursor() as cur: + await asyncio.to_thread(cur.execute, query, args) + results = await asyncio.to_thread(cur.fetchall) + for value in results: + ( + thread_id, + checkpoint, + checkpoint_ns, + checkpoint_id, + parent_checkpoint_id, + metadata, + channel_values, + pending_writes, + pending_sends, + ) = value + yield CheckpointTuple( + { + "configurable": { + "thread_id": thread_id, + "checkpoint_ns": checkpoint_ns, + "checkpoint_id": checkpoint_id, + } + }, + await asyncio.to_thread( + self._load_checkpoint, + checkpoint, + channel_values, + pending_sends, + ), + self._load_metadata(metadata), + ( + { + "configurable": { + "thread_id": thread_id, + "checkpoint_ns": checkpoint_ns, + "checkpoint_id": parent_checkpoint_id, + } + } + if parent_checkpoint_id + else None + ), + await asyncio.to_thread(self._load_writes, pending_writes), + ) + + async def aget_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]: + """Get a checkpoint tuple from the database asynchronously. + + This method retrieves a checkpoint tuple from the DuckDBdatabase based on the + provided config. If the config contains a "checkpoint_id" key, the checkpoint with + the matching thread ID and "checkpoint_id" is retrieved. Otherwise, the latest checkpoint + for the given thread ID is retrieved. + + Args: + config (RunnableConfig): The config to use for retrieving the checkpoint. + + Returns: + Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found. + """ + thread_id = config["configurable"]["thread_id"] + checkpoint_id = get_checkpoint_id(config) + checkpoint_ns = config["configurable"].get("checkpoint_ns", "") + if checkpoint_id: + args: tuple[Any, ...] = (thread_id, checkpoint_ns, checkpoint_id) + where = "WHERE thread_id = ? AND checkpoint_ns = ? AND checkpoint_id = ?" + else: + args = (thread_id, checkpoint_ns) + where = "WHERE thread_id = ? AND checkpoint_ns = ? ORDER BY checkpoint_id DESC LIMIT 1" + + async with self._cursor() as cur: + await asyncio.to_thread( + cur.execute, + self.SELECT_SQL + where, + args, + ) + + value = await asyncio.to_thread(cur.fetchone) + if value: + ( + thread_id, + checkpoint, + checkpoint_ns, + checkpoint_id, + parent_checkpoint_id, + metadata, + channel_values, + pending_writes, + pending_sends, + ) = value + return CheckpointTuple( + { + "configurable": { + "thread_id": thread_id, + "checkpoint_ns": checkpoint_ns, + "checkpoint_id": checkpoint_id, + } + }, + await asyncio.to_thread( + self._load_checkpoint, + checkpoint, + channel_values, + pending_sends, + ), + self._load_metadata(metadata), + ( + { + "configurable": { + "thread_id": thread_id, + "checkpoint_ns": checkpoint_ns, + "checkpoint_id": parent_checkpoint_id, + } + } + if parent_checkpoint_id + else None + ), + await asyncio.to_thread(self._load_writes, pending_writes), + ) + + async def aput( + self, + config: RunnableConfig, + checkpoint: Checkpoint, + metadata: CheckpointMetadata, + new_versions: ChannelVersions, + ) -> RunnableConfig: + """Save a checkpoint to the database asynchronously. + + This method saves a checkpoint to the DuckDB database. The checkpoint is associated + with the provided config and its parent config (if any). + + Args: + config (RunnableConfig): The config to associate with the checkpoint. + checkpoint (Checkpoint): The checkpoint to save. + metadata (CheckpointMetadata): Additional metadata to save with the checkpoint. + new_versions (ChannelVersions): New channel versions as of this write. + + Returns: + RunnableConfig: Updated configuration after storing the checkpoint. + """ + configurable = config["configurable"].copy() + thread_id = configurable.pop("thread_id") + checkpoint_ns = configurable.pop("checkpoint_ns") + checkpoint_id = configurable.pop( + "checkpoint_id", configurable.pop("thread_ts", None) + ) + + copy = checkpoint.copy() + next_config = { + "configurable": { + "thread_id": thread_id, + "checkpoint_ns": checkpoint_ns, + "checkpoint_id": checkpoint["id"], + } + } + + checkpoint_blobs = await asyncio.to_thread( + self._dump_blobs, + thread_id, + checkpoint_ns, + copy.pop("channel_values"), # type: ignore[misc] + new_versions, + ) + async with self._cursor() as cur: + if checkpoint_blobs: + await asyncio.to_thread( + cur.executemany, self.UPSERT_CHECKPOINT_BLOBS_SQL, checkpoint_blobs + ) + await asyncio.to_thread( + cur.execute, + self.UPSERT_CHECKPOINTS_SQL, + ( + thread_id, + checkpoint_ns, + checkpoint["id"], + checkpoint_id, + self._dump_checkpoint(copy), + self._dump_metadata(metadata), + ), + ) + + return next_config + + async def aput_writes( + self, + config: RunnableConfig, + writes: Sequence[tuple[str, Any]], + task_id: str, + ) -> None: + """Store intermediate writes linked to a checkpoint asynchronously. + + This method saves intermediate writes associated with a checkpoint to the database. + + Args: + config (RunnableConfig): Configuration of the related checkpoint. + writes (Sequence[Tuple[str, Any]]): List of writes to store, each as (channel, value) pair. + task_id (str): Identifier for the task creating the writes. + """ + query = ( + self.UPSERT_CHECKPOINT_WRITES_SQL + if all(w[0] in WRITES_IDX_MAP for w in writes) + else self.INSERT_CHECKPOINT_WRITES_SQL + ) + params = await asyncio.to_thread( + self._dump_writes, + config["configurable"]["thread_id"], + config["configurable"]["checkpoint_ns"], + config["configurable"]["checkpoint_id"], + task_id, + writes, + ) + async with self._cursor() as cur: + await asyncio.to_thread(cur.executemany, query, params) + + @asynccontextmanager + async def _cursor(self) -> AsyncIterator[duckdb.DuckDBPyConnection]: + async with self.lock: + with self.conn.cursor() as cur: + yield cur + + def list( + self, + config: Optional[RunnableConfig], + *, + filter: Optional[dict[str, Any]] = None, + before: Optional[RunnableConfig] = None, + limit: Optional[int] = None, + ) -> Iterator[CheckpointTuple]: + """List checkpoints from the database. + + This method retrieves a list of checkpoint tuples from the DuckDB database based + on the provided config. The checkpoints are ordered by checkpoint ID in descending order (newest first). + + Args: + config (Optional[RunnableConfig]): Base configuration for filtering checkpoints. + filter (Optional[Dict[str, Any]]): Additional filtering criteria for metadata. + before (Optional[RunnableConfig]): If provided, only checkpoints before the specified checkpoint ID are returned. Defaults to None. + limit (Optional[int]): Maximum number of checkpoints to return. + + Yields: + Iterator[CheckpointTuple]: An iterator of matching checkpoint tuples. + """ + aiter_ = self.alist(config, filter=filter, before=before, limit=limit) + while True: + try: + yield asyncio.run_coroutine_threadsafe( + anext(aiter_), + self.loop, + ).result() + except StopAsyncIteration: + break + + def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]: + """Get a checkpoint tuple from the database. + + This method retrieves a checkpoint tuple from the DuckDB database based on the + provided config. If the config contains a "checkpoint_id" key, the checkpoint with + the matching thread ID and "checkpoint_id" is retrieved. Otherwise, the latest checkpoint + for the given thread ID is retrieved. + + Args: + config (RunnableConfig): The config to use for retrieving the checkpoint. + + Returns: + Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found. + """ + try: + # check if we are in the main thread, only bg threads can block + # we don't check in other methods to avoid the overhead + if asyncio.get_running_loop() is self.loop: + raise asyncio.InvalidStateError( + "Synchronous calls to AsyncDuckDBSaver are only allowed from a " + "different thread. From the main thread, use the async interface." + "For example, use `await checkpointer.aget_tuple(...)` or `await " + "graph.ainvoke(...)`." + ) + except RuntimeError: + pass + return asyncio.run_coroutine_threadsafe( + self.aget_tuple(config), self.loop + ).result() + + def put( + self, + config: RunnableConfig, + checkpoint: Checkpoint, + metadata: CheckpointMetadata, + new_versions: ChannelVersions, + ) -> RunnableConfig: + """Save a checkpoint to the database. + + This method saves a checkpoint to the DuckDB database. The checkpoint is associated + with the provided config and its parent config (if any). + + Args: + config (RunnableConfig): The config to associate with the checkpoint. + checkpoint (Checkpoint): The checkpoint to save. + metadata (CheckpointMetadata): Additional metadata to save with the checkpoint. + new_versions (ChannelVersions): New channel versions as of this write. + + Returns: + RunnableConfig: Updated configuration after storing the checkpoint. + """ + return asyncio.run_coroutine_threadsafe( + self.aput(config, checkpoint, metadata, new_versions), self.loop + ).result() + + def put_writes( + self, + config: RunnableConfig, + writes: Sequence[tuple[str, Any]], + task_id: str, + ) -> None: + """Store intermediate writes linked to a checkpoint. + + This method saves intermediate writes associated with a checkpoint to the database. + + Args: + config (RunnableConfig): Configuration of the related checkpoint. + writes (Sequence[Tuple[str, Any]]): List of writes to store, each as (channel, value) pair. + task_id (str): Identifier for the task creating the writes. + """ + return asyncio.run_coroutine_threadsafe( + self.aput_writes(config, writes, task_id), self.loop + ).result() diff --git a/libs/checkpoint-duckdb/langgraph/checkpoint/duckdb/base.py b/libs/checkpoint-duckdb/langgraph/checkpoint/duckdb/base.py new file mode 100644 index 000000000..951eadaca --- /dev/null +++ b/libs/checkpoint-duckdb/langgraph/checkpoint/duckdb/base.py @@ -0,0 +1,290 @@ +import json +import random +from typing import Any, List, Optional, Sequence, Tuple, cast + +from langchain_core.runnables import RunnableConfig + +from langgraph.checkpoint.base import ( + WRITES_IDX_MAP, + BaseCheckpointSaver, + ChannelVersions, + Checkpoint, + CheckpointMetadata, + get_checkpoint_id, +) +from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer +from langgraph.checkpoint.serde.types import TASKS, ChannelProtocol + +MetadataInput = Optional[dict[str, Any]] + +""" +To add a new migration, add a new string to the MIGRATIONS list. +The position of the migration in the list is the version number. +""" +MIGRATIONS = [ + """CREATE TABLE IF NOT EXISTS checkpoint_migrations ( + v INTEGER PRIMARY KEY +);""", + """CREATE TABLE IF NOT EXISTS checkpoints ( + thread_id TEXT NOT NULL, + checkpoint_ns TEXT NOT NULL DEFAULT '', + checkpoint_id TEXT NOT NULL, + parent_checkpoint_id TEXT, + type TEXT, + checkpoint JSON NOT NULL, + metadata JSON NOT NULL DEFAULT '{}', + PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id) +);""", + """CREATE TABLE IF NOT EXISTS checkpoint_blobs ( + thread_id TEXT NOT NULL, + checkpoint_ns TEXT NOT NULL DEFAULT '', + channel TEXT NOT NULL, + version TEXT NOT NULL, + type TEXT NOT NULL, + blob BLOB, + PRIMARY KEY (thread_id, checkpoint_ns, channel, version) +);""", + """CREATE TABLE IF NOT EXISTS checkpoint_writes ( + thread_id TEXT NOT NULL, + checkpoint_ns TEXT NOT NULL DEFAULT '', + checkpoint_id TEXT NOT NULL, + task_id TEXT NOT NULL, + idx INTEGER NOT NULL, + channel TEXT NOT NULL, + type TEXT, + blob BLOB NOT NULL, + PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, task_id, idx) +);""", +] + +SELECT_SQL = f""" +select + thread_id, + checkpoint, + checkpoint_ns, + checkpoint_id, + parent_checkpoint_id, + metadata, + ( + select array_agg(array[bl.channel::bytea, bl.type::bytea, bl.blob]) + from ( + SELECT unnest(json_keys(json_extract(checkpoint, '$.channel_versions'))) as key + ) cv + inner join checkpoint_blobs bl + on bl.thread_id = checkpoints.thread_id + and bl.checkpoint_ns = checkpoints.checkpoint_ns + and bl.channel = cv.key + and bl.version = json_extract_string(checkpoint, '$.channel_versions.' || cv.key) + ) as channel_values, + ( + select + array_agg(array[cw.task_id::blob, cw.channel::blob, cw.type::blob, cw.blob]) + from checkpoint_writes cw + where cw.thread_id = checkpoints.thread_id + and cw.checkpoint_ns = checkpoints.checkpoint_ns + and cw.checkpoint_id = checkpoints.checkpoint_id + ) as pending_writes, + ( + select array_agg(array[cw.type::blob, cw.blob]) + from checkpoint_writes cw + where cw.thread_id = checkpoints.thread_id + and cw.checkpoint_ns = checkpoints.checkpoint_ns + and cw.checkpoint_id = checkpoints.parent_checkpoint_id + and cw.channel = '{TASKS}' + ) as pending_sends +from checkpoints """ + +UPSERT_CHECKPOINT_BLOBS_SQL = """ + INSERT INTO checkpoint_blobs (thread_id, checkpoint_ns, channel, version, type, blob) + VALUES (?, ?, ?, ?, ?, ?) + ON CONFLICT (thread_id, checkpoint_ns, channel, version) DO NOTHING +""" + +UPSERT_CHECKPOINTS_SQL = """ + INSERT INTO checkpoints (thread_id, checkpoint_ns, checkpoint_id, parent_checkpoint_id, checkpoint, metadata) + VALUES (?, ?, ?, ?, ?, ?) + ON CONFLICT (thread_id, checkpoint_ns, checkpoint_id) + DO UPDATE SET + checkpoint = EXCLUDED.checkpoint, + metadata = EXCLUDED.metadata; +""" + +UPSERT_CHECKPOINT_WRITES_SQL = """ + INSERT INTO checkpoint_writes (thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, blob) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT (thread_id, checkpoint_ns, checkpoint_id, task_id, idx) DO UPDATE SET + channel = EXCLUDED.channel, + type = EXCLUDED.type, + blob = EXCLUDED.blob; +""" + +INSERT_CHECKPOINT_WRITES_SQL = """ + INSERT INTO checkpoint_writes (thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, blob) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT (thread_id, checkpoint_ns, checkpoint_id, task_id, idx) DO NOTHING +""" + + +class BaseDuckDBSaver(BaseCheckpointSaver[str]): + SELECT_SQL = SELECT_SQL + MIGRATIONS = MIGRATIONS + UPSERT_CHECKPOINT_BLOBS_SQL = UPSERT_CHECKPOINT_BLOBS_SQL + UPSERT_CHECKPOINTS_SQL = UPSERT_CHECKPOINTS_SQL + UPSERT_CHECKPOINT_WRITES_SQL = UPSERT_CHECKPOINT_WRITES_SQL + INSERT_CHECKPOINT_WRITES_SQL = INSERT_CHECKPOINT_WRITES_SQL + + jsonplus_serde = JsonPlusSerializer() + + def _load_checkpoint( + self, + checkpoint_json_str: str, + channel_values: list[tuple[bytes, bytes, bytes]], + pending_sends: list[tuple[bytes, bytes]], + ) -> Checkpoint: + checkpoint = json.loads(checkpoint_json_str) + return { + **checkpoint, + "pending_sends": [ + self.serde.loads_typed((c.decode(), b)) for c, b in pending_sends or [] + ], + "channel_values": self._load_blobs(channel_values), + } + + def _dump_checkpoint(self, checkpoint: Checkpoint) -> dict[str, Any]: + return {**checkpoint, "pending_sends": []} + + def _load_blobs( + self, blob_values: list[tuple[bytes, bytes, bytes]] + ) -> dict[str, Any]: + if not blob_values: + return {} + return { + k.decode(): self.serde.loads_typed((t.decode(), v)) + for k, t, v in blob_values + if t.decode() != "empty" + } + + def _dump_blobs( + self, + thread_id: str, + checkpoint_ns: str, + values: dict[str, Any], + versions: ChannelVersions, + ) -> list[tuple[str, str, str, str, str, Optional[bytes]]]: + if not versions: + return [] + + return [ + ( + thread_id, + checkpoint_ns, + k, + cast(str, ver), + *( + self.serde.dumps_typed(values[k]) + if k in values + else ("empty", None) + ), + ) + for k, ver in versions.items() + ] + + def _load_writes( + self, writes: list[tuple[bytes, bytes, bytes, bytes]] + ) -> list[tuple[str, str, Any]]: + return ( + [ + ( + tid.decode(), + channel.decode(), + self.serde.loads_typed((t.decode(), v)), + ) + for tid, channel, t, v in writes + ] + if writes + else [] + ) + + def _dump_writes( + self, + thread_id: str, + checkpoint_ns: str, + checkpoint_id: str, + task_id: str, + writes: Sequence[tuple[str, Any]], + ) -> list[tuple[str, str, str, str, int, str, str, bytes]]: + return [ + ( + thread_id, + checkpoint_ns, + checkpoint_id, + task_id, + WRITES_IDX_MAP.get(channel, idx), + channel, + *self.serde.dumps_typed(value), + ) + for idx, (channel, value) in enumerate(writes) + ] + + def _load_metadata(self, metadata_json_str: str) -> CheckpointMetadata: + return self.jsonplus_serde.loads(metadata_json_str.encode()) + + def _dump_metadata(self, metadata: CheckpointMetadata) -> str: + serialized_metadata = self.jsonplus_serde.dumps(metadata) + # NOTE: we're using JSON serializer (not msgpack), so we need to remove null characters before writing + return serialized_metadata.decode().replace("\\u0000", "") + + def get_next_version(self, current: Optional[str], channel: ChannelProtocol) -> str: + if current is None: + current_v = 0 + elif isinstance(current, int): + current_v = current + else: + current_v = int(current.split(".")[0]) + next_v = current_v + 1 + next_h = random.random() + return f"{next_v:032}.{next_h:016}" + + def _search_where( + self, + config: Optional[RunnableConfig], + filter: MetadataInput, + before: Optional[RunnableConfig] = None, + ) -> Tuple[str, List[Any]]: + """Return WHERE clause predicates for alist() given config, filter, before. + + This method returns a tuple of a string and a tuple of values. The string + is the parametered WHERE clause predicate (including the WHERE keyword): + "WHERE column1 = $1 AND column2 IS $2". The list of values contains the + values for each of the corresponding parameters. + """ + wheres = [] + param_values = [] + + # construct predicate for config filter + if config: + wheres.append("thread_id = ?") + param_values.append(config["configurable"]["thread_id"]) + checkpoint_ns = config["configurable"].get("checkpoint_ns") + if checkpoint_ns is not None: + wheres.append("checkpoint_ns = ?") + param_values.append(checkpoint_ns) + + if checkpoint_id := get_checkpoint_id(config): + wheres.append("checkpoint_id = ?") + param_values.append(checkpoint_id) + + # construct predicate for metadata filter + if filter: + wheres.append("json_contains(metadata, ?)") + param_values.append(json.dumps(filter)) + + # construct predicate for `before` + if before is not None: + wheres.append("checkpoint_id < ?") + param_values.append(get_checkpoint_id(before)) + + return ( + "WHERE " + " AND ".join(wheres) if wheres else "", + param_values, + ) diff --git a/libs/checkpoint-postgres/langgraph/checkpoint/py.typed b/libs/checkpoint-duckdb/langgraph/checkpoint/duckdb/py.typed similarity index 100% rename from libs/checkpoint-postgres/langgraph/checkpoint/py.typed rename to libs/checkpoint-duckdb/langgraph/checkpoint/duckdb/py.typed diff --git a/libs/checkpoint-duckdb/langgraph/store/duckdb/__init__.py b/libs/checkpoint-duckdb/langgraph/store/duckdb/__init__.py new file mode 100644 index 000000000..058c64bf7 --- /dev/null +++ b/libs/checkpoint-duckdb/langgraph/store/duckdb/__init__.py @@ -0,0 +1,4 @@ +from langgraph.store.duckdb.aio import AsyncDuckDBStore +from langgraph.store.duckdb.base import DuckDBStore + +__all__ = ["AsyncDuckDBStore", "DuckDBStore"] diff --git a/libs/checkpoint-duckdb/langgraph/store/duckdb/aio.py b/libs/checkpoint-duckdb/langgraph/store/duckdb/aio.py new file mode 100644 index 000000000..d6fd7dd89 --- /dev/null +++ b/libs/checkpoint-duckdb/langgraph/store/duckdb/aio.py @@ -0,0 +1,195 @@ +import asyncio +import logging +from contextlib import asynccontextmanager +from typing import ( + AsyncIterator, + Iterable, + Sequence, + cast, +) + +import duckdb +from langgraph.store.base import GetOp, ListNamespacesOp, Op, PutOp, Result, SearchOp +from langgraph.store.base.batch import AsyncBatchedBaseStore +from langgraph.store.duckdb.base import ( + BaseDuckDBStore, + _convert_ns, + _group_ops, + _row_to_item, +) + +logger = logging.getLogger(__name__) + + +class AsyncDuckDBStore(AsyncBatchedBaseStore, BaseDuckDBStore): + def __init__( + self, + conn: duckdb.DuckDBPyConnection, + ) -> None: + super().__init__() + self.conn = conn + self.loop = asyncio.get_running_loop() + + async def abatch(self, ops: Iterable[Op]) -> list[Result]: + grouped_ops, num_ops = _group_ops(ops) + results: list[Result] = [None] * num_ops + + tasks = [] + + if GetOp in grouped_ops: + tasks.append( + self._batch_get_ops( + cast(Sequence[tuple[int, GetOp]], grouped_ops[GetOp]), results + ) + ) + + if PutOp in grouped_ops: + tasks.append( + self._batch_put_ops( + cast(Sequence[tuple[int, PutOp]], grouped_ops[PutOp]) + ) + ) + + if SearchOp in grouped_ops: + tasks.append( + self._batch_search_ops( + cast(Sequence[tuple[int, SearchOp]], grouped_ops[SearchOp]), + results, + ) + ) + + if ListNamespacesOp in grouped_ops: + tasks.append( + self._batch_list_namespaces_ops( + cast( + Sequence[tuple[int, ListNamespacesOp]], + grouped_ops[ListNamespacesOp], + ), + results, + ) + ) + + await asyncio.gather(*tasks) + + return results + + def batch(self, ops: Iterable[Op]) -> list[Result]: + return asyncio.run_coroutine_threadsafe(self.abatch(ops), self.loop).result() + + async def _batch_get_ops( + self, + get_ops: Sequence[tuple[int, GetOp]], + results: list[Result], + ) -> None: + cursors = [] + for query, params, namespace, items in self._get_batch_GET_ops_queries(get_ops): + cur = self.conn.cursor() + await asyncio.to_thread(cur.execute, query, params) + cursors.append((cur, namespace, items)) + + for cur, namespace, items in cursors: + rows = await asyncio.to_thread(cur.fetchall) + key_to_row = {row[1]: row for row in rows} + for idx, key in items: + row = key_to_row.get(key) + if row: + results[idx] = _row_to_item(namespace, row) + else: + results[idx] = None + + async def _batch_put_ops( + self, + put_ops: Sequence[tuple[int, PutOp]], + ) -> None: + queries = self._get_batch_PUT_queries(put_ops) + for query, params in queries: + cur = self.conn.cursor() + await asyncio.to_thread(cur.execute, query, params) + + async def _batch_search_ops( + self, + search_ops: Sequence[tuple[int, SearchOp]], + results: list[Result], + ) -> None: + queries = self._get_batch_search_queries(search_ops) + cursors: list[tuple[duckdb.DuckDBPyConnection, int]] = [] + + for (query, params), (idx, _) in zip(queries, search_ops): + cur = self.conn.cursor() + await asyncio.to_thread(cur.execute, query, params) + cursors.append((cur, idx)) + + for cur, idx in cursors: + rows = await asyncio.to_thread(cur.fetchall) + items = [_row_to_item(_convert_ns(row[0]), row) for row in rows] + results[idx] = items + + async def _batch_list_namespaces_ops( + self, + list_ops: Sequence[tuple[int, ListNamespacesOp]], + results: list[Result], + ) -> None: + queries = self._get_batch_list_namespaces_queries(list_ops) + cursors: list[tuple[duckdb.DuckDBPyConnection, int]] = [] + for (query, params), (idx, _) in zip(queries, list_ops): + cur = self.conn.cursor() + await asyncio.to_thread(cur.execute, query, params) + cursors.append((cur, idx)) + + for cur, idx in cursors: + rows = cast(list[tuple], await asyncio.to_thread(cur.fetchall)) + namespaces = [_convert_ns(row[0]) for row in rows] + results[idx] = namespaces + + @classmethod + @asynccontextmanager + async def from_conn_string( + cls, + conn_string: str, + ) -> AsyncIterator["AsyncDuckDBStore"]: + """Create a new AsyncDuckDBStore instance from a connection string. + + Args: + conn_string (str): The DuckDB connection info string. + + Returns: + AsyncDuckDBStore: A new AsyncDuckDBStore instance. + """ + with duckdb.connect(conn_string) as conn: + yield AsyncDuckDBStore(conn) + + async def setup(self) -> None: + """Set up the store database asynchronously. + + This method creates the necessary tables in the DuckDB database if they don't + already exist and runs database migrations. It is called automatically when needed and should not be called + directly by the user. + """ + cur = self.conn.cursor() + try: + await asyncio.to_thread( + cur.execute, "SELECT v FROM store_migrations ORDER BY v DESC LIMIT 1" + ) + row = await asyncio.to_thread(cur.fetchone) + if row is None: + version = -1 + else: + version = row[0] + except duckdb.CatalogException: + version = -1 + # Create store_migrations table if it doesn't exist + await asyncio.to_thread( + cur.execute, + """ + CREATE TABLE IF NOT EXISTS store_migrations ( + v INTEGER PRIMARY KEY + ) + """, + ) + for v, migration in enumerate( + self.MIGRATIONS[version + 1 :], start=version + 1 + ): + await asyncio.to_thread(cur.execute, migration) + await asyncio.to_thread( + cur.execute, "INSERT INTO store_migrations (v) VALUES (?)", (v,) + ) diff --git a/libs/checkpoint-duckdb/langgraph/store/duckdb/base.py b/libs/checkpoint-duckdb/langgraph/store/duckdb/base.py new file mode 100644 index 000000000..e0fb57067 --- /dev/null +++ b/libs/checkpoint-duckdb/langgraph/store/duckdb/base.py @@ -0,0 +1,391 @@ +import asyncio +import json +import logging +from collections import defaultdict +from contextlib import contextmanager +from typing import ( + Any, + Generic, + Iterable, + Iterator, + Sequence, + TypeVar, + Union, + cast, +) + +import duckdb +from langgraph.store.base import ( + BaseStore, + GetOp, + Item, + ListNamespacesOp, + Op, + PutOp, + Result, + SearchOp, +) + +logger = logging.getLogger(__name__) + + +MIGRATIONS = [ + """ +CREATE TABLE IF NOT EXISTS store ( + prefix TEXT NOT NULL, + key TEXT NOT NULL, + value JSON NOT NULL, + created_at TIMESTAMP DEFAULT now(), + updated_at TIMESTAMP DEFAULT now(), + PRIMARY KEY (prefix, key) +); +""", + """ +CREATE INDEX IF NOT EXISTS store_prefix_idx ON store (prefix); +""", +] + +C = TypeVar("C", bound=duckdb.DuckDBPyConnection) + + +class BaseDuckDBStore(Generic[C]): + MIGRATIONS = MIGRATIONS + conn: C + + def _get_batch_GET_ops_queries( + self, + get_ops: Sequence[tuple[int, GetOp]], + ) -> list[tuple[str, tuple, tuple[str, ...], list]]: + namespace_groups = defaultdict(list) + for idx, op in get_ops: + namespace_groups[op.namespace].append((idx, op.key)) + results = [] + for namespace, items in namespace_groups.items(): + _, keys = zip(*items) + keys_to_query = ",".join(["?"] * len(keys)) + query = f""" + SELECT prefix, key, value, created_at, updated_at + FROM store + WHERE prefix = ? AND key IN ({keys_to_query}) + """ + params = (_namespace_to_text(namespace), *keys) + results.append((query, params, namespace, items)) + return results + + def _get_batch_PUT_queries( + self, + put_ops: Sequence[tuple[int, PutOp]], + ) -> list[tuple[str, Sequence]]: + inserts: list[PutOp] = [] + deletes: list[PutOp] = [] + for _, op in put_ops: + if op.value is None: + deletes.append(op) + else: + inserts.append(op) + + queries: list[tuple[str, Sequence]] = [] + + if deletes: + namespace_groups: dict[tuple[str, ...], list[str]] = defaultdict(list) + for op in deletes: + namespace_groups[op.namespace].append(op.key) + for namespace, keys in namespace_groups.items(): + placeholders = ",".join(["?"] * len(keys)) + query = ( + f"DELETE FROM store WHERE prefix = ? AND key IN ({placeholders})" + ) + params = (_namespace_to_text(namespace), *keys) + queries.append((query, params)) + if inserts: + values = [] + insertion_params = [] + for op in inserts: + values.append("(?, ?, ?, now(), now())") + insertion_params.extend( + [ + _namespace_to_text(op.namespace), + op.key, + json.dumps(op.value), + ] + ) + values_str = ",".join(values) + query = f""" + INSERT INTO store (prefix, key, value, created_at, updated_at) + VALUES {values_str} + ON CONFLICT (prefix, key) DO UPDATE + SET value = EXCLUDED.value, updated_at = now() + """ + queries.append((query, insertion_params)) + + return queries + + def _get_batch_search_queries( + self, + search_ops: Sequence[tuple[int, SearchOp]], + ) -> list[tuple[str, Sequence]]: + queries: list[tuple[str, Sequence]] = [] + for _, op in search_ops: + query = """ + SELECT prefix, key, value, created_at, updated_at + FROM store + WHERE prefix LIKE ? + """ + params: list = [f"{_namespace_to_text(op.namespace_prefix)}%"] + + if op.filter: + filter_conditions = [] + for key, value in op.filter.items(): + filter_conditions.append(f"json_extract(value, '$.{key}') = ?") + params.append(json.dumps(value)) + query += " AND " + " AND ".join(filter_conditions) + + query += " ORDER BY updated_at DESC LIMIT ? OFFSET ?" + params.extend([op.limit, op.offset]) + + queries.append((query, params)) + return queries + + def _get_batch_list_namespaces_queries( + self, + list_ops: Sequence[tuple[int, ListNamespacesOp]], + ) -> list[tuple[str, Sequence]]: + queries: list[tuple[str, Sequence]] = [] + for _, op in list_ops: + query = """ + WITH split_prefix AS ( + SELECT + prefix, + string_split(prefix, '.') AS parts + FROM store + ) + SELECT DISTINCT ON (truncated_prefix) + CASE + WHEN ? IS NOT NULL THEN + array_to_string(array_slice(parts, 1, ?), '.') + ELSE prefix + END AS truncated_prefix, + prefix + FROM split_prefix + """ + params: list[Any] = [op.max_depth, op.max_depth] + + conditions = [] + if op.match_conditions: + for condition in op.match_conditions: + if condition.match_type == "prefix": + conditions.append("prefix LIKE ?") + params.append( + f"{_namespace_to_text(condition.path, handle_wildcards=True)}%" + ) + elif condition.match_type == "suffix": + conditions.append("prefix LIKE ?") + params.append( + f"%{_namespace_to_text(condition.path, handle_wildcards=True)}" + ) + else: + logger.warning( + f"Unknown match_type in list_namespaces: {condition.match_type}" + ) + + if conditions: + query += " WHERE " + " AND ".join(conditions) + + query += " ORDER BY prefix LIMIT ? OFFSET ?" + params.extend([op.limit, op.offset]) + queries.append((query, params)) + + return queries + + +class DuckDBStore(BaseStore, BaseDuckDBStore[duckdb.DuckDBPyConnection]): + def __init__( + self, + conn: duckdb.DuckDBPyConnection, + ) -> None: + super().__init__() + self.conn = conn + + def batch(self, ops: Iterable[Op]) -> list[Result]: + grouped_ops, num_ops = _group_ops(ops) + results: list[Result] = [None] * num_ops + + if GetOp in grouped_ops: + self._batch_get_ops( + cast(Sequence[tuple[int, GetOp]], grouped_ops[GetOp]), results + ) + + if PutOp in grouped_ops: + self._batch_put_ops(cast(Sequence[tuple[int, PutOp]], grouped_ops[PutOp])) + + if SearchOp in grouped_ops: + self._batch_search_ops( + cast(Sequence[tuple[int, SearchOp]], grouped_ops[SearchOp]), + results, + ) + + if ListNamespacesOp in grouped_ops: + self._batch_list_namespaces_ops( + cast( + Sequence[tuple[int, ListNamespacesOp]], + grouped_ops[ListNamespacesOp], + ), + results, + ) + + return results + + async def abatch(self, ops: Iterable[Op]) -> list[Result]: + return await asyncio.get_running_loop().run_in_executor(None, self.batch, ops) + + def _batch_get_ops( + self, + get_ops: Sequence[tuple[int, GetOp]], + results: list[Result], + ) -> None: + cursors = [] + for query, params, namespace, items in self._get_batch_GET_ops_queries(get_ops): + cur = self.conn.cursor() + cur.execute(query, params) + cursors.append((cur, namespace, items)) + + for cur, namespace, items in cursors: + rows = cur.fetchall() + key_to_row = {row[1]: row for row in rows} + for idx, key in items: + row = key_to_row.get(key) + if row: + results[idx] = _row_to_item(namespace, row) + else: + results[idx] = None + + def _batch_put_ops( + self, + put_ops: Sequence[tuple[int, PutOp]], + ) -> None: + queries = self._get_batch_PUT_queries(put_ops) + for query, params in queries: + cur = self.conn.cursor() + cur.execute(query, params) + + def _batch_search_ops( + self, + search_ops: Sequence[tuple[int, SearchOp]], + results: list[Result], + ) -> None: + queries = self._get_batch_search_queries(search_ops) + cursors: list[tuple[duckdb.DuckDBPyConnection, int]] = [] + + for (query, params), (idx, _) in zip(queries, search_ops): + cur = self.conn.cursor() + cur.execute(query, params) + cursors.append((cur, idx)) + + for cur, idx in cursors: + rows = cur.fetchall() + items = [_row_to_item(_convert_ns(row[0]), row) for row in rows] + results[idx] = items + + def _batch_list_namespaces_ops( + self, + list_ops: Sequence[tuple[int, ListNamespacesOp]], + results: list[Result], + ) -> None: + queries = self._get_batch_list_namespaces_queries(list_ops) + cursors: list[tuple[duckdb.DuckDBPyConnection, int]] = [] + for (query, params), (idx, _) in zip(queries, list_ops): + cur = self.conn.cursor() + cur.execute(query, params) + cursors.append((cur, idx)) + + for cur, idx in cursors: + rows = cast(list[dict], cur.fetchall()) + namespaces = [_convert_ns(row[0]) for row in rows] + results[idx] = namespaces + + @classmethod + @contextmanager + def from_conn_string( + cls, + conn_string: str, + ) -> Iterator["DuckDBStore"]: + """Create a new BaseDuckDBStore instance from a connection string. + + Args: + conn_string (str): The DuckDB connection info string. + + Returns: + DuckDBStore: A new DuckDBStore instance. + """ + with duckdb.connect(conn_string) as conn: + yield cls(conn=conn) + + def setup(self) -> None: + """Set up the store database. + + This method creates the necessary tables in the DuckDB database if they don't + already exist and runs database migrations. It is called automatically when needed and should not be called + directly by the user. + """ + with self.conn.cursor() as cur: + try: + cur.execute("SELECT v FROM store_migrations ORDER BY v DESC LIMIT 1") + row = cast(dict, cur.fetchone()) + if row is None: + version = -1 + else: + version = row["v"] + except duckdb.CatalogException: + version = -1 + # Create store_migrations table if it doesn't exist + cur.execute( + """ + CREATE TABLE IF NOT EXISTS store_migrations ( + v INTEGER PRIMARY KEY + ) + """ + ) + for v, migration in enumerate( + self.MIGRATIONS[version + 1 :], start=version + 1 + ): + cur.execute(migration) + cur.execute("INSERT INTO store_migrations (v) VALUES (?)", (v,)) + + +def _namespace_to_text( + namespace: tuple[str, ...], handle_wildcards: bool = False +) -> str: + """Convert namespace tuple to text string.""" + if handle_wildcards: + namespace = tuple("%" if val == "*" else val for val in namespace) + return ".".join(namespace) + + +def _row_to_item( + namespace: tuple[str, ...], + row: tuple, +) -> Item: + """Convert a row from the database into an Item.""" + _, key, val, created_at, updated_at = row + return Item( + value=val if isinstance(val, dict) else json.loads(val), + key=key, + namespace=namespace, + created_at=created_at, + updated_at=updated_at, + ) + + +def _group_ops(ops: Iterable[Op]) -> tuple[dict[type, list[tuple[int, Op]]], int]: + grouped_ops: dict[type, list[tuple[int, Op]]] = defaultdict(list) + tot = 0 + for idx, op in enumerate(ops): + grouped_ops[type(op)].append((idx, op)) + tot += 1 + return grouped_ops, tot + + +def _convert_ns(namespace: Union[str, list]) -> tuple[str, ...]: + if isinstance(namespace, list): + return tuple(namespace) + return tuple(namespace.split(".")) diff --git a/libs/checkpoint-sqlite/langgraph/checkpoint/py.typed b/libs/checkpoint-duckdb/langgraph/store/duckdb/py.typed similarity index 100% rename from libs/checkpoint-sqlite/langgraph/checkpoint/py.typed rename to libs/checkpoint-duckdb/langgraph/store/duckdb/py.typed diff --git a/libs/checkpoint-duckdb/poetry.lock b/libs/checkpoint-duckdb/poetry.lock new file mode 100644 index 000000000..4ce1cbaba --- /dev/null +++ b/libs/checkpoint-duckdb/poetry.lock @@ -0,0 +1,1058 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "anyio" +version = "4.4.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, + {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + +[[package]] +name = "certifi" +version = "2024.7.4" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "codespell" +version = "2.3.0" +description = "Codespell" +optional = false +python-versions = ">=3.8" +files = [ + {file = "codespell-2.3.0-py3-none-any.whl", hash = "sha256:a9c7cef2501c9cfede2110fd6d4e5e62296920efe9abfb84648df866e47f58d1"}, + {file = "codespell-2.3.0.tar.gz", hash = "sha256:360c7d10f75e65f67bad720af7007e1060a5d395670ec11a7ed1fed9dd17471f"}, +] + +[package.extras] +dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"] +hard-encoding-detection = ["chardet"] +toml = ["tomli"] +types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "docopt" +version = "0.6.2" +description = "Pythonic argument parser, that will make you smile" +optional = false +python-versions = "*" +files = [ + {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, +] + +[[package]] +name = "duckdb" +version = "1.1.2" +description = "DuckDB in-process database" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "duckdb-1.1.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:91e7f99cf5cab1d26f92cb014429153497d805e79689baa44f4c4585a8cb243f"}, + {file = "duckdb-1.1.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:0107de622fe208142a1108263a03c43956048dcc99be3702d8e5d2aeaf99554c"}, + {file = "duckdb-1.1.2-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:8a09610f780857677725897856f8cdf3cafd8a991f871e6cb8ba88b2dbc8d737"}, + {file = "duckdb-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0f0ddac0482f0f3fece54d720d13819e82ae26c01a939ffa66a87be53f7f665"}, + {file = "duckdb-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84103373e818758dfa361d27781d0f096553843c5ffb9193260a0786c5248270"}, + {file = "duckdb-1.1.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bfdfd23e2bf58014ad0673973bd0ed88cd048dfe8e82420814a71d7d52ef2288"}, + {file = "duckdb-1.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25889e6e29b87047b1dd56385ac08156e4713c59326cc6fff89657d01b2c417b"}, + {file = "duckdb-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:312570fa5277c3079de18388b86c2d87cbe1044838bb152b235c0227581d5d42"}, + {file = "duckdb-1.1.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:568439ea4fce8cb72ec1f767cd510686a9e7e29a011fc7c56d990059a6e94e48"}, + {file = "duckdb-1.1.2-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:74974f2d7210623a5d61b1fb0cb589c6e5ffcbf7dbb757a04c5ba24adcfc8cac"}, + {file = "duckdb-1.1.2-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:e26422a3358c816d764639070945b73eef55d1b4df990989e3492c85ef725c21"}, + {file = "duckdb-1.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87e972bd452eeeab197fe39dcaeecdb7c264b1f75a0ee67e532e235fe45b84df"}, + {file = "duckdb-1.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a6b73e70b73c8df85da383f6e557c03cad5c877868b9a7e41715761e8166c1e"}, + {file = "duckdb-1.1.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:623cb1952466aae5907af84107bcdec25a5ca021a8b6441e961f41edc724f6f2"}, + {file = "duckdb-1.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9fc0b550f96901fa7e76dc70a13f6477ad3e18ef1cb21d414c3a5569de3f27e"}, + {file = "duckdb-1.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:181edb1973bd8f493bcb6ecfa035f1a592dff4667758592f300619012ba251c0"}, + {file = "duckdb-1.1.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:83372b1b411086cac01ab2071122772fa66170b1b41ddbc37527464066083668"}, + {file = "duckdb-1.1.2-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:db37441deddfee6ac35a0c742d2f9e90e4e50b9e76d586a060d122b8fc56dada"}, + {file = "duckdb-1.1.2-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:19142a77e72874aeaa6fda30aeb13612c6de5e8c60fbcc3392cea6ef0694eeaf"}, + {file = "duckdb-1.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:099d99dd48d6e4682a3dd6233ceab73d977ebe1a87afaac54cf77c844e24514a"}, + {file = "duckdb-1.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be86e586ca7af7e807f72479a2b8d0983565360b19dbda4ef8a9d7b3909b8e2c"}, + {file = "duckdb-1.1.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:578e0953e4d8ba8da0cd69fb2930c45f51ce47d213b77d8a4cd461f9c0960b87"}, + {file = "duckdb-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:72b5eb5762c1a5e68849c7143f3b3747a9f15c040e34e41559f233a1569ad16f"}, + {file = "duckdb-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:9b4c6b6a08180261d98330d97355503961a25ca31cd9ef296e0681f7895b4a2c"}, + {file = "duckdb-1.1.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:695dcbc561374b126e86659709feadf883c9969ed718e94713edd4ba15d16619"}, + {file = "duckdb-1.1.2-cp313-cp313-macosx_12_0_universal2.whl", hash = "sha256:ada29be1e889f486c6cf1f6dffd15463e748faf361f33996f2e862779edc24a9"}, + {file = "duckdb-1.1.2-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:6ca722738fa9eb6218619740631de29acfdd132de6f6a6350fee5e291c2f6117"}, + {file = "duckdb-1.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c796d33f1e5a0c8c570d22da0c0b1db8578687e427029e1ce2c8ce3f9fffa6a3"}, + {file = "duckdb-1.1.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5c0996988a70dd3bc8111d9b9aeab7e38ed1999a52607c5f1b528e362b4dd1c"}, + {file = "duckdb-1.1.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c37b039f6d6fed14d89450f5ccf54922b3304192d7412e12d6cc8d9e757f7a2"}, + {file = "duckdb-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8c766b87f675c76d6d17103bf6fb9fb1a9e2fcb3d9b25c28bbc634bde31223e"}, + {file = "duckdb-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:e3e6300b7ccaf64b609f4f0780a6e1d25ab8cf34cceed46e62c35b6c4c5cb63b"}, + {file = "duckdb-1.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a01fae9604a54ecbc26e7503c522311f15afbd2870e6d8f6fbef4545dfae550"}, + {file = "duckdb-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:492b1d86a696428bd3f14dc1c7c3230e2dbca8978f288be64b04a26e0e00fad5"}, + {file = "duckdb-1.1.2-cp37-cp37m-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bba58459ad897a78c4e478a097626fc266459a40338cecc68a49a8d5dc72fb7"}, + {file = "duckdb-1.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:d395a3bf510bf24686821eec15802624797dcb33e8f14f8a7cc8e17d909474af"}, + {file = "duckdb-1.1.2-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:fd800f75728727fe699ed1eb22b636867cf48c9dd105ee88b977e20c89df4509"}, + {file = "duckdb-1.1.2-cp38-cp38-macosx_12_0_universal2.whl", hash = "sha256:d8caaf43909e49537e26df51d80d075ae2b25a610d28ed8bd31d6ccebeaf3c65"}, + {file = "duckdb-1.1.2-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:564166811c68d9c7f9911eb707ad32ec9c2507b98336d894fbe658b85bf1c697"}, + {file = "duckdb-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19386aa09f0d6f97634ba2972096d1c80d880176dfb0e949eadc91c98262a663"}, + {file = "duckdb-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9e8387bcc9a591ad14011ddfec0d408d1d9b1889c6c9b495a04c7016a24b9b3"}, + {file = "duckdb-1.1.2-cp38-cp38-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8c5ff4970403ed3ff0ac71fe0ce1e6be3199df9d542afc84c424b444ba4ffe8"}, + {file = "duckdb-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:9283dcca87c3260eb631a99d738fa72b8545ed45b475bc72ad254f7310e14284"}, + {file = "duckdb-1.1.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:f87edaf20001530e63a4f7bda13b55dc3152d7171226915f2bf34e0813c8759e"}, + {file = "duckdb-1.1.2-cp39-cp39-macosx_12_0_universal2.whl", hash = "sha256:efec169b3fe0b821e3207ba3e445f227d42dd62b4440ff79c37fa168a4fc5a71"}, + {file = "duckdb-1.1.2-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:89164a2d29d56605a95ee5032aa415dd487028c4fd3e06d971497840e74c56e7"}, + {file = "duckdb-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6858e10c60ff7e70e61d3dd53d2545c8b2609942e45fd6de38cd0dee52932de3"}, + {file = "duckdb-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca967c5a57b1d0cb0fd5e539ab24110e5a59dcbedd365bb2dc80533d6e44a8d"}, + {file = "duckdb-1.1.2-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ce949f1d7999aa6a046eb64067eee41d4c5c2872ba4fa408c9947742d0c7231"}, + {file = "duckdb-1.1.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ba6d1f918e6ca47a368a0c32806016405cb9beb2c245806b0ca998f569d2bdf"}, + {file = "duckdb-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:7111fd3e7b334a7be383313ce29918b7c643e4f6ef44d6d63c3ab3fa6716c114"}, + {file = "duckdb-1.1.2.tar.gz", hash = "sha256:c8232861dc8ec6daa29067056d5a0e5789919f2ab22ab792787616d7cd52f02a"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.5" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.26.0)"] + +[[package]] +name = "httpx" +version = "0.27.2" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, + {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jsonpatch" +version = "1.33" +description = "Apply JSON-Patches (RFC 6902)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, + {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, +] + +[package.dependencies] +jsonpointer = ">=1.9" + +[[package]] +name = "jsonpointer" +version = "3.0.0" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, + {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, +] + +[[package]] +name = "langchain-core" +version = "0.3.0" +description = "Building applications with LLMs through composability" +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "langchain_core-0.3.0-py3-none-any.whl", hash = "sha256:bee6dae2366d037ef0c5b87401fed14b5497cad26f97724e8c9ca7bc9239e847"}, + {file = "langchain_core-0.3.0.tar.gz", hash = "sha256:1249149ea3ba24c9c761011483c14091573a5eb1a773aa0db9c8ad155dd4a69d"}, +] + +[package.dependencies] +jsonpatch = ">=1.33,<2.0" +langsmith = ">=0.1.117,<0.2.0" +packaging = ">=23.2,<25" +pydantic = [ + {version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""}, + {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, +] +PyYAML = ">=5.3" +tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0" +typing-extensions = ">=4.7" + +[[package]] +name = "langgraph-checkpoint" +version = "2.0.2" +description = "Library with base interfaces for LangGraph checkpoint savers." +optional = false +python-versions = "^3.9.0,<4.0" +files = [] +develop = true + +[package.dependencies] +langchain-core = ">=0.2.38,<0.4" +msgpack = "^1.1.0" + +[package.source] +type = "directory" +url = "../checkpoint" + +[[package]] +name = "langsmith" +version = "0.1.120" +description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." +optional = false +python-versions = "<4.0,>=3.8.1" +files = [ + {file = "langsmith-0.1.120-py3-none-any.whl", hash = "sha256:54d2785e301646c0988e0a69ebe4d976488c87b41928b358cb153b6ddd8db62b"}, + {file = "langsmith-0.1.120.tar.gz", hash = "sha256:25499ca187b41bd89d784b272b97a8d76f60e0e21bdf20336e8a2aa6a9b23ac9"}, +] + +[package.dependencies] +httpx = ">=0.23.0,<1" +orjson = ">=3.9.14,<4.0.0" +pydantic = [ + {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""}, + {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, +] +requests = ">=2,<3" + +[[package]] +name = "msgpack" +version = "1.1.0" +description = "MessagePack serializer" +optional = false +python-versions = ">=3.8" +files = [ + {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, + {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, + {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"}, + {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"}, + {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"}, + {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"}, + {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"}, + {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"}, + {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"}, + {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"}, + {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"}, + {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"}, + {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"}, + {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"}, + {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"}, + {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"}, +] + +[[package]] +name = "mypy" +version = "1.11.2" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, + {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, + {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, + {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, + {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, + {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, + {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, + {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, + {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, + {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, + {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, + {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, + {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, + {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, + {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, + {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, + {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, + {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, + {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, + {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, + {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, + {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, + {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "orjson" +version = "3.10.6" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +optional = false +python-versions = ">=3.8" +files = [ + {file = "orjson-3.10.6-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:fb0ee33124db6eaa517d00890fc1a55c3bfe1cf78ba4a8899d71a06f2d6ff5c7"}, + {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c1c4b53b24a4c06547ce43e5fee6ec4e0d8fe2d597f4647fc033fd205707365"}, + {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eadc8fd310edb4bdbd333374f2c8fec6794bbbae99b592f448d8214a5e4050c0"}, + {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61272a5aec2b2661f4fa2b37c907ce9701e821b2c1285d5c3ab0207ebd358d38"}, + {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57985ee7e91d6214c837936dc1608f40f330a6b88bb13f5a57ce5257807da143"}, + {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:633a3b31d9d7c9f02d49c4ab4d0a86065c4a6f6adc297d63d272e043472acab5"}, + {file = "orjson-3.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1c680b269d33ec444afe2bdc647c9eb73166fa47a16d9a75ee56a374f4a45f43"}, + {file = "orjson-3.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f759503a97a6ace19e55461395ab0d618b5a117e8d0fbb20e70cfd68a47327f2"}, + {file = "orjson-3.10.6-cp310-none-win32.whl", hash = "sha256:95a0cce17f969fb5391762e5719575217bd10ac5a189d1979442ee54456393f3"}, + {file = "orjson-3.10.6-cp310-none-win_amd64.whl", hash = "sha256:df25d9271270ba2133cc88ee83c318372bdc0f2cd6f32e7a450809a111efc45c"}, + {file = "orjson-3.10.6-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b1ec490e10d2a77c345def52599311849fc063ae0e67cf4f84528073152bb2ba"}, + {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d43d3feb8f19d07e9f01e5b9be4f28801cf7c60d0fa0d279951b18fae1932b"}, + {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3045267e98fe749408eee1593a142e02357c5c99be0802185ef2170086a863"}, + {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c27bc6a28ae95923350ab382c57113abd38f3928af3c80be6f2ba7eb8d8db0b0"}, + {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d27456491ca79532d11e507cadca37fb8c9324a3976294f68fb1eff2dc6ced5a"}, + {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05ac3d3916023745aa3b3b388e91b9166be1ca02b7c7e41045da6d12985685f0"}, + {file = "orjson-3.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1335d4ef59ab85cab66fe73fd7a4e881c298ee7f63ede918b7faa1b27cbe5212"}, + {file = "orjson-3.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4bbc6d0af24c1575edc79994c20e1b29e6fb3c6a570371306db0993ecf144dc5"}, + {file = "orjson-3.10.6-cp311-none-win32.whl", hash = "sha256:450e39ab1f7694465060a0550b3f6d328d20297bf2e06aa947b97c21e5241fbd"}, + {file = "orjson-3.10.6-cp311-none-win_amd64.whl", hash = "sha256:227df19441372610b20e05bdb906e1742ec2ad7a66ac8350dcfd29a63014a83b"}, + {file = "orjson-3.10.6-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ea2977b21f8d5d9b758bb3f344a75e55ca78e3ff85595d248eee813ae23ecdfb"}, + {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6f3d167d13a16ed263b52dbfedff52c962bfd3d270b46b7518365bcc2121eed"}, + {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f710f346e4c44a4e8bdf23daa974faede58f83334289df80bc9cd12fe82573c7"}, + {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7275664f84e027dcb1ad5200b8b18373e9c669b2a9ec33d410c40f5ccf4b257e"}, + {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0943e4c701196b23c240b3d10ed8ecd674f03089198cf503105b474a4f77f21f"}, + {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:446dee5a491b5bc7d8f825d80d9637e7af43f86a331207b9c9610e2f93fee22a"}, + {file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:64c81456d2a050d380786413786b057983892db105516639cb5d3ee3c7fd5148"}, + {file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:960db0e31c4e52fa0fc3ecbaea5b2d3b58f379e32a95ae6b0ebeaa25b93dfd34"}, + {file = "orjson-3.10.6-cp312-none-win32.whl", hash = "sha256:a6ea7afb5b30b2317e0bee03c8d34c8181bc5a36f2afd4d0952f378972c4efd5"}, + {file = "orjson-3.10.6-cp312-none-win_amd64.whl", hash = "sha256:874ce88264b7e655dde4aeaacdc8fd772a7962faadfb41abe63e2a4861abc3dc"}, + {file = "orjson-3.10.6-cp313-none-win32.whl", hash = "sha256:efdf2c5cde290ae6b83095f03119bdc00303d7a03b42b16c54517baa3c4ca3d0"}, + {file = "orjson-3.10.6-cp313-none-win_amd64.whl", hash = "sha256:8e190fe7888e2e4392f52cafb9626113ba135ef53aacc65cd13109eb9746c43e"}, + {file = "orjson-3.10.6-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:66680eae4c4e7fc193d91cfc1353ad6d01b4801ae9b5314f17e11ba55e934183"}, + {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:caff75b425db5ef8e8f23af93c80f072f97b4fb3afd4af44482905c9f588da28"}, + {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3722fddb821b6036fd2a3c814f6bd9b57a89dc6337b9924ecd614ebce3271394"}, + {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2c116072a8533f2fec435fde4d134610f806bdac20188c7bd2081f3e9e0133f"}, + {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6eeb13218c8cf34c61912e9df2de2853f1d009de0e46ea09ccdf3d757896af0a"}, + {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:965a916373382674e323c957d560b953d81d7a8603fbeee26f7b8248638bd48b"}, + {file = "orjson-3.10.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:03c95484d53ed8e479cade8628c9cea00fd9d67f5554764a1110e0d5aa2de96e"}, + {file = "orjson-3.10.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e060748a04cccf1e0a6f2358dffea9c080b849a4a68c28b1b907f272b5127e9b"}, + {file = "orjson-3.10.6-cp38-none-win32.whl", hash = "sha256:738dbe3ef909c4b019d69afc19caf6b5ed0e2f1c786b5d6215fbb7539246e4c6"}, + {file = "orjson-3.10.6-cp38-none-win_amd64.whl", hash = "sha256:d40f839dddf6a7d77114fe6b8a70218556408c71d4d6e29413bb5f150a692ff7"}, + {file = "orjson-3.10.6-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:697a35a083c4f834807a6232b3e62c8b280f7a44ad0b759fd4dce748951e70db"}, + {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd502f96bf5ea9a61cbc0b2b5900d0dd68aa0da197179042bdd2be67e51a1e4b"}, + {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f215789fb1667cdc874c1b8af6a84dc939fd802bf293a8334fce185c79cd359b"}, + {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2debd8ddce948a8c0938c8c93ade191d2f4ba4649a54302a7da905a81f00b56"}, + {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5410111d7b6681d4b0d65e0f58a13be588d01b473822483f77f513c7f93bd3b2"}, + {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb1f28a137337fdc18384079fa5726810681055b32b92253fa15ae5656e1dddb"}, + {file = "orjson-3.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bf2fbbce5fe7cd1aa177ea3eab2b8e6a6bc6e8592e4279ed3db2d62e57c0e1b2"}, + {file = "orjson-3.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:79b9b9e33bd4c517445a62b90ca0cc279b0f1f3970655c3df9e608bc3f91741a"}, + {file = "orjson-3.10.6-cp39-none-win32.whl", hash = "sha256:30b0a09a2014e621b1adf66a4f705f0809358350a757508ee80209b2d8dae219"}, + {file = "orjson-3.10.6-cp39-none-win_amd64.whl", hash = "sha256:49e3bc615652617d463069f91b867a4458114c5b104e13b7ae6872e5f79d0844"}, + {file = "orjson-3.10.6.tar.gz", hash = "sha256:e54b63d0a7c6c54a5f5f726bc93a2078111ef060fec4ecbf34c5db800ca3b3a7"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pydantic" +version = "2.8.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, + {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.20.1" +typing-extensions = [ + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, +] + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.20.1" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, + {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, + {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, + {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, + {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, + {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, + {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, + {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, + {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, + {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, + {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, + {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, + {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, + {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.21.2" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest_asyncio-0.21.2-py3-none-any.whl", hash = "sha256:ab664c88bb7998f711d8039cacd4884da6430886ae8bbd4eded552ed2004f16b"}, + {file = "pytest_asyncio-0.21.2.tar.gz", hash = "sha256:d67738fc232b94b326b9d060750beb16e0074210b98dd8b58a5239fa2a154f45"}, +] + +[package.dependencies] +pytest = ">=7.0.0" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] + +[[package]] +name = "pytest-mock" +version = "3.14.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + +[[package]] +name = "pytest-watch" +version = "4.2.0" +description = "Local continuous test runner with pytest and watchdog." +optional = false +python-versions = "*" +files = [ + {file = "pytest-watch-4.2.0.tar.gz", hash = "sha256:06136f03d5b361718b8d0d234042f7b2f203910d8568f63df2f866b547b3d4b9"}, +] + +[package.dependencies] +colorama = ">=0.3.3" +docopt = ">=0.4.0" +pytest = ">=2.6.4" +watchdog = ">=0.6.0" + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "ruff" +version = "0.6.2" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.6.2-py3-none-linux_armv6l.whl", hash = "sha256:5c8cbc6252deb3ea840ad6a20b0f8583caab0c5ef4f9cca21adc5a92b8f79f3c"}, + {file = "ruff-0.6.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:17002fe241e76544448a8e1e6118abecbe8cd10cf68fde635dad480dba594570"}, + {file = "ruff-0.6.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3dbeac76ed13456f8158b8f4fe087bf87882e645c8e8b606dd17b0b66c2c1158"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:094600ee88cda325988d3f54e3588c46de5c18dae09d683ace278b11f9d4d534"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:316d418fe258c036ba05fbf7dfc1f7d3d4096db63431546163b472285668132b"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d72b8b3abf8a2d51b7b9944a41307d2f442558ccb3859bbd87e6ae9be1694a5d"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2aed7e243be68487aa8982e91c6e260982d00da3f38955873aecd5a9204b1d66"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d371f7fc9cec83497fe7cf5eaf5b76e22a8efce463de5f775a1826197feb9df8"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8f310d63af08f583363dfb844ba8f9417b558199c58a5999215082036d795a1"}, + {file = "ruff-0.6.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7db6880c53c56addb8638fe444818183385ec85eeada1d48fc5abe045301b2f1"}, + {file = "ruff-0.6.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1175d39faadd9a50718f478d23bfc1d4da5743f1ab56af81a2b6caf0a2394f23"}, + {file = "ruff-0.6.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b939f9c86d51635fe486585389f54582f0d65b8238e08c327c1534844b3bb9a"}, + {file = "ruff-0.6.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d0d62ca91219f906caf9b187dea50d17353f15ec9bb15aae4a606cd697b49b4c"}, + {file = "ruff-0.6.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7438a7288f9d67ed3c8ce4d059e67f7ed65e9fe3aa2ab6f5b4b3610e57e3cb56"}, + {file = "ruff-0.6.2-py3-none-win32.whl", hash = "sha256:279d5f7d86696df5f9549b56b9b6a7f6c72961b619022b5b7999b15db392a4da"}, + {file = "ruff-0.6.2-py3-none-win_amd64.whl", hash = "sha256:d9f3469c7dd43cd22eb1c3fc16926fb8258d50cb1b216658a07be95dd117b0f2"}, + {file = "ruff-0.6.2-py3-none-win_arm64.whl", hash = "sha256:f28fcd2cd0e02bdf739297516d5643a945cc7caf09bd9bcb4d932540a5ea4fa9"}, + {file = "ruff-0.6.2.tar.gz", hash = "sha256:239ee6beb9e91feb8e0ec384204a763f36cb53fb895a1a364618c6abb076b3be"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "tenacity" +version = "8.5.0" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687"}, + {file = "tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78"}, +] + +[package.extras] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "watchdog" +version = "4.0.1" +description = "Filesystem events monitoring" +optional = false +python-versions = ">=3.8" +files = [ + {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682"}, + {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"}, + {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"}, + {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193"}, + {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625"}, + {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd"}, + {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"}, + {file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"}, + {file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"}, + {file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"}, + {file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9.0,<4.0" +content-hash = "c319b072af396b6f10fd6f75544816ea717741ed8b35ce675df65506c585f67e" diff --git a/libs/checkpoint-duckdb/pyproject.toml b/libs/checkpoint-duckdb/pyproject.toml new file mode 100644 index 000000000..74fbc07ca --- /dev/null +++ b/libs/checkpoint-duckdb/pyproject.toml @@ -0,0 +1,60 @@ +[tool.poetry] +name = "langgraph-checkpoint-duckdb" +version = "2.0.1" +description = "Library with a DuckDB implementation of LangGraph checkpoint saver." +authors = [] +license = "MIT" +readme = "README.md" +repository = "https://www.github.com/langchain-ai/langgraph" +packages = [{ include = "langgraph" }] + +[tool.poetry.dependencies] +python = "^3.9.0,<4.0" +langgraph-checkpoint = "^2.0.2" +duckdb = ">=1.1.2" + +[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" +pytest-watch = "^4.2.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 + "F", # Pyflakes + "UP", # pyupgrade + "B", # flake8-bugbear + "I", # isort +] +lint.ignore = ["E501", "B008", "UP007", "UP006"] + +[tool.mypy] +# https://mypy.readthedocs.io/en/stable/config_file.html +disallow_untyped_defs = "True" +explicit_package_bases = "True" +warn_no_return = "False" +warn_unused_ignores = "True" +warn_redundant_casts = "True" +allow_redefinition = "True" +disable_error_code = "typeddict-item, return-value" diff --git a/libs/checkpoint-duckdb/tests/test_async.py b/libs/checkpoint-duckdb/tests/test_async.py new file mode 100644 index 000000000..85c0f6dfb --- /dev/null +++ b/libs/checkpoint-duckdb/tests/test_async.py @@ -0,0 +1,112 @@ +from typing import Any + +import pytest +from langchain_core.runnables import RunnableConfig + +from langgraph.checkpoint.base import ( + Checkpoint, + CheckpointMetadata, + create_checkpoint, + empty_checkpoint, +) +from langgraph.checkpoint.duckdb.aio import AsyncDuckDBSaver + + +class TestAsyncDuckDBSaver: + @pytest.fixture(autouse=True) + async def setup(self) -> None: + # objects for test setup + self.config_1: RunnableConfig = { + "configurable": { + "thread_id": "thread-1", + # for backwards compatibility testing + "thread_ts": "1", + "checkpoint_ns": "", + } + } + self.config_2: RunnableConfig = { + "configurable": { + "thread_id": "thread-2", + "checkpoint_id": "2", + "checkpoint_ns": "", + } + } + self.config_3: RunnableConfig = { + "configurable": { + "thread_id": "thread-2", + "checkpoint_id": "2-inner", + "checkpoint_ns": "inner", + } + } + + self.chkpnt_1: Checkpoint = empty_checkpoint() + self.chkpnt_2: Checkpoint = create_checkpoint(self.chkpnt_1, {}, 1) + self.chkpnt_3: Checkpoint = empty_checkpoint() + + self.metadata_1: CheckpointMetadata = { + "source": "input", + "step": 2, + "writes": {}, + "score": 1, + } + self.metadata_2: CheckpointMetadata = { + "source": "loop", + "step": 1, + "writes": {"foo": "bar"}, + "score": None, + } + self.metadata_3: CheckpointMetadata = {} + + async def test_asearch(self) -> None: + async with AsyncDuckDBSaver.from_conn_string(":memory:") as saver: + await saver.setup() + await saver.aput(self.config_1, self.chkpnt_1, self.metadata_1, {}) + await saver.aput(self.config_2, self.chkpnt_2, self.metadata_2, {}) + await saver.aput(self.config_3, self.chkpnt_3, self.metadata_3, {}) + + # call method / assertions + query_1 = {"source": "input"} # search by 1 key + query_2 = { + "step": 1, + "writes": {"foo": "bar"}, + } # search by multiple keys + query_3: dict[str, Any] = {} # search by no keys, return all checkpoints + query_4 = {"source": "update", "step": 1} # no match + + search_results_1 = [c async for c in saver.alist(None, filter=query_1)] + assert len(search_results_1) == 1 + assert search_results_1[0].metadata == self.metadata_1 + + search_results_2 = [c async for c in saver.alist(None, filter=query_2)] + assert len(search_results_2) == 1 + assert search_results_2[0].metadata == self.metadata_2 + + search_results_3 = [c async for c in saver.alist(None, filter=query_3)] + assert len(search_results_3) == 3 + + search_results_4 = [c async for c in saver.alist(None, filter=query_4)] + assert len(search_results_4) == 0 + + # search by config (defaults to checkpoints across all namespaces) + search_results_5 = [ + c + async for c in saver.alist({"configurable": {"thread_id": "thread-2"}}) + ] + assert len(search_results_5) == 2 + assert { + search_results_5[0].config["configurable"]["checkpoint_ns"], + search_results_5[1].config["configurable"]["checkpoint_ns"], + } == {"", "inner"} + + # TODO: test before and limit params + + async def test_null_chars(self) -> None: + async with AsyncDuckDBSaver.from_conn_string(":memory:") as saver: + await saver.setup() + config = await saver.aput( + self.config_1, self.chkpnt_1, {"my_key": "\x00abc"}, {} + ) + assert (await saver.aget_tuple(config)).metadata["my_key"] == "abc" # type: ignore + assert [c async for c in saver.alist(None, filter={"my_key": "abc"})][ + 0 + ].metadata["my_key"] == "abc" diff --git a/libs/checkpoint-duckdb/tests/test_async_store.py b/libs/checkpoint-duckdb/tests/test_async_store.py new file mode 100644 index 000000000..140807c08 --- /dev/null +++ b/libs/checkpoint-duckdb/tests/test_async_store.py @@ -0,0 +1,517 @@ +# type: ignore +import uuid +from datetime import datetime +from typing import Any +from unittest.mock import MagicMock + +import pytest + +from langgraph.store.base import GetOp, Item, ListNamespacesOp, PutOp, SearchOp +from langgraph.store.duckdb import AsyncDuckDBStore + + +class MockCursor: + def __init__(self, fetch_result: Any) -> None: + self.fetch_result = fetch_result + self.execute = MagicMock() + self.fetchall = MagicMock(return_value=self.fetch_result) + + +class MockConnection: + def __init__(self) -> None: + self.cursor = MagicMock() + + +@pytest.fixture +def mock_connection() -> MockConnection: + return MockConnection() + + +@pytest.fixture +async def store(mock_connection: MockConnection) -> AsyncDuckDBStore: + duck_db_store = AsyncDuckDBStore(mock_connection) + await duck_db_store.setup() + return duck_db_store + + +async def test_abatch_order(store: AsyncDuckDBStore) -> None: + mock_connection = store.conn + mock_get_cursor = MockCursor( + [ + ( + "test.foo", + "key1", + '{"data": "value1"}', + datetime.now(), + datetime.now(), + ), + ( + "test.bar", + "key2", + '{"data": "value2"}', + datetime.now(), + datetime.now(), + ), + ] + ) + mock_search_cursor = MockCursor( + [ + ( + "test.foo", + "key1", + '{"data": "value1"}', + datetime.now(), + datetime.now(), + ), + ] + ) + mock_list_namespaces_cursor = MockCursor( + [ + ("test",), + ] + ) + + failures = [] + + def cursor_side_effect() -> Any: + cursor = MagicMock() + + def execute_side_effect(query: str, *params: Any) -> None: + # My super sophisticated database. + if "WHERE prefix = ? AND key" in query: + cursor.fetchall = mock_get_cursor.fetchall + elif "SELECT prefix, key, value" in query: + cursor.fetchall = mock_search_cursor.fetchall + elif "SELECT DISTINCT ON (truncated_prefix)" in query: + cursor.fetchall = mock_list_namespaces_cursor.fetchall + elif "INSERT INTO " in query: + pass + else: + e = ValueError(f"Unmatched query: {query}") + failures.append(e) + raise e + + cursor.execute = MagicMock(side_effect=execute_side_effect) + return cursor + + mock_connection.cursor.side_effect = cursor_side_effect # type: ignore + + ops = [ + GetOp(namespace=("test",), key="key1"), + PutOp(namespace=("test",), key="key2", value={"data": "value2"}), + SearchOp( + namespace_prefix=("test",), filter={"data": "value1"}, limit=10, offset=0 + ), + ListNamespacesOp(match_conditions=None, max_depth=None, limit=10, offset=0), + GetOp(namespace=("test",), key="key3"), + ] + results = await store.abatch(ops) + assert not failures + assert len(results) == 5 + assert isinstance(results[0], Item) + assert isinstance(results[0].value, dict) + assert results[0].value == {"data": "value1"} + assert results[0].key == "key1" + assert results[1] is None + assert isinstance(results[2], list) + assert len(results[2]) == 1 + assert isinstance(results[3], list) + assert results[3] == [("test",)] + assert results[4] is None + + ops_reordered = [ + SearchOp(namespace_prefix=("test",), filter=None, limit=5, offset=0), + GetOp(namespace=("test",), key="key2"), + ListNamespacesOp(match_conditions=None, max_depth=None, limit=5, offset=0), + PutOp(namespace=("test",), key="key3", value={"data": "value3"}), + GetOp(namespace=("test",), key="key1"), + ] + + results_reordered = await store.abatch(ops_reordered) + assert not failures + assert len(results_reordered) == 5 + assert isinstance(results_reordered[0], list) + assert len(results_reordered[0]) == 1 + assert isinstance(results_reordered[1], Item) + assert results_reordered[1].value == {"data": "value2"} + assert results_reordered[1].key == "key2" + assert isinstance(results_reordered[2], list) + assert results_reordered[2] == [("test",)] + assert results_reordered[3] is None + assert isinstance(results_reordered[4], Item) + assert results_reordered[4].value == {"data": "value1"} + assert results_reordered[4].key == "key1" + + +async def test_batch_get_ops(store: AsyncDuckDBStore) -> None: + mock_connection = store.conn + mock_cursor = MockCursor( + [ + ( + "test.foo", + "key1", + '{"data": "value1"}', + datetime.now(), + datetime.now(), + ), + ( + "test.bar", + "key2", + '{"data": "value2"}', + datetime.now(), + datetime.now(), + ), + ] + ) + mock_connection.cursor.return_value = mock_cursor + + ops = [ + GetOp(namespace=("test",), key="key1"), + GetOp(namespace=("test",), key="key2"), + GetOp(namespace=("test",), key="key3"), + ] + + results = await store.abatch(ops) + + assert len(results) == 3 + assert results[0] is not None + assert results[1] is not None + assert results[2] is None + assert results[0].key == "key1" + assert results[1].key == "key2" + + +async def test_batch_put_ops(store: AsyncDuckDBStore) -> None: + mock_connection = store.conn + mock_cursor = MockCursor([]) + mock_connection.cursor.return_value = mock_cursor + + ops = [ + PutOp(namespace=("test",), key="key1", value={"data": "value1"}), + PutOp(namespace=("test",), key="key2", value={"data": "value2"}), + PutOp(namespace=("test",), key="key3", value=None), + ] + + results = await store.abatch(ops) + + assert len(results) == 3 + assert all(result is None for result in results) + assert mock_cursor.execute.call_count == 2 + + +async def test_batch_search_ops(store: AsyncDuckDBStore) -> None: + mock_connection = store.conn + mock_cursor = MockCursor( + [ + ( + "test.foo", + "key1", + '{"data": "value1"}', + datetime.now(), + datetime.now(), + ), + ( + "test.bar", + "key2", + '{"data": "value2"}', + datetime.now(), + datetime.now(), + ), + ] + ) + mock_connection.cursor.return_value = mock_cursor + + ops = [ + SearchOp( + namespace_prefix=("test",), filter={"data": "value1"}, limit=10, offset=0 + ), + SearchOp(namespace_prefix=("test",), filter=None, limit=5, offset=0), + ] + + results = await store.abatch(ops) + + assert len(results) == 2 + assert len(results[0]) == 2 + assert len(results[1]) == 2 + + +async def test_batch_list_namespaces_ops(store: AsyncDuckDBStore) -> None: + mock_connection = store.conn + mock_cursor = MockCursor([("test.namespace1",), ("test.namespace2",)]) + mock_connection.cursor.return_value = mock_cursor + + ops = [ListNamespacesOp(match_conditions=None, max_depth=None, limit=10, offset=0)] + + results = await store.abatch(ops) + + assert len(results) == 1 + assert results[0] == [("test", "namespace1"), ("test", "namespace2")] + + +# The following use the actual DB connection + + +async def test_basic_store_ops() -> None: + async with AsyncDuckDBStore.from_conn_string(":memory:") as store: + await store.setup() + namespace = ("test", "documents") + item_id = "doc1" + item_value = {"title": "Test Document", "content": "Hello, World!"} + + await store.aput(namespace, item_id, item_value) + item = await store.aget(namespace, item_id) + + assert item + assert item.namespace == namespace + assert item.key == item_id + assert item.value == item_value + + updated_value = { + "title": "Updated Test Document", + "content": "Hello, LangGraph!", + } + await store.aput(namespace, item_id, updated_value) + updated_item = await store.aget(namespace, item_id) + + assert updated_item.value == updated_value + assert updated_item.updated_at > item.updated_at + different_namespace = ("test", "other_documents") + item_in_different_namespace = await store.aget(different_namespace, item_id) + assert item_in_different_namespace is None + + new_item_id = "doc2" + new_item_value = {"title": "Another Document", "content": "Greetings!"} + await store.aput(namespace, new_item_id, new_item_value) + + search_results = await store.asearch(["test"], limit=10) + items = search_results + assert len(items) == 2 + assert any(item.key == item_id for item in items) + assert any(item.key == new_item_id for item in items) + + namespaces = await store.alist_namespaces(prefix=["test"]) + assert ("test", "documents") in namespaces + + await store.adelete(namespace, item_id) + await store.adelete(namespace, new_item_id) + deleted_item = await store.aget(namespace, item_id) + assert deleted_item is None + + deleted_item = await store.aget(namespace, new_item_id) + assert deleted_item is None + + empty_search_results = await store.asearch(["test"], limit=10) + assert len(empty_search_results) == 0 + + +async def test_list_namespaces() -> None: + async with AsyncDuckDBStore.from_conn_string(":memory:") as store: + await store.setup() + test_pref = str(uuid.uuid4()) + test_namespaces = [ + (test_pref, "test", "documents", "public", test_pref), + (test_pref, "test", "documents", "private", test_pref), + (test_pref, "test", "images", "public", test_pref), + (test_pref, "test", "images", "private", test_pref), + (test_pref, "prod", "documents", "public", test_pref), + ( + test_pref, + "prod", + "documents", + "some", + "nesting", + "public", + test_pref, + ), + (test_pref, "prod", "documents", "private", test_pref), + ] + + for namespace in test_namespaces: + await store.aput(namespace, "dummy", {"content": "dummy"}) + + prefix_result = await store.alist_namespaces(prefix=[test_pref, "test"]) + assert len(prefix_result) == 4 + assert all([ns[1] == "test" for ns in prefix_result]) + + specific_prefix_result = await store.alist_namespaces( + prefix=[test_pref, "test", "documents"] + ) + assert len(specific_prefix_result) == 2 + assert all([ns[1:3] == ("test", "documents") for ns in specific_prefix_result]) + + suffix_result = await store.alist_namespaces(suffix=["public", test_pref]) + assert len(suffix_result) == 4 + assert all(ns[-2] == "public" for ns in suffix_result) + + prefix_suffix_result = await store.alist_namespaces( + prefix=[test_pref, "test"], suffix=["public", test_pref] + ) + assert len(prefix_suffix_result) == 2 + assert all( + ns[1] == "test" and ns[-2] == "public" for ns in prefix_suffix_result + ) + + wildcard_prefix_result = await store.alist_namespaces( + prefix=[test_pref, "*", "documents"] + ) + assert len(wildcard_prefix_result) == 5 + assert all(ns[2] == "documents" for ns in wildcard_prefix_result) + + wildcard_suffix_result = await store.alist_namespaces( + suffix=["*", "public", test_pref] + ) + assert len(wildcard_suffix_result) == 4 + assert all(ns[-2] == "public" for ns in wildcard_suffix_result) + wildcard_single = await store.alist_namespaces( + suffix=["some", "*", "public", test_pref] + ) + assert len(wildcard_single) == 1 + assert wildcard_single[0] == ( + test_pref, + "prod", + "documents", + "some", + "nesting", + "public", + test_pref, + ) + + max_depth_result = await store.alist_namespaces(max_depth=3) + assert all([len(ns) <= 3 for ns in max_depth_result]) + max_depth_result = await store.alist_namespaces( + max_depth=4, prefix=[test_pref, "*", "documents"] + ) + assert ( + len(set(tuple(res) for res in max_depth_result)) + == len(max_depth_result) + == 5 + ) + + limit_result = await store.alist_namespaces(prefix=[test_pref], limit=3) + assert len(limit_result) == 3 + + offset_result = await store.alist_namespaces(prefix=[test_pref], offset=3) + assert len(offset_result) == len(test_namespaces) - 3 + + empty_prefix_result = await store.alist_namespaces(prefix=[test_pref]) + assert len(empty_prefix_result) == len(test_namespaces) + assert set(tuple(ns) for ns in empty_prefix_result) == set( + tuple(ns) for ns in test_namespaces + ) + + for namespace in test_namespaces: + await store.adelete(namespace, "dummy") + + +async def test_search(): + async with AsyncDuckDBStore.from_conn_string(":memory:") as store: + await store.setup() + test_namespaces = [ + ("test_search", "documents", "user1"), + ("test_search", "documents", "user2"), + ("test_search", "reports", "department1"), + ("test_search", "reports", "department2"), + ] + test_items = [ + {"title": "Doc 1", "author": "John Doe", "tags": ["important"]}, + {"title": "Doc 2", "author": "Jane Smith", "tags": ["draft"]}, + {"title": "Report A", "author": "John Doe", "tags": ["final"]}, + {"title": "Report B", "author": "Alice Johnson", "tags": ["draft"]}, + ] + empty = await store.asearch( + ( + "scoped", + "assistant_id", + "shared", + "6c5356f6-63ab-4158-868d-cd9fd14c736e", + ), + limit=10, + offset=0, + ) + assert len(empty) == 0 + + for namespace, item in zip(test_namespaces, test_items): + await store.aput(namespace, f"item_{namespace[-1]}", item) + + docs_result = await store.asearch(["test_search", "documents"]) + assert len(docs_result) == 2 + assert all([item.namespace[1] == "documents" for item in docs_result]), [ + item.namespace for item in docs_result + ] + + reports_result = await store.asearch(["test_search", "reports"]) + assert len(reports_result) == 2 + assert all(item.namespace[1] == "reports" for item in reports_result) + + limited_result = await store.asearch(["test_search"], limit=2) + assert len(limited_result) == 2 + offset_result = await store.asearch(["test_search"]) + assert len(offset_result) == 4 + + offset_result = await store.asearch(["test_search"], offset=2) + assert len(offset_result) == 2 + assert all(item not in limited_result for item in offset_result) + + john_doe_result = await store.asearch( + ["test_search"], filter={"author": "John Doe"} + ) + assert len(john_doe_result) == 2 + assert all(item.value["author"] == "John Doe" for item in john_doe_result) + + draft_result = await store.asearch(["test_search"], filter={"tags": ["draft"]}) + assert len(draft_result) == 2 + assert all("draft" in item.value["tags"] for item in draft_result) + + page1 = await store.asearch(["test_search"], limit=2, offset=0) + page2 = await store.asearch(["test_search"], limit=2, offset=2) + all_items = page1 + page2 + assert len(all_items) == 4 + assert len(set(item.key for item in all_items)) == 4 + empty = await store.asearch( + ( + "scoped", + "assistant_id", + "shared", + "again", + "maybe", + "some-long", + "6be5cb0e-2eb4-42e6-bb6b-fba3c269db25", + ), + limit=10, + offset=0, + ) + assert len(empty) == 0 + + # Test with a namespace beginning with a number (like a UUID) + uuid_namespace = (str(uuid.uuid4()), "documents") + uuid_item_id = "uuid_doc" + uuid_item_value = { + "title": "UUID Document", + "content": "This document has a UUID namespace.", + } + + # Insert the item with the UUID namespace + await store.aput(uuid_namespace, uuid_item_id, uuid_item_value) + + # Retrieve the item to verify it was stored correctly + retrieved_item = await store.aget(uuid_namespace, uuid_item_id) + assert retrieved_item is not None + assert retrieved_item.namespace == uuid_namespace + assert retrieved_item.key == uuid_item_id + assert retrieved_item.value == uuid_item_value + + # Search for the item using the UUID namespace + search_result = await store.asearch([uuid_namespace[0]]) + assert len(search_result) == 1 + assert search_result[0].key == uuid_item_id + assert search_result[0].value == uuid_item_value + + # Clean up: delete the item with the UUID namespace + await store.adelete(uuid_namespace, uuid_item_id) + + # Verify the item was deleted + deleted_item = await store.aget(uuid_namespace, uuid_item_id) + assert deleted_item is None + + for namespace in test_namespaces: + await store.adelete(namespace, f"item_{namespace[-1]}") diff --git a/libs/checkpoint-duckdb/tests/test_store.py b/libs/checkpoint-duckdb/tests/test_store.py new file mode 100644 index 000000000..47d2d573c --- /dev/null +++ b/libs/checkpoint-duckdb/tests/test_store.py @@ -0,0 +1,457 @@ +# type: ignore +import uuid +from datetime import datetime +from typing import Any +from unittest.mock import MagicMock + +import pytest + +from langgraph.store.base import GetOp, Item, ListNamespacesOp, PutOp, SearchOp +from langgraph.store.duckdb import DuckDBStore + + +class MockCursor: + def __init__(self, fetch_result: Any) -> None: + self.fetch_result = fetch_result + self.execute = MagicMock() + self.fetchall = MagicMock(return_value=self.fetch_result) + + +class MockConnection: + def __init__(self) -> None: + self.cursor = MagicMock() + + +@pytest.fixture +def mock_connection() -> MockConnection: + return MockConnection() + + +@pytest.fixture +def store(mock_connection: MockConnection) -> DuckDBStore: + duck_db_store = DuckDBStore(mock_connection) + duck_db_store.setup() + return duck_db_store + + +def test_batch_order(store: DuckDBStore) -> None: + mock_connection = store.conn + mock_get_cursor = MockCursor( + [ + ( + "test.foo", + "key1", + '{"data": "value1"}', + datetime.now(), + datetime.now(), + ), + ( + "test.bar", + "key2", + '{"data": "value2"}', + datetime.now(), + datetime.now(), + ), + ] + ) + mock_search_cursor = MockCursor( + [ + ( + "test.foo", + "key1", + '{"data": "value1"}', + datetime.now(), + datetime.now(), + ), + ] + ) + mock_list_namespaces_cursor = MockCursor( + [ + ("test",), + ] + ) + + failures = [] + + def cursor_side_effect() -> Any: + cursor = MagicMock() + + def execute_side_effect(query: str, *params: Any) -> None: + # My super sophisticated database. + if "WHERE prefix = ? AND key" in query: + cursor.fetchall = mock_get_cursor.fetchall + elif "SELECT prefix, key, value" in query: + cursor.fetchall = mock_search_cursor.fetchall + elif "SELECT DISTINCT ON (truncated_prefix)" in query: + cursor.fetchall = mock_list_namespaces_cursor.fetchall + elif "INSERT INTO " in query: + pass + else: + e = ValueError(f"Unmatched query: {query}") + failures.append(e) + raise e + + cursor.execute = MagicMock(side_effect=execute_side_effect) + return cursor + + mock_connection.cursor.side_effect = cursor_side_effect + + ops = [ + GetOp(namespace=("test",), key="key1"), + PutOp(namespace=("test",), key="key2", value={"data": "value2"}), + SearchOp( + namespace_prefix=("test",), filter={"data": "value1"}, limit=10, offset=0 + ), + ListNamespacesOp(match_conditions=None, max_depth=None, limit=10, offset=0), + GetOp(namespace=("test",), key="key3"), + ] + results = store.batch(ops) + assert not failures + assert len(results) == 5 + assert isinstance(results[0], Item) + assert isinstance(results[0].value, dict) + assert results[0].value == {"data": "value1"} + assert results[0].key == "key1" + assert results[1] is None + assert isinstance(results[2], list) + assert len(results[2]) == 1 + assert isinstance(results[3], list) + assert results[3] == [("test",)] + assert results[4] is None + + ops_reordered = [ + SearchOp(namespace_prefix=("test",), filter=None, limit=5, offset=0), + GetOp(namespace=("test",), key="key2"), + ListNamespacesOp(match_conditions=None, max_depth=None, limit=5, offset=0), + PutOp(namespace=("test",), key="key3", value={"data": "value3"}), + GetOp(namespace=("test",), key="key1"), + ] + + results_reordered = store.batch(ops_reordered) + assert not failures + assert len(results_reordered) == 5 + assert isinstance(results_reordered[0], list) + assert len(results_reordered[0]) == 1 + assert isinstance(results_reordered[1], Item) + assert results_reordered[1].value == {"data": "value2"} + assert results_reordered[1].key == "key2" + assert isinstance(results_reordered[2], list) + assert results_reordered[2] == [("test",)] + assert results_reordered[3] is None + assert isinstance(results_reordered[4], Item) + assert results_reordered[4].value == {"data": "value1"} + assert results_reordered[4].key == "key1" + + +def test_batch_get_ops(store: DuckDBStore) -> None: + mock_connection = store.conn + mock_cursor = MockCursor( + [ + ( + "test.foo", + "key1", + '{"data": "value1"}', + datetime.now(), + datetime.now(), + ), + ( + "test.bar", + "key2", + '{"data": "value2"}', + datetime.now(), + datetime.now(), + ), + ] + ) + mock_connection.cursor.return_value = mock_cursor + + ops = [ + GetOp(namespace=("test",), key="key1"), + GetOp(namespace=("test",), key="key2"), + GetOp(namespace=("test",), key="key3"), + ] + + results = store.batch(ops) + + assert len(results) == 3 + assert results[0] is not None + assert results[1] is not None + assert results[2] is None + assert results[0].key == "key1" + assert results[1].key == "key2" + + +def test_batch_put_ops(store: DuckDBStore) -> None: + mock_connection = store.conn + mock_cursor = MockCursor([]) + mock_connection.cursor.return_value = mock_cursor + + ops = [ + PutOp(namespace=("test",), key="key1", value={"data": "value1"}), + PutOp(namespace=("test",), key="key2", value={"data": "value2"}), + PutOp(namespace=("test",), key="key3", value=None), + ] + + results = store.batch(ops) + + assert len(results) == 3 + assert all(result is None for result in results) + assert mock_cursor.execute.call_count == 2 + + +def test_batch_search_ops(store: DuckDBStore) -> None: + mock_connection = store.conn + mock_cursor = MockCursor( + [ + ( + "test.foo", + "key1", + '{"data": "value1"}', + datetime.now(), + datetime.now(), + ), + ( + "test.bar", + "key2", + '{"data": "value2"}', + datetime.now(), + datetime.now(), + ), + ] + ) + mock_connection.cursor.return_value = mock_cursor + + ops = [ + SearchOp( + namespace_prefix=("test",), filter={"data": "value1"}, limit=10, offset=0 + ), + SearchOp(namespace_prefix=("test",), filter=None, limit=5, offset=0), + ] + + results = store.batch(ops) + + assert len(results) == 2 + assert len(results[0]) == 2 + assert len(results[1]) == 2 + + +def test_batch_list_namespaces_ops(store: DuckDBStore) -> None: + mock_connection = store.conn + mock_cursor = MockCursor([("test.namespace1",), ("test.namespace2",)]) + mock_connection.cursor.return_value = mock_cursor + + ops = [ListNamespacesOp(match_conditions=None, max_depth=None, limit=10, offset=0)] + + results = store.batch(ops) + + assert len(results) == 1 + assert results[0] == [("test", "namespace1"), ("test", "namespace2")] + + +def test_basic_store_ops() -> None: + with DuckDBStore.from_conn_string(":memory:") as store: + store.setup() + namespace = ("test", "documents") + item_id = "doc1" + item_value = {"title": "Test Document", "content": "Hello, World!"} + + store.put(namespace, item_id, item_value) + item = store.get(namespace, item_id) + + assert item + assert item.namespace == namespace + assert item.key == item_id + assert item.value == item_value + + updated_value = { + "title": "Updated Test Document", + "content": "Hello, LangGraph!", + } + store.put(namespace, item_id, updated_value) + updated_item = store.get(namespace, item_id) + + assert updated_item.value == updated_value + assert updated_item.updated_at > item.updated_at + different_namespace = ("test", "other_documents") + item_in_different_namespace = store.get(different_namespace, item_id) + assert item_in_different_namespace is None + + new_item_id = "doc2" + new_item_value = {"title": "Another Document", "content": "Greetings!"} + store.put(namespace, new_item_id, new_item_value) + + search_results = store.search(["test"], limit=10) + items = search_results + assert len(items) == 2 + assert any(item.key == item_id for item in items) + assert any(item.key == new_item_id for item in items) + + namespaces = store.list_namespaces(prefix=["test"]) + assert ("test", "documents") in namespaces + + store.delete(namespace, item_id) + store.delete(namespace, new_item_id) + deleted_item = store.get(namespace, item_id) + assert deleted_item is None + + deleted_item = store.get(namespace, new_item_id) + assert deleted_item is None + + empty_search_results = store.search(["test"], limit=10) + assert len(empty_search_results) == 0 + + +def test_list_namespaces() -> None: + with DuckDBStore.from_conn_string(":memory:") as store: + store.setup() + test_pref = str(uuid.uuid4()) + test_namespaces = [ + (test_pref, "test", "documents", "public", test_pref), + (test_pref, "test", "documents", "private", test_pref), + (test_pref, "test", "images", "public", test_pref), + (test_pref, "test", "images", "private", test_pref), + (test_pref, "prod", "documents", "public", test_pref), + ( + test_pref, + "prod", + "documents", + "some", + "nesting", + "public", + test_pref, + ), + (test_pref, "prod", "documents", "private", test_pref), + ] + + for namespace in test_namespaces: + store.put(namespace, "dummy", {"content": "dummy"}) + + prefix_result = store.list_namespaces(prefix=[test_pref, "test"]) + assert len(prefix_result) == 4 + assert all([ns[1] == "test" for ns in prefix_result]) + + specific_prefix_result = store.list_namespaces( + prefix=[test_pref, "test", "documents"] + ) + assert len(specific_prefix_result) == 2 + assert all([ns[1:3] == ("test", "documents") for ns in specific_prefix_result]) + + suffix_result = store.list_namespaces(suffix=["public", test_pref]) + assert len(suffix_result) == 4 + assert all(ns[-2] == "public" for ns in suffix_result) + + prefix_suffix_result = store.list_namespaces( + prefix=[test_pref, "test"], suffix=["public", test_pref] + ) + assert len(prefix_suffix_result) == 2 + assert all( + ns[1] == "test" and ns[-2] == "public" for ns in prefix_suffix_result + ) + + wildcard_prefix_result = store.list_namespaces( + prefix=[test_pref, "*", "documents"] + ) + assert len(wildcard_prefix_result) == 5 + assert all(ns[2] == "documents" for ns in wildcard_prefix_result) + + wildcard_suffix_result = store.list_namespaces( + suffix=["*", "public", test_pref] + ) + assert len(wildcard_suffix_result) == 4 + assert all(ns[-2] == "public" for ns in wildcard_suffix_result) + wildcard_single = store.list_namespaces( + suffix=["some", "*", "public", test_pref] + ) + assert len(wildcard_single) == 1 + assert wildcard_single[0] == ( + test_pref, + "prod", + "documents", + "some", + "nesting", + "public", + test_pref, + ) + + max_depth_result = store.list_namespaces(max_depth=3) + assert all([len(ns) <= 3 for ns in max_depth_result]) + + max_depth_result = store.list_namespaces( + max_depth=4, prefix=[test_pref, "*", "documents"] + ) + assert ( + len(set(tuple(res) for res in max_depth_result)) + == len(max_depth_result) + == 5 + ) + + limit_result = store.list_namespaces(prefix=[test_pref], limit=3) + assert len(limit_result) == 3 + + offset_result = store.list_namespaces(prefix=[test_pref], offset=3) + assert len(offset_result) == len(test_namespaces) - 3 + + empty_prefix_result = store.list_namespaces(prefix=[test_pref]) + assert len(empty_prefix_result) == len(test_namespaces) + assert set(tuple(ns) for ns in empty_prefix_result) == set( + tuple(ns) for ns in test_namespaces + ) + + for namespace in test_namespaces: + store.delete(namespace, "dummy") + + +def test_search(): + with DuckDBStore.from_conn_string(":memory:") as store: + store.setup() + test_namespaces = [ + ("test_search", "documents", "user1"), + ("test_search", "documents", "user2"), + ("test_search", "reports", "department1"), + ("test_search", "reports", "department2"), + ] + test_items = [ + {"title": "Doc 1", "author": "John Doe", "tags": ["important"]}, + {"title": "Doc 2", "author": "Jane Smith", "tags": ["draft"]}, + {"title": "Report A", "author": "John Doe", "tags": ["final"]}, + {"title": "Report B", "author": "Alice Johnson", "tags": ["draft"]}, + ] + + for namespace, item in zip(test_namespaces, test_items): + store.put(namespace, f"item_{namespace[-1]}", item) + + docs_result = store.search(["test_search", "documents"]) + assert len(docs_result) == 2 + assert all( + [item.namespace[1] == "documents" for item in docs_result] + ), docs_result + + reports_result = store.search(["test_search", "reports"]) + assert len(reports_result) == 2 + assert all(item.namespace[1] == "reports" for item in reports_result) + + limited_result = store.search(["test_search"], limit=2) + assert len(limited_result) == 2 + offset_result = store.search(["test_search"]) + assert len(offset_result) == 4 + + offset_result = store.search(["test_search"], offset=2) + assert len(offset_result) == 2 + assert all(item not in limited_result for item in offset_result) + + john_doe_result = store.search(["test_search"], filter={"author": "John Doe"}) + assert len(john_doe_result) == 2 + assert all(item.value["author"] == "John Doe" for item in john_doe_result) + + draft_result = store.search(["test_search"], filter={"tags": ["draft"]}) + assert len(draft_result) == 2 + assert all("draft" in item.value["tags"] for item in draft_result) + + page1 = store.search(["test_search"], limit=2, offset=0) + page2 = store.search(["test_search"], limit=2, offset=2) + all_items = page1 + page2 + assert len(all_items) == 4 + assert len(set(item.key for item in all_items)) == 4 + + for namespace in test_namespaces: + store.delete(namespace, f"item_{namespace[-1]}") diff --git a/libs/checkpoint-duckdb/tests/test_sync.py b/libs/checkpoint-duckdb/tests/test_sync.py new file mode 100644 index 000000000..c63e32927 --- /dev/null +++ b/libs/checkpoint-duckdb/tests/test_sync.py @@ -0,0 +1,111 @@ +from typing import Any + +import pytest +from langchain_core.runnables import RunnableConfig + +from langgraph.checkpoint.base import ( + Checkpoint, + CheckpointMetadata, + create_checkpoint, + empty_checkpoint, +) +from langgraph.checkpoint.duckdb import DuckDBSaver + + +class TestDuckDBSaver: + @pytest.fixture(autouse=True) + def setup(self) -> None: + # objects for test setup + self.config_1: RunnableConfig = { + "configurable": { + "thread_id": "thread-1", + # for backwards compatibility testing + "thread_ts": "1", + "checkpoint_ns": "", + } + } + self.config_2: RunnableConfig = { + "configurable": { + "thread_id": "thread-2", + "checkpoint_id": "2", + "checkpoint_ns": "", + } + } + self.config_3: RunnableConfig = { + "configurable": { + "thread_id": "thread-2", + "checkpoint_id": "2-inner", + "checkpoint_ns": "inner", + } + } + + self.chkpnt_1: Checkpoint = empty_checkpoint() + self.chkpnt_2: Checkpoint = create_checkpoint(self.chkpnt_1, {}, 1) + self.chkpnt_3: Checkpoint = empty_checkpoint() + + self.metadata_1: CheckpointMetadata = { + "source": "input", + "step": 2, + "writes": {}, + "score": 1, + } + self.metadata_2: CheckpointMetadata = { + "source": "loop", + "step": 1, + "writes": {"foo": "bar"}, + "score": None, + } + self.metadata_3: CheckpointMetadata = {} + + def test_search(self) -> None: + with DuckDBSaver.from_conn_string(":memory:") as saver: + saver.setup() + # save checkpoints + saver.put(self.config_1, self.chkpnt_1, self.metadata_1, {}) + saver.put(self.config_2, self.chkpnt_2, self.metadata_2, {}) + saver.put(self.config_3, self.chkpnt_3, self.metadata_3, {}) + + # call method / assertions + query_1 = {"source": "input"} # search by 1 key + query_2 = { + "step": 1, + "writes": {"foo": "bar"}, + } # search by multiple keys + query_3: dict[str, Any] = {} # search by no keys, return all checkpoints + query_4 = {"source": "update", "step": 1} # no match + + search_results_1 = list(saver.list(None, filter=query_1)) + assert len(search_results_1) == 1 + assert search_results_1[0].metadata == self.metadata_1 + + search_results_2 = list(saver.list(None, filter=query_2)) + assert len(search_results_2) == 1 + assert search_results_2[0].metadata == self.metadata_2 + + search_results_3 = list(saver.list(None, filter=query_3)) + assert len(search_results_3) == 3 + + search_results_4 = list(saver.list(None, filter=query_4)) + assert len(search_results_4) == 0 + + # search by config (defaults to checkpoints across all namespaces) + search_results_5 = list( + saver.list({"configurable": {"thread_id": "thread-2"}}) + ) + assert len(search_results_5) == 2 + assert { + search_results_5[0].config["configurable"]["checkpoint_ns"], + search_results_5[1].config["configurable"]["checkpoint_ns"], + } == {"", "inner"} + + # TODO: test before and limit params + + def test_null_chars(self) -> None: + with DuckDBSaver.from_conn_string(":memory:") as saver: + saver.setup() + config = saver.put(self.config_1, self.chkpnt_1, {"my_key": "\x00abc"}, {}) + assert saver.get_tuple(config).metadata["my_key"] == "abc" # type: ignore + assert ( + list(saver.list(None, filter={"my_key": "abc"}))[0].metadata["my_key"] # type: ignore + == "abc" + ) diff --git a/libs/checkpoint-postgres/langgraph/checkpoint/postgres/__init__.py b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/__init__.py index 04e3747d2..b8138a945 100644 --- a/libs/checkpoint-postgres/langgraph/checkpoint/postgres/__init__.py +++ b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/__init__.py @@ -3,7 +3,7 @@ from contextlib import contextmanager from typing import Any, Iterator, Optional, Sequence, Union from langchain_core.runnables import RunnableConfig -from psycopg import Connection, Cursor, Pipeline +from psycopg import Capabilities, Connection, Cursor, Pipeline from psycopg.errors import UndefinedTable from psycopg.rows import DictRow, dict_row from psycopg.types.json import Jsonb @@ -52,6 +52,7 @@ class PostgresSaver(BasePostgresSaver): self.conn = conn self.pipe = pipe self.lock = threading.Lock() + self.supports_pipeline = Capabilities().has_pipeline() @classmethod @contextmanager @@ -287,7 +288,7 @@ class PostgresSaver(BasePostgresSaver): >>> DB_URI = "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable" >>> with PostgresSaver.from_conn_string(DB_URI) as memory: >>> config = {"configurable": {"thread_id": "1", "checkpoint_ns": ""}} - >>> checkpoint = {"ts": "2024-05-04T06:32:42.235444+00:00", "id": "1ef4f797-8335-6428-8001-8a1503f9b875", "data": {"key": "value"}} + >>> checkpoint = {"ts": "2024-05-04T06:32:42.235444+00:00", "id": "1ef4f797-8335-6428-8001-8a1503f9b875", "channel_values": {"key": "value"}} >>> saved_config = memory.put(config, checkpoint, {"source": "input", "step": 1, "writes": {"key": "value"}}, {}) >>> print(saved_config) {'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef4f797-8335-6428-8001-8a1503f9b875'}} @@ -365,6 +366,13 @@ class PostgresSaver(BasePostgresSaver): @contextmanager def _cursor(self, *, pipeline: bool = False) -> Iterator[Cursor[DictRow]]: + """Create a database cursor as a context manager. + + Args: + pipeline (bool): whether to use pipeline for the DB operations inside the context manager. + Will be applied regardless of whether the PostgresSaver instance was initialized with a pipeline. + If pipeline mode is not supported, will fall back to using transaction context manager. + """ with _get_connection(self.conn) as conn: if self.pipe: # a connection in pipeline mode can be used concurrently @@ -379,10 +387,17 @@ class PostgresSaver(BasePostgresSaver): elif pipeline: # a connection not in pipeline mode can only be used by one # thread/coroutine at a time, so we acquire a lock - with self.lock, conn.pipeline(), conn.cursor( - binary=True, row_factory=dict_row - ) as cur: - yield cur + if self.supports_pipeline: + with self.lock, conn.pipeline(), conn.cursor( + binary=True, row_factory=dict_row + ) as cur: + yield cur + else: + # Use connection's transaction context manager when pipeline mode not supported + with self.lock, conn.transaction(), conn.cursor( + binary=True, row_factory=dict_row + ) as cur: + yield cur else: with self.lock, conn.cursor(binary=True, row_factory=dict_row) as cur: yield cur diff --git a/libs/checkpoint-postgres/langgraph/checkpoint/postgres/aio.py b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/aio.py index 59ee7cbf9..5b67e4ca9 100644 --- a/libs/checkpoint-postgres/langgraph/checkpoint/postgres/aio.py +++ b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/aio.py @@ -3,7 +3,7 @@ from contextlib import asynccontextmanager from typing import Any, AsyncIterator, Iterator, Optional, Sequence, Union from langchain_core.runnables import RunnableConfig -from psycopg import AsyncConnection, AsyncCursor, AsyncPipeline +from psycopg import AsyncConnection, AsyncCursor, AsyncPipeline, Capabilities from psycopg.errors import UndefinedTable from psycopg.rows import DictRow, dict_row from psycopg.types.json import Jsonb @@ -55,6 +55,7 @@ class AsyncPostgresSaver(BasePostgresSaver): self.pipe = pipe self.lock = asyncio.Lock() self.loop = asyncio.get_running_loop() + self.supports_pipeline = Capabilities().has_pipeline() @classmethod @asynccontextmanager @@ -323,6 +324,13 @@ class AsyncPostgresSaver(BasePostgresSaver): async def _cursor( self, *, pipeline: bool = False ) -> AsyncIterator[AsyncCursor[DictRow]]: + """Create a database cursor as a context manager. + + Args: + pipeline (bool): whether to use pipeline for the DB operations inside the context manager. + Will be applied regardless of whether the AsyncPostgresSaver instance was initialized with a pipeline. + If pipeline mode is not supported, will fall back to using transaction context manager. + """ async with _get_connection(self.conn) as conn: if self.pipe: # a connection in pipeline mode can be used concurrently @@ -337,10 +345,17 @@ class AsyncPostgresSaver(BasePostgresSaver): elif pipeline: # a connection not in pipeline mode can only be used by one # thread/coroutine at a time, so we acquire a lock - async with self.lock, conn.pipeline(), conn.cursor( - binary=True, row_factory=dict_row - ) as cur: - yield cur + if self.supports_pipeline: + async with self.lock, conn.pipeline(), conn.cursor( + binary=True, row_factory=dict_row + ) as cur: + yield cur + else: + # Use connection's transaction context manager when pipeline mode not supported + async with self.lock, conn.transaction(), conn.cursor( + binary=True, row_factory=dict_row + ) as cur: + yield cur else: async with self.lock, conn.cursor( binary=True, row_factory=dict_row diff --git a/libs/checkpoint-postgres/langgraph/checkpoint/postgres/base.py b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/base.py index 535232370..ae65cab68 100644 --- a/libs/checkpoint-postgres/langgraph/checkpoint/postgres/base.py +++ b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/base.py @@ -84,7 +84,7 @@ select and cw.checkpoint_id = checkpoints.checkpoint_id ) as pending_writes, ( - select array_agg(array[cw.type::bytea, cw.blob] order by cw.idx) + select array_agg(array[cw.type::bytea, cw.blob] order by cw.task_id, cw.idx) from checkpoint_writes cw where cw.thread_id = checkpoints.thread_id and cw.checkpoint_ns = checkpoints.checkpoint_ns @@ -133,6 +133,7 @@ class BasePostgresSaver(BaseCheckpointSaver[str]): INSERT_CHECKPOINT_WRITES_SQL = INSERT_CHECKPOINT_WRITES_SQL jsonplus_serde = JsonPlusSerializer() + supports_pipeline: bool def _load_checkpoint( self, @@ -249,7 +250,7 @@ class BasePostgresSaver(BaseCheckpointSaver[str]): filter: MetadataInput, before: Optional[RunnableConfig] = None, ) -> Tuple[str, List[Any]]: - """Return WHERE clause predicates for alist() given config, filter, cursor. + """Return WHERE clause predicates for alist() given config, filter, before. This method returns a tuple of a string and a tuple of values. The string is the parametered WHERE clause predicate (including the WHERE keyword): diff --git a/libs/checkpoint/langgraph/checkpoint/py.typed b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/py.typed similarity index 100% rename from libs/checkpoint/langgraph/checkpoint/py.typed rename to libs/checkpoint-postgres/langgraph/checkpoint/postgres/py.typed diff --git a/libs/checkpoint-postgres/langgraph/store/postgres/aio.py b/libs/checkpoint-postgres/langgraph/store/postgres/aio.py index 71d497012..dda7321d0 100644 --- a/libs/checkpoint-postgres/langgraph/store/postgres/aio.py +++ b/libs/checkpoint-postgres/langgraph/store/postgres/aio.py @@ -44,7 +44,6 @@ class AsyncPostgresStore(AsyncBatchedBaseStore, BasePostgresStore[AsyncConnectio super().__init__() self._deserializer = deserializer self.conn = conn - self.conn = conn self.loop = asyncio.get_running_loop() async def abatch(self, ops: Iterable[Op]) -> list[Result]: diff --git a/libs/checkpoint/langgraph/store/py.typed b/libs/checkpoint-postgres/langgraph/store/postgres/py.typed similarity index 100% rename from libs/checkpoint/langgraph/store/py.typed rename to libs/checkpoint-postgres/langgraph/store/postgres/py.typed diff --git a/libs/checkpoint-postgres/poetry.lock b/libs/checkpoint-postgres/poetry.lock index 69c267371..b57babc0c 100644 --- a/libs/checkpoint-postgres/poetry.lock +++ b/libs/checkpoint-postgres/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "annotated-types" @@ -324,7 +324,7 @@ typing-extensions = ">=4.7" [[package]] name = "langgraph-checkpoint" -version = "2.0.0" +version = "2.0.2" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = "^3.9.0,<4.0" @@ -1116,4 +1116,4 @@ watchmedo = ["PyYAML (>=3.10)"] [metadata] lock-version = "2.0" python-versions = "^3.9.0,<4.0" -content-hash = "8f763cd1727287f8c8b5ad2b4d8df00fb446e68d0cd4e88c278e4007969b83fd" +content-hash = "6bd85ce8ee1192995c1ff03d5fa65af8ee7872214d71b84559a6192cadf82be6" diff --git a/libs/checkpoint-postgres/pyproject.toml b/libs/checkpoint-postgres/pyproject.toml index d687b9a42..90a68eabf 100644 --- a/libs/checkpoint-postgres/pyproject.toml +++ b/libs/checkpoint-postgres/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langgraph-checkpoint-postgres" -version = "2.0.1" +version = "2.0.3" description = "Library with a Postgres implementation of LangGraph checkpoint saver." authors = [] license = "MIT" @@ -10,7 +10,7 @@ packages = [{ include = "langgraph" }] [tool.poetry.dependencies] python = "^3.9.0,<4.0" -langgraph-checkpoint = "^2.0.0" +langgraph-checkpoint = "^2.0.2" orjson = ">=3.10.1" psycopg = "^3.0.0" psycopg-pool = "^3.0.0" diff --git a/libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/__init__.py b/libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/__init__.py index 2c6ef4a31..b552a75f4 100644 --- a/libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/__init__.py +++ b/libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/__init__.py @@ -389,7 +389,7 @@ class SqliteSaver(BaseCheckpointSaver[str]): >>> from langgraph.checkpoint.sqlite import SqliteSaver >>> with SqliteSaver.from_conn_string(":memory:") as memory: >>> config = {"configurable": {"thread_id": "1", "checkpoint_ns": ""}} - >>> checkpoint = {"ts": "2024-05-04T06:32:42.235444+00:00", "id": "1ef4f797-8335-6428-8001-8a1503f9b875", "data": {"key": "value"}} + >>> checkpoint = {"ts": "2024-05-04T06:32:42.235444+00:00", "id": "1ef4f797-8335-6428-8001-8a1503f9b875", "channel_values": {"key": "value"}} >>> saved_config = memory.put(config, checkpoint, {"source": "input", "step": 1, "writes": {"key": "value"}}, {}) >>> print(saved_config) {'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef4f797-8335-6428-8001-8a1503f9b875'}} diff --git a/libs/scheduler-kafka/langgraph/scheduler/py.typed b/libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/py.typed similarity index 100% rename from libs/scheduler-kafka/langgraph/scheduler/py.typed rename to libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/py.typed diff --git a/libs/checkpoint-sqlite/poetry.lock b/libs/checkpoint-sqlite/poetry.lock index e6e0aac17..f90145f35 100644 --- a/libs/checkpoint-sqlite/poetry.lock +++ b/libs/checkpoint-sqlite/poetry.lock @@ -332,7 +332,7 @@ typing-extensions = ">=4.7" [[package]] name = "langgraph-checkpoint" -version = "2.0.0" +version = "2.0.2" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = "^3.9.0,<4.0" @@ -1001,4 +1001,4 @@ watchmedo = ["PyYAML (>=3.10)"] [metadata] lock-version = "2.0" python-versions = "^3.9.0" -content-hash = "e0091cc2deab4de99a6bc4eb262b0040b771a9659dd3638ac1c4a225a1f11dc2" +content-hash = "927b49b9ba72a301980237d7adc2e73cdacfbe127a174c7488136a9af9372796" diff --git a/libs/checkpoint-sqlite/pyproject.toml b/libs/checkpoint-sqlite/pyproject.toml index 19cbf643e..2e12cb638 100644 --- a/libs/checkpoint-sqlite/pyproject.toml +++ b/libs/checkpoint-sqlite/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langgraph-checkpoint-sqlite" -version = "2.0.0" +version = "2.0.1" description = "Library with a SQLite implementation of LangGraph checkpoint saver." authors = [] license = "MIT" @@ -10,7 +10,7 @@ packages = [{ include = "langgraph" }] [tool.poetry.dependencies] python = "^3.9.0" -langgraph-checkpoint = "^2.0.0" +langgraph-checkpoint = "^2.0.2" aiosqlite = "^0.20.0" [tool.poetry.group.dev.dependencies] diff --git a/libs/checkpoint/langgraph/checkpoint/base/__init__.py b/libs/checkpoint/langgraph/checkpoint/base/__init__.py index 93d510daa..6805ada0e 100644 --- a/libs/checkpoint/langgraph/checkpoint/base/__init__.py +++ b/libs/checkpoint/langgraph/checkpoint/base/__init__.py @@ -24,6 +24,8 @@ from langgraph.checkpoint.serde.base import SerializerProtocol, maybe_add_typed_ from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer from langgraph.checkpoint.serde.types import ( ERROR, + INTERRUPT, + RESUME, SCHEDULED, ChannelProtocol, SendProtocol, @@ -37,12 +39,13 @@ PendingWrite = Tuple[str, str, Any] class CheckpointMetadata(TypedDict, total=False): """Metadata associated with a checkpoint.""" - source: Literal["input", "loop", "update"] + source: Literal["input", "loop", "update", "fork"] """The source of the checkpoint. - "input": The checkpoint was created from an input to invoke/stream/batch. - "loop": The checkpoint was created from inside the pregel loop. - "update": The checkpoint was created from a manual state update. + - "fork": The checkpoint was created as a copy of another checkpoint. """ step: int """The step number of the checkpoint. @@ -449,4 +452,4 @@ Special writes (e.g. errors) map to negative indices, to avoid those writes from conflicting with regular writes. Each Checkpointer implementation should use this mapping in put_writes. """ -WRITES_IDX_MAP = {ERROR: -1, SCHEDULED: -2} +WRITES_IDX_MAP = {ERROR: -1, SCHEDULED: -2, INTERRUPT: -3, RESUME: -4} diff --git a/libs/checkpoint/langgraph/checkpoint/base/py.typed b/libs/checkpoint/langgraph/checkpoint/base/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/libs/checkpoint/langgraph/checkpoint/memory/__init__.py b/libs/checkpoint/langgraph/checkpoint/memory/__init__.py index aea9069b9..e30c082c7 100644 --- a/libs/checkpoint/langgraph/checkpoint/memory/__init__.py +++ b/libs/checkpoint/langgraph/checkpoint/memory/__init__.py @@ -1,10 +1,14 @@ import asyncio +import logging +import os +import pickle import random +import shutil from collections import defaultdict -from contextlib import AbstractAsyncContextManager, AbstractContextManager +from contextlib import AbstractAsyncContextManager, AbstractContextManager, ExitStack from functools import partial from types import TracebackType -from typing import Any, AsyncIterator, Dict, Iterator, Optional, Sequence, Tuple +from typing import Any, AsyncIterator, Dict, Iterator, Optional, Sequence, Tuple, Type from langchain_core.runnables import RunnableConfig @@ -20,6 +24,8 @@ from langgraph.checkpoint.base import ( ) from langgraph.checkpoint.serde.types import TASKS, ChannelProtocol +logger = logging.getLogger(__name__) + class MemorySaver( BaseCheckpointSaver[str], AbstractContextManager, AbstractAsyncContextManager @@ -68,13 +74,18 @@ class MemorySaver( self, *, serde: Optional[SerializerProtocol] = None, + factory: Type[defaultdict] = defaultdict, ) -> None: super().__init__(serde=serde) - self.storage = defaultdict(lambda: defaultdict(dict)) - self.writes = defaultdict(dict) + self.storage = factory(lambda: defaultdict(dict)) + self.writes = factory(dict) + self.stack = ExitStack() + if factory is not defaultdict: + self.stack.enter_context(self.storage) # type: ignore[arg-type] + self.stack.enter_context(self.writes) # type: ignore[arg-type] def __enter__(self) -> "MemorySaver": - return self + return self.stack.__enter__() def __exit__( self, @@ -82,10 +93,10 @@ class MemorySaver( exc_value: Optional[BaseException], traceback: Optional[TracebackType], ) -> Optional[bool]: - return + return self.stack.__exit__(exc_type, exc_value, traceback) async def __aenter__(self) -> "MemorySaver": - return self + return self.stack.__enter__() async def __aexit__( self, @@ -93,7 +104,7 @@ class MemorySaver( __exc_value: Optional[BaseException], __traceback: Optional[TracebackType], ) -> Optional[bool]: - return + return self.stack.__exit__(__exc_type, __exc_value, __traceback) def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]: """Get a checkpoint tuple from the in-memory storage. @@ -361,11 +372,15 @@ class MemorySaver( RunnableConfig: The updated config containing the saved writes' timestamp. """ thread_id = config["configurable"]["thread_id"] - checkpoint_ns = config["configurable"]["checkpoint_ns"] + checkpoint_ns = config["configurable"].get("checkpoint_ns", "") checkpoint_id = config["configurable"]["checkpoint_id"] outer_key = (thread_id, checkpoint_ns, checkpoint_id) + outer_writes_ = self.writes.get(outer_key) for idx, (c, v) in enumerate(writes): inner_key = (task_id, WRITES_IDX_MAP.get(c, idx)) + if inner_key[1] >= 0 and outer_writes_ and inner_key in outer_writes_: + continue + self.writes[outer_key][inner_key] = (task_id, c, self.serde.dumps_typed(v)) async def aget_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]: @@ -474,3 +489,76 @@ class MemorySaver( next_v = current_v + 1 next_h = random.random() return f"{next_v:032}.{next_h:016}" + + +class PersistentDict(defaultdict): + """Persistent dictionary with an API compatible with shelve and anydbm. + + The dict is kept in memory, so the dictionary operations run as fast as + a regular dictionary. + + Write to disk is delayed until close or sync (similar to gdbm's fast mode). + + Input file format is automatically discovered. + Output file format is selectable between pickle, json, and csv. + All three serialization formats are backed by fast C implementations. + + Adapted from https://code.activestate.com/recipes/576642-persistent-dict-with-multiple-standard-file-format/ + + """ + + def __init__(self, *args: Any, filename: str, **kwds: Any) -> None: + self.flag = "c" # r=readonly, c=create, or n=new + self.mode = None # None or an octal triple like 0644 + self.format = "pickle" # 'csv', 'json', or 'pickle' + self.filename = filename + super().__init__(*args, **kwds) + + def sync(self) -> None: + "Write dict to disk" + if self.flag == "r": + return + tempname = self.filename + ".tmp" + fileobj = open(tempname, "wb" if self.format == "pickle" else "w") + try: + self.dump(fileobj) + except Exception: + os.remove(tempname) + raise + finally: + fileobj.close() + shutil.move(tempname, self.filename) # atomic commit + if self.mode is not None: + os.chmod(self.filename, self.mode) + + def close(self) -> None: + self.sync() + self.clear() + + def __enter__(self) -> "PersistentDict": + return self + + def __exit__(self, *exc_info: Any) -> None: + self.close() + + def dump(self, fileobj: Any) -> None: + if self.format == "pickle": + pickle.dump(dict(self), fileobj, 2) + else: + raise NotImplementedError("Unknown format: " + repr(self.format)) + + def load(self) -> None: + # try formats from most restrictive to least restrictive + if self.flag == "n": + return + with open(self.filename, "rb" if self.format == "pickle" else "r") as fileobj: + for loader in (pickle.load,): + fileobj.seek(0) + try: + return self.update(loader(fileobj)) + except EOFError: + return + except Exception: + logging.error(f"Failed to load file: {fileobj.name}") + raise + raise ValueError("File not in a supported f ormat") diff --git a/libs/checkpoint/langgraph/checkpoint/memory/py.typed b/libs/checkpoint/langgraph/checkpoint/memory/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/libs/checkpoint/langgraph/checkpoint/serde/py.typed b/libs/checkpoint/langgraph/checkpoint/serde/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/libs/checkpoint/langgraph/checkpoint/serde/types.py b/libs/checkpoint/langgraph/checkpoint/serde/types.py index 43a5bf878..1df967b5f 100644 --- a/libs/checkpoint/langgraph/checkpoint/serde/types.py +++ b/libs/checkpoint/langgraph/checkpoint/serde/types.py @@ -11,6 +11,8 @@ from typing_extensions import Self ERROR = "__error__" SCHEDULED = "__scheduled__" +INTERRUPT = "__interrupt__" +RESUME = "__resume__" TASKS = "__pregel_tasks" Value = TypeVar("Value", covariant=True) diff --git a/libs/checkpoint/langgraph/store/base/py.typed b/libs/checkpoint/langgraph/store/base/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/libs/checkpoint/langgraph/store/memory/py.typed b/libs/checkpoint/langgraph/store/memory/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/libs/checkpoint/pyproject.toml b/libs/checkpoint/pyproject.toml index 376e63422..deb7de5c4 100644 --- a/libs/checkpoint/pyproject.toml +++ b/libs/checkpoint/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langgraph-checkpoint" -version = "2.0.1" +version = "2.0.5" description = "Library with base interfaces for LangGraph checkpoint savers." authors = [] license = "MIT" diff --git a/libs/checkpoint/tests/test_store.py b/libs/checkpoint/tests/test_store.py index d9fbc5084..9d06281d0 100644 --- a/libs/checkpoint/tests/test_store.py +++ b/libs/checkpoint/tests/test_store.py @@ -314,7 +314,7 @@ async def test_cannot_put_empty_namespace() -> None: assert store.get(("langgraph", "foo"), "bar") is None class MockAsyncBatchedStore(AsyncBatchedBaseStore): - def __init__(self): + def __init__(self) -> None: super().__init__() self._store = InMemoryStore() @@ -340,13 +340,17 @@ async def test_cannot_put_empty_namespace() -> None: await async_store.aput(("langgraph", "foo"), "bar", doc) await async_store.aput(("foo", "langgraph", "foo"), "bar", doc) - assert (await async_store.aget(("foo", "langgraph", "foo"), "bar")).value == doc + val = await async_store.aget(("foo", "langgraph", "foo"), "bar") + assert val is not None + assert val.value == doc assert (await async_store.asearch(("foo", "langgraph", "foo")))[0].value == doc await async_store.adelete(("foo", "langgraph", "foo"), "bar") assert (await async_store.aget(("foo", "langgraph", "foo"), "bar")) is None await async_store.abatch([PutOp(("valid", "namespace"), "key", doc)]) - assert (await async_store.aget(("valid", "namespace"), "key")).value == doc + val = await async_store.aget(("valid", "namespace"), "key") + assert val is not None + assert val.value == doc assert (await async_store.asearch(("valid", "namespace")))[0].value == doc await async_store.adelete(("valid", "namespace"), "key") assert (await async_store.aget(("valid", "namespace"), "key")) is None diff --git a/libs/cli/Makefile b/libs/cli/Makefile index 424946fdf..22506684d 100644 --- a/libs/cli/Makefile +++ b/libs/cli/Makefile @@ -1,11 +1,13 @@ -.PHONY: test lint format +.PHONY: test lint format test-integration ###################### # TESTING AND COVERAGE ###################### test: - poetry run pytest tests + poetry run pytest tests/unit_tests +test-integration: + poetry run pytest tests/integration_tests ###################### # LINTING AND FORMATTING diff --git a/libs/cli/README.md b/libs/cli/README.md index 511e1b6fe..595372ef7 100644 --- a/libs/cli/README.md +++ b/libs/cli/README.md @@ -1,10 +1,105 @@ -# langchain-cli +# LangGraph CLI -This package implements the official CLI for LangGraph API. +The official command-line interface for LangGraph, providing tools to create, develop, and deploy LangGraph applications. -## How to Test CLI Changes Locally -These instructions are for CLI development and testing. Use the CLI examples to test CLI changes locally. -1. Make changes to the CLI code. -1. Navigate to the `libs/cli/examples`: `cd libs/cli/examples` -1. Install CLI examples dependencies: `poetry install` -1. Run/test CLI command (e.g. `langgraph build`). +## Installation + +Install via pip: +```bash +pip install langgraph-cli +``` + +For development mode with hot reloading: +```bash +pip install "langgraph-cli[inmem]" +``` + +## Commands + +### `langgraph new` 🌱 +Create a new LangGraph project from a template +```bash +langgraph new [PATH] --template TEMPLATE_NAME +``` + +### `langgraph dev` 🏃‍♀️ +Run LangGraph API server in development mode with hot reloading +```bash +langgraph dev [OPTIONS] + --host TEXT Host to bind to (default: 127.0.0.1) + --port INTEGER Port to bind to (default: 2024) + --no-reload Disable auto-reload + --debug-port INTEGER Enable remote debugging + --no-browser Skip opening browser window + -c, --config FILE Config file path (default: langgraph.json) +``` + +### `langgraph up` 🚀 +Launch LangGraph API server in Docker +```bash +langgraph up [OPTIONS] + -p, --port INTEGER Port to expose (default: 8123) + --wait Wait for services to start + --watch Restart on file changes + --verbose Show detailed logs + -c, --config FILE Config file path + -d, --docker-compose Additional services file +``` + +### `langgraph build` +Build a Docker image for your LangGraph application +```bash +langgraph build -t IMAGE_TAG [OPTIONS] + --platform TEXT Target platforms (e.g., linux/amd64,linux/arm64) + --pull / --no-pull Use latest/local base image + -c, --config FILE Config file path +``` + +### `langgraph dockerfile` +Generate a Dockerfile for custom deployments +```bash +langgraph dockerfile SAVE_PATH [OPTIONS] + -c, --config FILE Config file path +``` + +## Configuration + +The CLI uses a `langgraph.json` configuration file with these key settings: + +```json +{ + "dependencies": ["langchain_openai", "./your_package"], // Required: Package dependencies + "graphs": { + "my_graph": "./your_package/file.py:graph" // Required: Graph definitions + }, + "env": "./.env", // Optional: Environment variables + "python_version": "3.11", // Optional: Python version (3.11/3.12) + "pip_config_file": "./pip.conf", // Optional: pip configuration + "dockerfile_lines": [] // Optional: Additional Dockerfile commands +} +``` + +See the [full documentation](https://langchain-ai.github.io/langgraph/docs/cloud/reference/cli.html) for detailed configuration options. + +## Development + +To develop the CLI itself: + +1. Clone the repository +2. Navigate to the CLI directory: `cd libs/cli` +3. Install development dependencies: `poetry install` +4. Make your changes to the CLI code +5. Test your changes: + ```bash + # Run CLI commands directly + poetry run langgraph --help + + # Or use the examples + cd examples + poetry install + poetry run langgraph dev # or other commands + ``` + +## License + +This project is licensed under the terms specified in the repository's LICENSE file. diff --git a/libs/cli/js-examples/.dockerignore b/libs/cli/js-examples/.dockerignore new file mode 100644 index 000000000..76add878f --- /dev/null +++ b/libs/cli/js-examples/.dockerignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/libs/cli/js-examples/.editorconfig b/libs/cli/js-examples/.editorconfig new file mode 100644 index 000000000..1ed453a37 --- /dev/null +++ b/libs/cli/js-examples/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +[*.{js,json,yml}] +charset = utf-8 +indent_style = space +indent_size = 2 diff --git a/libs/cli/js-examples/.env.example b/libs/cli/js-examples/.env.example new file mode 100644 index 000000000..1f381d1b1 --- /dev/null +++ b/libs/cli/js-examples/.env.example @@ -0,0 +1,3 @@ +# Copy this over: +# cp .env.example .env +# Then modify to suit your needs \ No newline at end of file diff --git a/libs/cli/js-examples/.eslintrc.cjs b/libs/cli/js-examples/.eslintrc.cjs new file mode 100644 index 000000000..da4c3ecb4 --- /dev/null +++ b/libs/cli/js-examples/.eslintrc.cjs @@ -0,0 +1,62 @@ +module.exports = { + extends: [ + "eslint:recommended", + "prettier", + "plugin:@typescript-eslint/recommended", + ], + parserOptions: { + ecmaVersion: 12, + parser: "@typescript-eslint/parser", + project: "./tsconfig.json", + sourceType: "module", + }, + plugins: ["import", "@typescript-eslint", "no-instanceof"], + ignorePatterns: [ + ".eslintrc.cjs", + "scripts", + "src/utils/lodash/*", + "node_modules", + "dist", + "dist-cjs", + "*.js", + "*.cjs", + "*.d.ts", + ], + rules: { + "no-process-env": 2, + "no-instanceof/no-instanceof": 2, + "@typescript-eslint/explicit-module-boundary-types": 0, + "@typescript-eslint/no-empty-function": 0, + "@typescript-eslint/no-shadow": 0, + "@typescript-eslint/no-empty-interface": 0, + "@typescript-eslint/no-use-before-define": ["error", "nofunc"], + "@typescript-eslint/no-unused-vars": ["warn", { args: "none" }], + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-misused-promises": "error", + camelcase: 0, + "class-methods-use-this": 0, + "import/extensions": [2, "ignorePackages"], + "import/no-extraneous-dependencies": [ + "error", + { devDependencies: ["**/*.test.ts"] }, + ], + "import/no-unresolved": 0, + "import/prefer-default-export": 0, + "keyword-spacing": "error", + "max-classes-per-file": 0, + "max-len": 0, + "no-await-in-loop": 0, + "no-bitwise": 0, + "no-console": 0, + "no-restricted-syntax": 0, + "no-shadow": 0, + "no-continue": 0, + "no-underscore-dangle": 0, + "no-use-before-define": 0, + "no-useless-constructor": 0, + "no-return-await": 0, + "consistent-return": 0, + "no-else-return": 0, + "new-cap": ["error", { properties: false, capIsNew: false }], + }, +}; diff --git a/libs/cli/js-examples/.gitignore b/libs/cli/js-examples/.gitignore new file mode 100644 index 000000000..e5363cc29 --- /dev/null +++ b/libs/cli/js-examples/.gitignore @@ -0,0 +1,19 @@ +index.cjs +index.js +index.d.ts +node_modules +dist +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +.turbo +**/.turbo +**/.eslintcache + +.env +.ipynb_checkpoints + diff --git a/libs/cli/js-examples/LICENSE b/libs/cli/js-examples/LICENSE new file mode 100644 index 000000000..57d0481d4 --- /dev/null +++ b/libs/cli/js-examples/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 LangChain + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/libs/cli/js-examples/README.md b/libs/cli/js-examples/README.md new file mode 100644 index 000000000..799fe90f1 --- /dev/null +++ b/libs/cli/js-examples/README.md @@ -0,0 +1,79 @@ +# New LangGraph.js Project + +[![CI](https://github.com/langchain-ai/new-langgraphjs-project/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/langchain-ai/new-langgraphjs-project/actions/workflows/unit-tests.yml) +[![Integration Tests](https://github.com/langchain-ai/new-langgraphjs-project/actions/workflows/integration-tests.yml/badge.svg)](https://github.com/langchain-ai/new-langgraphjs-project/actions/workflows/integration-tests.yml) +[![Open in - LangGraph Studio](https://img.shields.io/badge/Open_in-LangGraph_Studio-00324d.svg?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NS4zMzMiIGhlaWdodD0iODUuMzMzIiB2ZXJzaW9uPSIxLjAiIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHBhdGggZD0iTTEzIDcuOGMtNi4zIDMuMS03LjEgNi4zLTYuOCAyNS43LjQgMjQuNi4zIDI0LjUgMjUuOSAyNC41QzU3LjUgNTggNTggNTcuNSA1OCAzMi4zIDU4IDcuMyA1Ni43IDYgMzIgNmMtMTIuOCAwLTE2LjEuMy0xOSAxLjhtMzcuNiAxNi42YzIuOCAyLjggMy40IDQuMiAzLjQgNy42cy0uNiA0LjgtMy40IDcuNkw0Ny4yIDQzSDE2LjhsLTMuNC0zLjRjLTQuOC00LjgtNC44LTEwLjQgMC0xNS4ybDMuNC0zLjRoMzAuNHoiLz48cGF0aCBkPSJNMTguOSAyNS42Yy0xLjEgMS4zLTEgMS43LjQgMi41LjkuNiAxLjcgMS44IDEuNyAyLjcgMCAxIC43IDIuOCAxLjYgNC4xIDEuNCAxLjkgMS40IDIuNS4zIDMuMi0xIC42LS42LjkgMS40LjkgMS41IDAgMi43LS41IDIuNy0xIDAtLjYgMS4xLS44IDIuNi0uNGwyLjYuNy0xLjgtMi45Yy01LjktOS4zLTkuNC0xMi4zLTExLjUtOS44TTM5IDI2YzAgMS4xLS45IDIuNS0yIDMuMi0yLjQgMS41LTIuNiAzLjQtLjUgNC4yLjguMyAyIDEuNyAyLjUgMy4xLjYgMS41IDEuNCAyLjMgMiAyIDEuNS0uOSAxLjItMy41LS40LTMuNS0yLjEgMC0yLjgtMi44LS44LTMuMyAxLjYtLjQgMS42LS41IDAtLjYtMS4xLS4xLTEuNS0uNi0xLjItMS42LjctMS43IDMuMy0yLjEgMy41LS41LjEuNS4yIDEuNi4zIDIuMiAwIC43LjkgMS40IDEuOSAxLjYgMi4xLjQgMi4zLTIuMy4yLTMuMi0uOC0uMy0yLTEuNy0yLjUtMy4xLTEuMS0zLTMtMy4zLTMtLjUiLz48L3N2Zz4=)](https://langgraph-studio.vercel.app/templates/open?githubUrl=https://github.com/langchain-ai/new-langgraphjs-project) + +This template demonstrates a simple chatbot implemented using [LangGraph.js](https://github.com/langchain-ai/langgraphjs), designed for [LangGraph Studio](https://github.com/langchain-ai/langgraph-studio). The chatbot maintains persistent chat memory, allowing for coherent conversations across multiple interactions. + +![Graph view in LangGraph studio UI](./static/studio.png) + +The core logic, defined in `src/agent/graph.ts`, showcases a straightforward chatbot that responds to user queries while maintaining context from previous messages. + +## What it does + +The simple chatbot: + +1. Takes a user **message** as input +2. Maintains a history of the conversation +3. Returns a placeholder response, updating the conversation history + +This template provides a foundation that can be easily customized and extended to create more complex conversational agents. + +## Getting Started + +Assuming you have already [installed LangGraph Studio](https://github.com/langchain-ai/langgraph-studio?tab=readme-ov-file#download), to set up: + +1. Create a `.env` file. This template does not require any environment variables by default, but you will likely want to add some when customizing. + +```bash +cp .env.example .env +``` + + + + + +2. Open the folder in LangGraph Studio! +3. Customize the code as needed. + +## How to customize + +1. **Add an LLM call**: You can select and install a chat model wrapper from [the LangChain.js ecosystem](https://js.langchain.com/docs/integrations/chat/), or use LangGraph.js without LangChain.js. +2. **Extend the graph**: The core logic of the chatbot is defined in [graph.ts](./src/agent/graph.ts). You can modify this file to add new nodes, edges, or change the flow of the conversation. + +You can also extend this template by: + +- Adding [custom tools or functions](https://js.langchain.com/docs/how_to/tool_calling) to enhance the chatbot's capabilities. +- Implementing additional logic for handling specific types of user queries or tasks. +- Add retrieval-augmented generation (RAG) capabilities by integrating [external APIs or databases](https://langchain-ai.github.io/langgraphjs/tutorials/rag/langgraph_agentic_rag/) to provide more customized responses. + +## Development + +While iterating on your graph, you can edit past state and rerun your app from previous states to debug specific nodes. Local changes will be automatically applied via hot reload. Try experimenting with: + +- Modifying the system prompt to give your chatbot a unique personality. +- Adding new nodes to the graph for more complex conversation flows. +- Implementing conditional logic to handle different types of user inputs. + +Follow-up requests will be appended to the same thread. You can create an entirely new thread, clearing previous history, using the `+` button in the top right. + +For more advanced features and examples, refer to the [LangGraph.js documentation](https://github.com/langchain-ai/langgraphjs). These resources can help you adapt this template for your specific use case and build more sophisticated conversational agents. + +LangGraph Studio also integrates with [LangSmith](https://smith.langchain.com/) for more in-depth tracing and collaboration with teammates, allowing you to analyze and optimize your chatbot's performance. + + diff --git a/libs/cli/js-examples/jest.config.js b/libs/cli/js-examples/jest.config.js new file mode 100644 index 000000000..9e8937435 --- /dev/null +++ b/libs/cli/js-examples/jest.config.js @@ -0,0 +1,18 @@ +export default { + preset: "ts-jest/presets/default-esm", + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, + transform: { + "^.+\\.tsx?$": [ + "ts-jest", + { + useESM: true, + }, + ], + }, + extensionsToTreatAsEsm: [".ts"], + setupFiles: ["dotenv/config"], + passWithNoTests: true, + testTimeout: 20_000, +}; diff --git a/libs/cli/js-examples/langgraph.json b/libs/cli/js-examples/langgraph.json new file mode 100644 index 000000000..ddb17babc --- /dev/null +++ b/libs/cli/js-examples/langgraph.json @@ -0,0 +1,8 @@ +{ + "node_version": "20", + "graphs": { + "agent": "./src/agent/graph.ts:graph" + }, + "env": ".env", + "dependencies": ["."] +} diff --git a/libs/cli/js-examples/package.json b/libs/cli/js-examples/package.json new file mode 100644 index 000000000..f7b8daa03 --- /dev/null +++ b/libs/cli/js-examples/package.json @@ -0,0 +1,45 @@ +{ + "name": "example-graph", + "version": "0.0.1", + "description": "A starter template for creating a LangGraph workflow.", + "packageManager": "yarn@1.22.22", + "main": "my_app/graph.ts", + "author": "Your Name", + "license": "MIT", + "private": true, + "type": "module", + "scripts": { + "build": "tsc", + "clean": "rm -rf dist", + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --testPathPattern=\\.test\\.ts$ --testPathIgnorePatterns=\\.int\\.test\\.ts$", + "test:int": "node --experimental-vm-modules node_modules/jest/bin/jest.js --testPathPattern=\\.int\\.test\\.ts$", + "format": "prettier --write .", + "lint": "eslint src", + "format:check": "prettier --check .", + "lint:langgraph-json": "node scripts/checkLanggraphPaths.js", + "lint:all": "yarn lint & yarn lint:langgraph-json & yarn format:check", + "test:all": "yarn test && yarn test:int && yarn lint:langgraph" + }, + "dependencies": { + "@langchain/core": "^0.3.2", + "@langchain/langgraph": "^0.2.5" + }, + "devDependencies": { + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "^9.9.1", + "@tsconfig/recommended": "^1.0.7", + "@types/jest": "^29.5.0", + "@typescript-eslint/eslint-plugin": "^5.59.8", + "@typescript-eslint/parser": "^5.59.8", + "dotenv": "^16.4.5", + "eslint": "^8.41.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-no-instanceof": "^1.0.1", + "eslint-plugin-prettier": "^4.2.1", + "jest": "^29.7.0", + "prettier": "^3.3.3", + "ts-jest": "^29.1.0", + "typescript": "^5.3.3" + } +} diff --git a/libs/cli/js-examples/src/agent/graph.ts b/libs/cli/js-examples/src/agent/graph.ts new file mode 100644 index 000000000..244f88ee4 --- /dev/null +++ b/libs/cli/js-examples/src/agent/graph.ts @@ -0,0 +1,104 @@ +/** + * Starter LangGraph.js Template + * Make this code your own! + */ +import { StateGraph } from "@langchain/langgraph"; +import { RunnableConfig } from "@langchain/core/runnables"; +import { StateAnnotation } from "./state.js"; + +/** + * Define a node, these do the work of the graph and should have most of the logic. + * Must return a subset of the properties set in StateAnnotation. + * @param state The current state of the graph. + * @param config Extra parameters passed into the state graph. + * @returns Some subset of parameters of the graph state, used to update the state + * for the edges and nodes executed next. + */ +const callModel = async ( + state: typeof StateAnnotation.State, + _config: RunnableConfig, +): Promise => { + /** + * Do some work... (e.g. call an LLM) + * For example, with LangChain you could do something like: + * + * ```bash + * $ npm i @langchain/anthropic + * ``` + * + * ```ts + * import { ChatAnthropic } from "@langchain/anthropic"; + * const model = new ChatAnthropic({ + * model: "claude-3-5-sonnet-20240620", + * apiKey: process.env.ANTHROPIC_API_KEY, + * }); + * const res = await model.invoke(state.messages); + * ``` + * + * Or, with an SDK directly: + * + * ```bash + * $ npm i openai + * ``` + * + * ```ts + * import OpenAI from "openai"; + * const openai = new OpenAI({ + * apiKey: process.env.OPENAI_API_KEY, + * }); + * + * const chatCompletion = await openai.chat.completions.create({ + * messages: [{ + * role: state.messages[0]._getType(), + * content: state.messages[0].content, + * }], + * model: "gpt-4o-mini", + * }); + * ``` + */ + console.log("Current state:", state); + return { + messages: [ + { + role: "assistant", + content: `Hi there! How are you?`, + }, + ], + }; +}; + +/** + * Routing function: Determines whether to continue research or end the builder. + * This function decides if the gathered information is satisfactory or if more research is needed. + * + * @param state - The current state of the research builder + * @returns Either "callModel" to continue research or END to finish the builder + */ +export const route = ( + state: typeof StateAnnotation.State, +): "__end__" | "callModel" => { + if (state.messages.length > 0) { + return "__end__"; + } + // Loop back + return "callModel"; +}; + +// Finally, create the graph itself. +const builder = new StateGraph(StateAnnotation) + // Add the nodes to do the work. + // Chaining the nodes together in this way + // updates the types of the StateGraph instance + // so you have static type checking when it comes time + // to add the edges. + .addNode("callModel", callModel) + // Regular edges mean "always transition to node B after node A is done" + // The "__start__" and "__end__" nodes are "virtual" nodes that are always present + // and represent the beginning and end of the builder. + .addEdge("__start__", "callModel") + // Conditional edges optionally route to different nodes (or end) + .addConditionalEdges("callModel", route); + +export const graph = builder.compile(); + +graph.name = "New Agent"; diff --git a/libs/cli/js-examples/src/agent/state.ts b/libs/cli/js-examples/src/agent/state.ts new file mode 100644 index 000000000..6f7963405 --- /dev/null +++ b/libs/cli/js-examples/src/agent/state.ts @@ -0,0 +1,59 @@ +import { BaseMessage, BaseMessageLike } from "@langchain/core/messages"; +import { Annotation, messagesStateReducer } from "@langchain/langgraph"; + +/** + * A graph's StateAnnotation defines three main things: + * 1. The structure of the data to be passed between nodes (which "channels" to read from/write to and their types) + * 2. Default values for each field + * 3. Reducers for the state's. Reducers are functions that determine how to apply updates to the state. + * See [Reducers](https://langchain-ai.github.io/langgraphjs/concepts/low_level/#reducers) for more information. + */ + +// This is the primary state of your agent, where you can store any information +export const StateAnnotation = Annotation.Root({ + /** + * Messages track the primary execution state of the agent. + * + * Typically accumulates a pattern of: + * + * 1. HumanMessage - user input + * 2. AIMessage with .tool_calls - agent picking tool(s) to use to collect + * information + * 3. ToolMessage(s) - the responses (or errors) from the executed tools + * + * (... repeat steps 2 and 3 as needed ...) + * 4. AIMessage without .tool_calls - agent responding in unstructured + * format to the user. + * + * 5. HumanMessage - user responds with the next conversational turn. + * + * (... repeat steps 2-5 as needed ... ) + * + * Merges two lists of messages or message-like objects with role and content, + * updating existing messages by ID. + * + * Message-like objects are automatically coerced by `messagesStateReducer` into + * LangChain message classes. If a message does not have a given id, + * LangGraph will automatically assign one. + * + * By default, this ensures the state is "append-only", unless the + * new message has the same ID as an existing message. + * + * Returns: + * A new list of messages with the messages from \`right\` merged into \`left\`. + * If a message in \`right\` has the same ID as a message in \`left\`, the + * message from \`right\` will replace the message from \`left\`.` + */ + messages: Annotation({ + reducer: messagesStateReducer, + default: () => [], + }), + /** + * Feel free to add additional attributes to your state as needed. + * Common examples include retrieved documents, extracted entities, API connections, etc. + * + * For simple fields whose value should be overwritten by the return value of a node, + * you don't need to define a reducer or default. + */ + // additionalField: Annotation, +}); diff --git a/libs/cli/js-examples/static/studio.png b/libs/cli/js-examples/static/studio.png new file mode 100644 index 000000000..5338518f9 Binary files /dev/null and b/libs/cli/js-examples/static/studio.png differ diff --git a/libs/cli/js-examples/tests/agent.test.ts b/libs/cli/js-examples/tests/agent.test.ts new file mode 100644 index 000000000..d2948bbae --- /dev/null +++ b/libs/cli/js-examples/tests/agent.test.ts @@ -0,0 +1,8 @@ +import { describe, it, expect } from "@jest/globals"; +import { route } from "../src/agent/graph.js"; +describe("Routers", () => { + it("Test route", async () => { + const res = route({ messages: [] }); + expect(res).toEqual("callModel"); + }, 100_000); +}); diff --git a/libs/cli/js-examples/tests/graph.int.test.ts b/libs/cli/js-examples/tests/graph.int.test.ts new file mode 100644 index 000000000..a05978f4c --- /dev/null +++ b/libs/cli/js-examples/tests/graph.int.test.ts @@ -0,0 +1,18 @@ +import { describe, it, expect } from "@jest/globals"; +import { graph } from "../src/agent/graph.js"; + +describe("Graph", () => { + it("should process input through the graph", async () => { + const input = "What is the capital of France?"; + const result = await graph.invoke({ input }); + + expect(result).toBeDefined(); + expect(typeof result).toBe("object"); + expect(result.messages).toBeDefined(); + expect(Array.isArray(result.messages)).toBe(true); + expect(result.messages.length).toBeGreaterThan(0); + + const lastMessage = result.messages[result.messages.length - 1]; + expect(lastMessage.content.toString().toLowerCase()).toContain("hi"); + }, 30000); // Increased timeout to 30 seconds +}); diff --git a/libs/cli/js-examples/tsconfig.json b/libs/cli/js-examples/tsconfig.json new file mode 100644 index 000000000..6e51abd3d --- /dev/null +++ b/libs/cli/js-examples/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "@tsconfig/recommended", + "compilerOptions": { + "target": "ES2021", + "lib": ["ES2021", "ES2022.Object", "DOM"], + "module": "NodeNext", + "moduleResolution": "nodenext", + "esModuleInterop": true, + "noImplicitReturns": true, + "declaration": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "useDefineForClassFields": true, + "strictPropertyInitialization": false, + "allowJs": true, + "strict": true, + "strictFunctionTypes": false, + "outDir": "dist", + "types": ["jest", "node"], + "resolveJsonModule": true + }, + "include": ["**/*.ts", "**/*.js"], + "exclude": ["node_modules", "dist"] +} diff --git a/libs/cli/js-examples/yarn.lock b/libs/cli/js-examples/yarn.lock new file mode 100644 index 000000000..034535b0e --- /dev/null +++ b/libs/cli/js-examples/yarn.lock @@ -0,0 +1,3801 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" + integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== + dependencies: + "@babel/highlight" "^7.24.7" + picocolors "^1.0.0" + +"@babel/compat-data@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.2.tgz#e41928bd33475305c586f6acbbb7e3ade7a6f7f5" + integrity sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.2.tgz#ed8eec275118d7613e77a352894cd12ded8eba77" + integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.25.0" + "@babel/helper-compilation-targets" "^7.25.2" + "@babel/helper-module-transforms" "^7.25.2" + "@babel/helpers" "^7.25.0" + "@babel/parser" "^7.25.0" + "@babel/template" "^7.25.0" + "@babel/traverse" "^7.25.2" + "@babel/types" "^7.25.2" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.25.0", "@babel/generator@^7.7.2": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.0.tgz#f858ddfa984350bc3d3b7f125073c9af6988f18e" + integrity sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw== + dependencies: + "@babel/types" "^7.25.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c" + integrity sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw== + dependencies: + "@babel/compat-data" "^7.25.2" + "@babel/helper-validator-option" "^7.24.8" + browserslist "^4.23.1" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-module-imports@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" + integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-module-transforms@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6" + integrity sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ== + dependencies: + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-simple-access" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + "@babel/traverse" "^7.25.2" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.8.0": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" + integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== + +"@babel/helper-simple-access@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3" + integrity sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-string-parser@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" + integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== + +"@babel/helper-validator-identifier@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" + integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== + +"@babel/helper-validator-option@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" + integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== + +"@babel/helpers@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.0.tgz#e69beb7841cb93a6505531ede34f34e6a073650a" + integrity sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw== + dependencies: + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.0" + +"@babel/highlight@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" + integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== + dependencies: + "@babel/helper-validator-identifier" "^7.24.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.0", "@babel/parser@^7.25.3": + version "7.25.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.3.tgz#91fb126768d944966263f0657ab222a642b82065" + integrity sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw== + dependencies: + "@babel/types" "^7.25.2" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz#b4f9ea95a79e6912480c4b626739f86a076624ca" + integrity sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d" + integrity sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz#58d458271b4d3b6bb27ee6ac9525acbb259bad1c" + integrity sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/template@^7.25.0", "@babel/template@^7.3.3": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" + integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/parser" "^7.25.0" + "@babel/types" "^7.25.0" + +"@babel/traverse@^7.24.7", "@babel/traverse@^7.25.2": + version "7.25.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.3.tgz#f1b901951c83eda2f3e29450ce92743783373490" + integrity sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.25.0" + "@babel/parser" "^7.25.3" + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.2" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.3.3": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.2.tgz#55fb231f7dc958cd69ea141a4c2997e819646125" + integrity sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q== + dependencies: + "@babel/helper-string-parser" "^7.24.8" + "@babel/helper-validator-identifier" "^7.24.7" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": + version "4.11.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" + integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/eslintrc@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.1.0.tgz#dbd3482bfd91efa663cbe7aa1f506839868207b6" + integrity sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== + +"@eslint/js@^9.9.1": + version "9.9.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.9.1.tgz#4a97e85e982099d6c7ee8410aacb55adaa576f06" + integrity sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ== + +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== + dependencies: + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@langchain/core@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.3.2.tgz#aff6d83149a40e0e735910f583aca0f1dd7d1bab" + integrity sha512-FeoDOStP8l1YdxgykpXnVoEnl4lxGNSOdYzUJN/EdFtkc6cIjDDS5+xewajme0+egaUsO4tGLezKaFpoWxAyQA== + dependencies: + ansi-styles "^5.0.0" + camelcase "6" + decamelize "1.2.0" + js-tiktoken "^1.0.12" + langsmith "^0.1.56" + mustache "^4.2.0" + p-queue "^6.6.2" + p-retry "4" + uuid "^10.0.0" + zod "^3.22.4" + zod-to-json-schema "^3.22.3" + +"@langchain/langgraph-checkpoint@~0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-0.0.6.tgz#69f0c5c9aeefd48dcf0fa1ffa0744d8139a9f27d" + integrity sha512-hQsznlUMFKyOCaN9VtqNSSemfKATujNy5ePM6NX7lruk/Mmi2t7R9SsBnf9G2Yts+IaIwv3vJJaAFYEHfqbc5g== + dependencies: + uuid "^10.0.0" + +"@langchain/langgraph@^0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.2.5.tgz#c42743a59adef03f2e1fea0c198a01694ae34d51" + integrity sha512-H4OgZyGRWZHBaiXXIb9avyB8zI6+3OewKn+UOZ+wUzYLKyF3cnq0cNF4/Ps+gxCa5RtOnsHIqQyRkojfXIOqgA== + dependencies: + "@langchain/langgraph-checkpoint" "~0.0.6" + double-ended-queue "^2.1.0-0" + uuid "^10.0.0" + zod "^3.23.8" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@tsconfig/recommended@^1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@tsconfig/recommended/-/recommended-1.0.7.tgz#fdd95fc2c8d643c8b4a8ca45fd68eea248512407" + integrity sha512-xiNMgCuoy4mCL4JTywk9XFs5xpRUcKxtWEcMR6FNMtsgewYTIgIR+nvlP4A4iRCAzRsHMnPhvTRrzp4AGcRTEA== + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" + integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== + dependencies: + "@babel/types" "^7.20.7" + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.5.0": + version "29.5.12" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544" + integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + +"@types/node@*": + version "22.4.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.4.1.tgz#9b595d292c65b94c20923159e2ce947731b6fdce" + integrity sha512-1tbpb9325+gPnKK0dMm+/LMriX0vKxf6RnB0SZUqfyVkQ4fMgUSySqhxE/y8Jvs4NyF1yHzTfG9KlnkIODxPKg== + dependencies: + undici-types "~6.19.2" + +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + +"@types/semver@^7.3.12": + version "7.5.8" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" + integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/uuid@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-10.0.0.tgz#e9c07fe50da0f53dc24970cca94d619ff03f6f6d" + integrity sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^5.59.8": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" + integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== + dependencies: + "@eslint-community/regexpp" "^4.4.0" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/type-utils" "5.62.0" + "@typescript-eslint/utils" "5.62.0" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.0" + natural-compare-lite "^1.4.0" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/parser@^5.59.8": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7" + integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== + dependencies: + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" + integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + +"@typescript-eslint/type-utils@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" + integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== + dependencies: + "@typescript-eslint/typescript-estree" "5.62.0" + "@typescript-eslint/utils" "5.62.0" + debug "^4.3.4" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" + integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== + +"@typescript-eslint/typescript-estree@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" + integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" + integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" + eslint-scope "^5.1.1" + semver "^7.3.7" + +"@typescript-eslint/visitor-keys@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" + integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== + dependencies: + "@typescript-eslint/types" "5.62.0" + eslint-visitor-keys "^3.3.0" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.12.0, acorn@^8.9.0: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-buffer-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" + integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== + dependencies: + call-bind "^1.0.5" + is-array-buffer "^3.0.4" + +array-includes@^3.1.7: + version "3.1.8" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" + integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + is-string "^1.0.7" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array.prototype.findlastindex@^1.2.3: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d" + integrity sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" + +array.prototype.flat@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" + integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +arraybuffer.prototype.slice@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" + integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.2.1" + get-intrinsic "^1.2.3" + is-array-buffer "^3.0.4" + is-shared-array-buffer "^1.0.2" + +async@^3.2.3: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" + integrity sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.23.1: + version "4.23.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800" + integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA== + dependencies: + caniuse-lite "^1.0.30001646" + electron-to-chromium "^1.5.4" + node-releases "^2.0.18" + update-browserslist-db "^1.1.0" + +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@6, camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +caniuse-lite@^1.0.30001646: + version "1.0.30001651" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz#52de59529e8b02b1aedcaaf5c05d9e23c0c28138" + integrity sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg== + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz#c485341ae8fd999ca4ee5af2d7a1c9ae01e0099c" + integrity sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +data-view-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" + integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" + integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" + integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" + integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== + dependencies: + ms "2.1.2" + +decamelize@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + +dedent@^1.0.0: + version "1.5.3" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" + integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.2.0, define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dotenv@^16.4.5: + version "16.4.5" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + +double-ended-queue@^2.1.0-0: + version "2.1.0-0" + resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c" + integrity sha512-+BNfZ+deCo8hMNpDqDnvT+c0XpJ5cUa6mqYq89bho2Ifze4URTqRkcwR399hWoTrTkbZ/XJYDgP6rc7pRgffEQ== + +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + +electron-to-chromium@^1.5.4: + version "1.5.12" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.12.tgz#ee31756eaa2e06f2aa606f170b7ad06dd402b4e4" + integrity sha512-tIhPkdlEoCL1Y+PToq3zRNehUaKp3wBX/sr7aclAWdIWjvqAe/Im/H0SiCM4c1Q8BLPHCdoJTol+ZblflydehA== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2: + version "1.23.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" + integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== + dependencies: + array-buffer-byte-length "^1.0.1" + arraybuffer.prototype.slice "^1.0.3" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + data-view-buffer "^1.0.1" + data-view-byte-length "^1.0.1" + data-view-byte-offset "^1.0.0" + es-define-property "^1.0.0" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-set-tostringtag "^2.0.3" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.4" + get-symbol-description "^1.0.2" + globalthis "^1.0.3" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + hasown "^2.0.2" + internal-slot "^1.0.7" + is-array-buffer "^3.0.4" + is-callable "^1.2.7" + is-data-view "^1.0.1" + is-negative-zero "^2.0.3" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.3" + is-string "^1.0.7" + is-typed-array "^1.1.13" + is-weakref "^1.0.2" + object-inspect "^1.13.1" + object-keys "^1.1.1" + object.assign "^4.1.5" + regexp.prototype.flags "^1.5.2" + safe-array-concat "^1.1.2" + safe-regex-test "^1.0.3" + string.prototype.trim "^1.2.9" + string.prototype.trimend "^1.0.8" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.2" + typed-array-byte-length "^1.0.1" + typed-array-byte-offset "^1.0.2" + typed-array-length "^1.0.6" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.15" + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.2.1, es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" + integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" + integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== + dependencies: + get-intrinsic "^1.2.4" + has-tostringtag "^1.0.2" + hasown "^2.0.1" + +es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" + integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== + dependencies: + hasown "^2.0.0" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escalade@^3.1.1, escalade@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-prettier@^8.8.0: + version "8.10.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz#3a06a662130807e2502fc3ff8b4143d8a0658e11" + integrity sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg== + +eslint-import-resolver-node@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== + dependencies: + debug "^3.2.7" + is-core-module "^2.13.0" + resolve "^1.22.4" + +eslint-module-utils@^2.8.0: + version "2.8.2" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.2.tgz#2ecad69d71e1fa81f17f7f24d5d3e46b168de663" + integrity sha512-3XnC5fDyc8M4J2E8pt8pmSVRX2M+5yWMCfI/kDZwauQeFgzQOuhcRBFKjTeJagqgk4sFKxe1mvNVnaWwImx/Tg== + dependencies: + debug "^3.2.7" + +eslint-plugin-import@^2.27.5: + version "2.29.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz#d45b37b5ef5901d639c15270d74d46d161150643" + integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw== + dependencies: + array-includes "^3.1.7" + array.prototype.findlastindex "^1.2.3" + array.prototype.flat "^1.3.2" + array.prototype.flatmap "^1.3.2" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.9" + eslint-module-utils "^2.8.0" + hasown "^2.0.0" + is-core-module "^2.13.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.fromentries "^2.0.7" + object.groupby "^1.0.1" + object.values "^1.1.7" + semver "^6.3.1" + tsconfig-paths "^3.15.0" + +eslint-plugin-no-instanceof@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-no-instanceof/-/eslint-plugin-no-instanceof-1.0.1.tgz#5d9fc86d160df6991b654b294a62390207f1bb97" + integrity sha512-zlqQ7EsfzbRO68uI+p8FIE7zYB4njs+nNbkNjSb5QmLi2et67zQLqSeaao5U9SpnlZTTJC87nS2oyHo2ACtajw== + +eslint-plugin-prettier@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" + integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb" + integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw== + +eslint@^8.41.0: + version "8.57.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" + integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.0" + "@humanwhocodes/config-array" "^0.11.14" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^10.0.1: + version "10.1.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.1.0.tgz#8788dae611574c0f070691f522e4116c5a11fc56" + integrity sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA== + dependencies: + acorn "^8.12.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.0.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +eventemitter3@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-glob@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-symbol-description@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" + integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== + dependencies: + call-bind "^1.0.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + +globalthis@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1, has-proto@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +ignore@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +internal-slot@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" + integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.0" + side-channel "^1.0.4" + +is-array-buffer@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" + integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-core-module@^2.13.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" + integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== + dependencies: + hasown "^2.0.2" + +is-core-module@^2.13.1: + version "2.15.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" + integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== + dependencies: + hasown "^2.0.2" + +is-data-view@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" + integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== + dependencies: + is-typed-array "^1.1.13" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" + integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== + dependencies: + call-bind "^1.0.7" + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" + integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== + dependencies: + which-typed-array "^1.1.14" + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.0.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +js-tiktoken@^1.0.12: + version "1.0.14" + resolved "https://registry.yarnpkg.com/js-tiktoken/-/js-tiktoken-1.0.14.tgz#756f353262d559da16b58b5bcecfd93330076da2" + integrity sha512-Pk3l3WOgM9joguZY2k52+jH82RtABRgB5RdGFZNUGbOKGMVlNmafcPA3b0ITcCZPu1L9UclP1tne6aw7ZI4Myg== + dependencies: + base64-js "^1.5.1" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +langsmith@^0.1.56: + version "0.1.58" + resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.1.58.tgz#502aa6c22fecd15fa65c14ffbe7fcc4643201f47" + integrity sha512-crbJbfw6hLBbVDQlMRWRVYwppApiDMncsqqBtTP1udUvilAsw4btIpBq0Tf+Jr8iQs6cEpZr/h7lGr5DdzAGew== + dependencies: + "@types/uuid" "^10.0.0" + commander "^10.0.1" + p-queue "^6.6.2" + p-retry "4" + semver "^7.6.3" + uuid "^10.0.0" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@1.x: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.0, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mustache@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64" + integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== + +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +object-inspect@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" + integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== + dependencies: + call-bind "^1.0.5" + define-properties "^1.2.1" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.fromentries@^2.0.7: + version "2.0.8" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" + integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + +object.groupby@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" + integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + +object.values@^1.1.7: + version "1.2.0" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" + integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-queue@^6.6.2: + version "6.6.2" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" + integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== + dependencies: + eventemitter3 "^4.0.4" + p-timeout "^3.2.0" + +p-retry@4: + version "4.6.2" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== + dependencies: + "@types/retry" "0.12.0" + retry "^0.13.1" + +p-timeout@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0, picocolors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +possible-typed-array-names@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" + integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +regexp.prototype.flags@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" + integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== + dependencies: + call-bind "^1.0.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== + +resolve@^1.20.0, resolve@^1.22.4: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-array-concat@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" + integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + has-symbols "^1.0.3" + isarray "^2.0.5" + +safe-regex-test@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" + integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-regex "^1.1.4" + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.7, semver@^7.5.3, semver@^7.5.4, semver@^7.6.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string.prototype.trim@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" + integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-object-atoms "^1.0.0" + +string.prototype.trimend@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" + integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-jest@^29.1.0: + version "29.2.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.4.tgz#38ccf487407d7a63054a72689f6f99b075e296e5" + integrity sha512-3d6tgDyhCI29HlpwIq87sNuI+3Q6GLTTCeYRHCs7vDz+/3GCMwEtV9jezLyl4ZtnBgx00I7hm8PCP8cTksMGrw== + dependencies: + bs-logger "0.x" + ejs "^3.1.10" + fast-json-stable-stringify "2.x" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "4.x" + make-error "1.x" + semver "^7.5.3" + yargs-parser "^21.0.1" + +tsconfig-paths@^3.15.0: + version "3.15.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" + integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +typed-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" + integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-typed-array "^1.1.13" + +typed-array-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" + integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-byte-offset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" + integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-length@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" + integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + +typescript@^5.3.3: + version "5.5.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" + integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + +update-browserslist-db@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" + integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== + dependencies: + escalade "^3.1.2" + picocolors "^1.0.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +uuid@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294" + integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-typed-array@^1.1.14, which-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" + integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.2" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^21.0.1, yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zod-to-json-schema@^3.22.3: + version "3.23.2" + resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.23.2.tgz#bc7e379c8050462538383e382964c03d8fe008f9" + integrity sha512-uSt90Gzc/tUfyNqxnjlfBs8W6WSGpNBv0rVsNxP/BVSMHMKGdthPYff4xtCHYloJGM0CFxFsb3NbC0eqPhfImw== + +zod@^3.22.4, zod@^3.23.8: + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== diff --git a/libs/cli/langgraph_cli/cli.py b/libs/cli/langgraph_cli/cli.py index ebd7e0701..4460f3f41 100644 --- a/libs/cli/langgraph_cli/cli.py +++ b/libs/cli/langgraph_cli/cli.py @@ -1,10 +1,12 @@ import json import pathlib +import shutil import sys -from typing import Callable, Optional +from typing import Callable, Optional, Sequence import click import click.exceptions +from click import secho import langgraph_cli.config import langgraph_cli.docker @@ -14,6 +16,8 @@ from langgraph_cli.constants import DEFAULT_CONFIG, DEFAULT_PORT from langgraph_cli.docker import DockerCapabilities from langgraph_cli.exec import Runner, subp_exec from langgraph_cli.progress import Progress +from langgraph_cli.templates import TEMPLATE_HELP_STRING, create_new +from langgraph_cli.version import __version__ OPT_DOCKER_COMPOSE = click.option( "--docker-compose", @@ -147,6 +151,7 @@ OPT_POSTGRES_URI = click.option( @click.group() +@click.version_option(version=__version__, prog_name="LangGraph CLI") def cli(): pass @@ -166,9 +171,7 @@ def cli(): is_flag=True, help="Wait for services to start before returning. Implies --detach", ) -@cli.command( - help="Start langgraph API server. For local testing, requires a LangSmith API key with access to LangGraph Cloud closed beta. Requires a license key for production use." -) +@cli.command(help="🚀 Launch LangGraph API server.") @log_command def up( config: pathlib.Path, @@ -238,7 +241,7 @@ For production use, requires a license key in env var LANGGRAPH_CLOUD_LICENSE_KE f"""Ready! - API: http://localhost:{port} - Docs: http://localhost:{port}/docs -- Debugger: {debugger_origin}/studio/?baseUrl={debugger_base_url_query} +- LangGraph Studio: {debugger_origin}/studio/?baseUrl={debugger_base_url_query} """ ) sys.stdout.flush() @@ -260,117 +263,15 @@ For production use, requires a license key in env var LANGGRAPH_CLOUD_LICENSE_KE ) -@OPT_PULL -@OPT_PORT -@OPT_CONFIG -@OPT_VERBOSE -@cli.command( - help="Start langgraph test server. This command enables you to confirm your graph will work inside the langgraph API server, before using LangGraph Cloud." -) -@log_command -def test( - config: pathlib.Path, - port: int, - pull: bool, - # stop_when_ready: bool, - verbose: bool, -): - with Runner() as runner, Progress(message="Pulling...") as set: - # check docker available - capabilities = langgraph_cli.docker.check_capabilities(runner) - # open config - with open(config) as f: - config_json = langgraph_cli.config.validate_config(json.load(f)) - # build - base_image = "langchain/langgraph-trial" - tag = f"langgraph-test-{config.parent.name}" - _build( - runner, - set, - config, - config_json, - None, - base_image, - pull, - tag, - ) - # run - set("Running...") - args = [ - "run", - "--rm", - "-p", - f"{port}:8000", - ] - if isinstance(config_json["env"], str): - args.extend( - [ - "--env-file", - str(config.parent / config_json["env"]), - ] - ) - else: - for k, v in config_json["env"].items(): - args.extend( - [ - "-e", - f"{k}={v}", - ] - ) - if capabilities.healthcheck_start_interval: - args.extend( - [ - "--health-interval", - "5s", - "--health-retries", - "1", - "--health-start-period", - "10s", - "--health-start-interval", - "1s", - ] - ) - else: - args.extend( - [ - "--health-interval", - "5s", - "--health-retries", - "2", - ] - ) - - def on_stdout(line: str): - if "GET /ok" in line: - set("") - sys.stdout.write( - f"""Ready! -- API: http://localhost:{port} -""" - ) - sys.stdout.flush() - return True - - runner.run( - subp_exec( - "docker", - *args, - tag, - verbose=verbose, - on_stdout=on_stdout, - ) - ) - - def _build( runner, set: Callable[[str], None], config: pathlib.Path, config_json: dict, - platform: Optional[str], base_image: Optional[str], pull: bool, tag: str, + passthrough: Sequence[str] = (), ): base_image = base_image or ( "langchain/langgraphjs-api" @@ -384,9 +285,11 @@ def _build( subp_exec( "docker", "pull", - f"{base_image}:{config_json['node_version']}" - if config_json.get("node_version") - else f"{base_image}:{config_json['python_version']}", + ( + f"{base_image}:{config_json['node_version']}" + if config_json.get("node_version") + else f"{base_image}:{config_json['python_version']}" + ), verbose=True, ) ) @@ -398,14 +301,18 @@ def _build( "-t", tag, ] - if platform: - args.extend(["--platform", platform]) # apply config stdin = langgraph_cli.config.config_to_docker(config, config_json, base_image) # run docker build runner.run( subp_exec( - "docker", "build", *args, str(config.parent), input=stdin, verbose=True + "docker", + "build", + *args, + *passthrough, + str(config.parent), + input=stdin, + verbose=True, ) ) @@ -425,56 +332,290 @@ def _build( """, required=True, ) -@click.option( - "--platform", - help="""Target platform(s) to build the docker image for. - - \b - Example: - langgraph build --platform linux/amd64,linux/arm64 - \b - """, -) @click.option( "--base-image", hidden=True, ) -@cli.command(help="Build langgraph API server docker image") +@click.argument("docker_build_args", nargs=-1, type=click.UNPROCESSED) +@cli.command( + help="📦 Build LangGraph API server Docker image.", + context_settings=dict( + ignore_unknown_options=True, + ), +) @log_command def build( config: pathlib.Path, - platform: Optional[str], + docker_build_args: Sequence[str], base_image: Optional[str], pull: bool, tag: str, ): with Runner() as runner, Progress(message="Pulling...") as set: - # check docker available - langgraph_cli.docker.check_capabilities(runner) - # open config + if shutil.which("docker") is None: + raise click.UsageError("Docker not installed") from None with open(config) as f: config_json = langgraph_cli.config.validate_config(json.load(f)) - # build - _build(runner, set, config, config_json, platform, base_image, pull, tag) + _build( + runner, set, config, config_json, base_image, pull, tag, docker_build_args + ) + + +def _get_docker_ignore_content() -> str: + """Return the content of a .dockerignore file. + + This file is used to exclude files and directories from the Docker build context. + + It may be overly broad, but it's better to be safe than sorry. + + The main goal is to exclude .env files by default. + """ + return """\ +# Ignore node_modules and other dependency directories +node_modules +bower_components +vendor + +# Ignore logs and temporary files +*.log +*.tmp +*.swp + +# Ignore .env files and other environment files +.env +.env.* +*.local + +# Ignore git-related files +.git +.gitignore + +# Ignore Docker-related files and configs +.dockerignore +docker-compose.yml + +# Ignore build and cache directories +dist +build +.cache +__pycache__ + +# Ignore IDE and editor configurations +.vscode +.idea +*.sublime-project +*.sublime-workspace +.DS_Store # macOS-specific + +# Ignore test and coverage files +coverage +*.coverage +*.test.js +*.spec.js +tests +""" @OPT_CONFIG @click.argument("save_path", type=click.Path(resolve_path=True)) -@cli.command(help="Generate a Dockerfile for langgraph API server") +@cli.command( + help="🐳 Generate a Dockerfile for the LangGraph API server, with Docker Compose options." +) +@click.option( + # Add a flag for adding a docker-compose.yml file as part of the output + "--add-docker-compose", + help=( + "Add additional files for running the LangGraph API server with " + "docker-compose. These files include a docker-compose.yml, .env file, " + "and a .dockerignore file." + ), + is_flag=True, +) @log_command -def dockerfile(save_path: pathlib.Path, config: pathlib.Path): - with open(config) as f: +def dockerfile(save_path: str, config: pathlib.Path, add_docker_compose: bool) -> None: + save_path = pathlib.Path(save_path).absolute() + secho(f"🔍 Validating configuration at path: {config}", fg="yellow") + with open(config, encoding="utf-8") as f: config_json = langgraph_cli.config.validate_config(json.load(f)) - with open(save_path, "w") as f: + secho("✅ Configuration validated!", fg="green") + + secho(f"📝 Generating Dockerfile at {save_path}", fg="yellow") + with open(str(save_path), "w", encoding="utf-8") as f: f.write( langgraph_cli.config.config_to_docker( config, config_json, - "langchain/langgraphjs-api" - if config_json.get("node_version") - else "langchain/langgraph-api", + ( + "langchain/langgraphjs-api" + if config_json.get("node_version") + else "langchain/langgraph-api" + ), ) ) + secho("✅ Created: Dockerfile", fg="green") + + if add_docker_compose: + # Add docker compose and related files + # Add .dockerignore file in the same directory as the Dockerfile + with open(str(save_path.parent / ".dockerignore"), "w", encoding="utf-8") as f: + f.write(_get_docker_ignore_content()) + secho("✅ Created: .dockerignore", fg="green") + + # Generate a docker-compose.yml file + path = str(save_path.parent / "docker-compose.yml") + with open(path, "w", encoding="utf-8") as f: + with Runner() as runner: + capabilities = langgraph_cli.docker.check_capabilities(runner) + + compose_dict = langgraph_cli.docker.compose_as_dict( + capabilities, + port=8123, + ) + # Add .env file to the docker-compose.yml for the langgraph-api service + compose_dict["services"]["langgraph-api"]["env_file"] = [".env"] + # Add the Dockerfile to the build context + compose_dict["services"]["langgraph-api"]["build"] = { + "context": ".", + "dockerfile": save_path.name, + } + f.write(langgraph_cli.docker.dict_to_yaml(compose_dict)) + secho("✅ Created: docker-compose.yml", fg="green") + + # Check if the .env file exists in the same directory as the Dockerfile + if not (save_path.parent / ".env").exists(): + # Also add an empty .env file + with open(str(save_path.parent / ".env"), "w", encoding="utf-8") as f: + f.writelines( + [ + "# Uncomment the following line to add your LangSmith API key", + "\n", + "# LANGSMITH_API_KEY=your-api-key", + "\n", + "# Or if you have a LangGraph Cloud license key, " + "then uncomment the following line: ", + "\n", + "# LANGGRAPH_CLOUD_LICENSE_KEY=your-license-key", + "\n", + "# Add any other environment variables go below...", + ] + ) + + secho("✅ Created: .env", fg="green") + else: + # Do nothing since the .env file already exists. Not a great + # idea to overwrite in case the user has added custom env vars set + # in the .env file already. + secho("➖ Skipped: .env. It already exists!", fg="yellow") + + secho( + f"🎉 Files generated successfully at path {save_path.parent}!", + fg="cyan", + bold=True, + ) + + +@click.argument("path", required=False) +@click.option( + "--template", + type=str, + help=TEMPLATE_HELP_STRING, +) +@cli.command("new", help="🌱 Create a new LangGraph project from a template.") +@log_command +def new(path: Optional[str], template: Optional[str]) -> None: + """Create a new LangGraph project from a template.""" + return create_new(path, template) + + +@click.option( + "--host", + default="127.0.0.1", + help="Network interface to bind the development server to. Default 127.0.0.1 is recommended for security. Only use 0.0.0.0 in trusted networks", +) +@click.option( + "--port", + default=2024, + type=int, + help="Port number to bind the development server to. Example: langgraph dev --port 8000", +) +@click.option( + "--no-reload", + is_flag=True, + help="Disable automatic reloading when code changes are detected", +) +@click.option( + "--config", + type=click.Path(exists=True), + default="langgraph.json", + help="Path to configuration file declaring dependencies, graphs and environment variables", +) +@click.option( + "--n-jobs-per-worker", + default=None, + type=int, + help="Maximum number of concurrent jobs each worker process can handle. Default: 10", +) +@click.option( + "--no-browser", + is_flag=True, + help="Skip automatically opening the browser when the server starts", +) +@click.option( + "--debug-port", + default=None, + type=int, + help="Enable remote debugging by listening on specified port. Requires debugpy to be installed", +) +@cli.command( + "dev", + help="🏃‍♀️‍➡️ Run LangGraph API server in development mode with hot reloading and debugging support", +) +@log_command +def dev( + host: str, + port: int, + no_reload: bool, + config: str, + n_jobs_per_worker: Optional[int], + no_browser: bool, + debug_port: Optional[int], +): + """CLI entrypoint for running the LangGraph API server.""" + try: + from langgraph_api.cli import run_server + except ImportError: + try: + import pkg_resources + + pkg_resources.require("langgraph-api-inmem") + except (ImportError, pkg_resources.DistributionNotFound): + raise click.UsageError( + "Required package 'langgraph-api-inmem' is not installed.\n" + "Please install it with:\n\n" + ' pip install -U "langgraph-cli[inmem]"\n\n' + "If you're developing the langgraph-cli package locally, you can install in development mode:\n" + " pip install -e ." + ) from None + raise click.UsageError( + "Could not import run_server. This likely means your installation is incomplete.\n" + "Please ensure langgraph-cli is installed with the 'inmem' extra: pip install -U \"langgraph-cli[inmem]\"" + ) from None + + import json + + with open(config, encoding="utf-8") as f: + config_data = json.load(f) + + graphs = config_data.get("graphs", {}) + run_server( + host, + port, + not no_reload, + graphs, + n_jobs_per_worker=n_jobs_per_worker, + open_browser=not no_browser, + debug_port=debug_port, + ) def prepare_args_and_stdin( @@ -510,9 +651,11 @@ def prepare_args_and_stdin( config_path, config, watch=watch, - base_image="langchain/langgraphjs-api" - if config.get("node_version") - else "langchain/langgraph-api", + base_image=( + "langchain/langgraphjs-api" + if config.get("node_version") + else "langchain/langgraph-api" + ), ) return args, stdin @@ -539,9 +682,11 @@ def prepare( subp_exec( "docker", "pull", - f"langchain/langgraphjs-api:{config['node_version']}" - if config.get("node_version") - else f"langchain/langgraph-api:{config['python_version']}", + ( + f"langchain/langgraphjs-api:{config['node_version']}" + if config.get("node_version") + else f"langchain/langgraph-api:{config['python_version']}" + ), verbose=verbose, ) ) diff --git a/libs/cli/langgraph_cli/config.py b/libs/cli/langgraph_cli/config.py index 49c1102b4..16473083f 100644 --- a/libs/cli/langgraph_cli/config.py +++ b/libs/cli/langgraph_cli/config.py @@ -289,17 +289,41 @@ ENV LANGSERVE_GRAPHS='{json.dumps(config["graphs"])}' def node_config_to_docker(config_path: pathlib.Path, config: Config, base_image: str): faux_path = f"/deps/{config_path.parent.name}" + def test_file(file_name): + full_path = config_path.parent / file_name + try: + return full_path.is_file() + except OSError: + return False + + npm, yarn, pnpm = [ + test_file("package-lock.json"), + test_file("yarn.lock"), + test_file("pnpm-lock.yaml"), + ] + + if yarn: + install_cmd = "yarn install --frozen-lockfile" + elif pnpm: + install_cmd = "pnpm i --frozen-lockfile" + elif npm: + install_cmd = "npm ci" + else: + install_cmd = "npm i" + return f"""FROM {base_image}:{config['node_version']} {os.linesep.join(config["dockerfile_lines"])} ADD . {faux_path} -RUN cd {faux_path} && yarn install --frozen-lockfile +RUN cd {faux_path} && {install_cmd} ENV LANGSERVE_GRAPHS='{json.dumps(config["graphs"])}' -WORKDIR {faux_path}""" +WORKDIR {faux_path} + +RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts""" def config_to_docker(config_path: pathlib.Path, config: Config, base_image: str): diff --git a/libs/cli/langgraph_cli/docker.py b/libs/cli/langgraph_cli/docker.py index f28274f4d..5d4c1cddd 100644 --- a/libs/cli/langgraph_cli/docker.py +++ b/libs/cli/langgraph_cli/docker.py @@ -12,34 +12,6 @@ DEFAULT_POSTGRES_URI = ( "postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable" ) -REDIS = """ - langgraph-redis: - image: redis:6 - healthcheck: - test: redis-cli ping - interval: 5s - timeout: 1s - retries: 5 -""" - -DB = """ - langgraph-postgres: - image: postgres:16 - ports: - - "5433:5432" - environment: - POSTGRES_DB: postgres - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - volumes: - - langgraph-data:/var/lib/postgresql/data - healthcheck: - test: pg_isready -U postgres - start_period: 10s - timeout: 1s - retries: 5 -""" - class Version(NamedTuple): major: int @@ -116,28 +88,149 @@ def check_capabilities(runner) -> DockerCapabilities: def debugger_compose( *, port: Optional[int] = None, base_url: Optional[str] = None -) -> str: +) -> dict: if port is None: return "" - compose_str = """ - langgraph-debugger: - image: langchain/langgraph-debugger - restart: on-failure - depends_on: - langgraph-postgres: - condition: service_healthy - ports: - - "{port}:3968" -""" + config = { + "langgraph-debugger": { + "image": "langchain/langgraph-debugger", + "restart": "on-failure", + "depends_on": { + "langgraph-postgres": {"condition": "service_healthy"}, + }, + "ports": [f'"{port}:3968"'], + } + } if base_url: - compose_str += """ - environment: - VITE_STUDIO_LOCAL_GRAPH_URL: {base_url} -""" + config["langgraph-debugger"]["environment"] = { + "VITE_STUDIO_LOCAL_GRAPH_URL": base_url + } - return compose_str.format(port=port, base_url=base_url) + return config + + +# Function to convert dictionary to YAML +def dict_to_yaml(d: dict, *, indent: int = 0) -> str: + """Convert a dictionary to a YAML string.""" + yaml_str = "" + + for idx, (key, value) in enumerate(d.items()): + # Format things in a visually appealing way + # Use an extra newline for top-level keys only + if idx >= 1 and indent < 2: + yaml_str += "\n" + space = " " * indent + if isinstance(value, dict): + yaml_str += f"{space}{key}:\n" + dict_to_yaml(value, indent=indent + 1) + elif isinstance(value, list): + yaml_str += f"{space}{key}:\n" + for item in value: + yaml_str += f"{space} - {item}\n" + else: + yaml_str += f"{space}{key}: {value}\n" + return yaml_str + + +def compose_as_dict( + capabilities: DockerCapabilities, + *, + port: int, + debugger_port: Optional[int] = None, + debugger_base_url: Optional[str] = None, + # postgres://user:password@host:port/database?option=value + postgres_uri: Optional[str] = None, +) -> dict: + """Create a docker compose file as a dictionary in YML style.""" + if postgres_uri is None: + include_db = True + postgres_uri = DEFAULT_POSTGRES_URI + else: + include_db = False + + # The services below are defined in a non-intuitive order to match + # the existing unit tests for this function. + # It's fine to re-order just requires updating the unit tests, so it should + # be done with caution. + + # Define the Redis service first as per the test order + services = { + "langgraph-redis": { + "image": "redis:6", + "healthcheck": { + "test": "redis-cli ping", + "interval": "5s", + "timeout": "1s", + "retries": 5, + }, + } + } + + # Add Postgres service before langgraph-api if it is needed + if include_db: + services["langgraph-postgres"] = { + "image": "postgres:16", + "ports": ['"5433:5432"'], + "environment": { + "POSTGRES_DB": "postgres", + "POSTGRES_USER": "postgres", + "POSTGRES_PASSWORD": "postgres", + }, + "volumes": ["langgraph-data:/var/lib/postgresql/data"], + "healthcheck": { + "test": "pg_isready -U postgres", + "start_period": "10s", + "timeout": "1s", + "retries": 5, + }, + } + if capabilities.healthcheck_start_interval: + services["langgraph-postgres"]["healthcheck"]["interval"] = "60s" + services["langgraph-postgres"]["healthcheck"]["start_interval"] = "1s" + else: + services["langgraph-postgres"]["healthcheck"]["interval"] = "5s" + + # Add optional debugger service if debugger_port is specified + if debugger_port: + services["langgraph-debugger"] = debugger_compose( + port=debugger_port, base_url=debugger_base_url + )["langgraph-debugger"] + + # Add langgraph-api service + services["langgraph-api"] = { + "ports": [f'"{port}:8000"'], + "depends_on": { + "langgraph-redis": {"condition": "service_healthy"}, + }, + "environment": { + "REDIS_URI": "redis://langgraph-redis:6379", + "POSTGRES_URI": postgres_uri, + }, + } + + # If Postgres is included, add it to the dependencies of langgraph-api + if include_db: + services["langgraph-api"]["depends_on"]["langgraph-postgres"] = { + "condition": "service_healthy" + } + + # Additional healthcheck for langgraph-api if required + if capabilities.healthcheck_start_interval: + services["langgraph-api"]["healthcheck"] = { + "test": "python /api/healthcheck.py", + "interval": "60s", + "start_interval": "1s", + "start_period": "10s", + } + + # Final compose dictionary with volumes included if needed + compose_dict = {} + if include_db: + compose_dict["volumes"] = {"langgraph-data": {"driver": "local"}} + compose_dict["services"] = services + + return compose_dict def compose( @@ -149,54 +242,13 @@ def compose( # postgres://user:password@host:port/database?option=value postgres_uri: Optional[str] = None, ) -> str: - if postgres_uri is None: - include_db = True - postgres_uri = DEFAULT_POSTGRES_URI - else: - include_db = False - - db = DB.format() if include_db else "" - volumes = ( - """volumes: - langgraph-data: - driver: local -""" - if include_db - else "" + """Create a docker compose file as a string.""" + compose_content = compose_as_dict( + capabilities, + port=port, + debugger_port=debugger_port, + debugger_base_url=debugger_base_url, + postgres_uri=postgres_uri, ) - if db: - if capabilities.healthcheck_start_interval: - db += """ - interval: 60s - start_interval: 1s""" - else: - db += """ - interval: 5s""" - - compose_str = f"""{volumes}services: -{REDIS} -{db} -{debugger_compose(port=debugger_port, base_url=debugger_base_url)} - langgraph-api: - ports: - - "{port}:8000\" - depends_on: - langgraph-redis: - condition: service_healthy""" - if include_db: - compose_str += """ - langgraph-postgres: - condition: service_healthy""" - compose_str += f""" - environment: - REDIS_URI: redis://langgraph-redis:6379 - POSTGRES_URI: {postgres_uri} -""" - if capabilities.healthcheck_start_interval: - compose_str += """ healthcheck: - test: python /api/healthcheck.py - interval: 60s - start_interval: 1s - start_period: 10s""" - + compose_str = dict_to_yaml(compose_content) return compose_str diff --git a/libs/cli/langgraph_cli/templates.py b/libs/cli/langgraph_cli/templates.py new file mode 100644 index 000000000..fbbc261a5 --- /dev/null +++ b/libs/cli/langgraph_cli/templates.py @@ -0,0 +1,223 @@ +import os +import shutil +import sys +from io import BytesIO +from typing import Dict, Optional +from urllib import error, request +from zipfile import ZipFile + +import click + +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", + "js": "https://github.com/langchain-ai/new-langgraphjs-project/archive/refs/heads/main.zip", + }, + "ReAct Agent": { + "description": "A simple agent that can be flexibly extended to many tools.", + "python": "https://github.com/langchain-ai/react-agent/archive/refs/heads/main.zip", + "js": "https://github.com/langchain-ai/react-agent-js/archive/refs/heads/main.zip", + }, + "Memory Agent": { + "description": "A ReAct-style agent with an additional tool to store memories for use across conversational threads.", + "python": "https://github.com/langchain-ai/memory-agent/archive/refs/heads/main.zip", + "js": "https://github.com/langchain-ai/memory-agent-js/archive/refs/heads/main.zip", + }, + "Retrieval Agent": { + "description": "An agent that includes a retrieval-based question-answering system.", + "python": "https://github.com/langchain-ai/retrieval-agent-template/archive/refs/heads/main.zip", + "js": "https://github.com/langchain-ai/retrieval-agent-template-js/archive/refs/heads/main.zip", + }, + "Data-enrichment Agent": { + "description": "An agent that performs web searches and organizes its findings into a structured format.", + "python": "https://github.com/langchain-ai/data-enrichment/archive/refs/heads/main.zip", + "js": "https://github.com/langchain-ai/data-enrichment-js/archive/refs/heads/main.zip", + }, +} + +# Generate TEMPLATE_IDS programmatically +TEMPLATE_ID_TO_CONFIG = { + f"{name.lower().replace(' ', '-')}-{lang}": (name, lang, url) + for name, versions in TEMPLATES.items() + for lang, url in versions.items() + if lang in {"python", "js"} +} + +TEMPLATE_IDS = list(TEMPLATE_ID_TO_CONFIG.keys()) + +TEMPLATE_HELP_STRING = ( + "The name of the template to use. Available options:\n" + + "\n".join(f"{id_}" for id_ in TEMPLATE_ID_TO_CONFIG) +) + + +def _choose_template() -> str: + """Presents a list of templates to the user and prompts them to select one. + + Returns: + str: The URL of the selected template. + """ + click.secho("🌟 Please select a template:", bold=True, fg="yellow") + for idx, (template_name, template_info) in enumerate(TEMPLATES.items(), 1): + click.secho(f"{idx}. ", nl=False, fg="cyan") + click.secho(template_name, fg="cyan", nl=False) + click.secho(f" - {template_info['description']}", fg="white") + + # Get the template choice from the user, defaulting to the first template if blank + template_choice: Optional[int] = click.prompt( + "Enter the number of your template choice (default is 1)", + type=int, + default=1, + show_default=False, + ) + + template_keys = list(TEMPLATES.keys()) + if 1 <= template_choice <= len(template_keys): + selected_template: str = template_keys[template_choice - 1] + else: + click.secho("❌ Invalid choice. Please try again.", fg="red") + return _choose_template() + + # Prompt the user to choose between Python or JS/TS version + click.secho( + f"\nYou selected: {selected_template} - {TEMPLATES[selected_template]['description']}", + fg="green", + ) + version_choice: int = click.prompt( + "Choose language (1 for Python 🐍, 2 for JS/TS 🌐)", type=int + ) + + if version_choice == 1: + return TEMPLATES[selected_template]["python"] + elif version_choice == 2: + return TEMPLATES[selected_template]["js"] + else: + click.secho("❌ Invalid choice. Please try again.", fg="red") + return _choose_template() + + +def _download_repo_with_requests(repo_url: str, path: str) -> None: + """Download a ZIP archive from the given URL and extracts it to the specified path. + + Args: + repo_url (str): The URL of the repository to download. + path (str): The path where the repository should be extracted. + """ + click.secho("📥 Attempting to download repository as a ZIP archive...", fg="yellow") + click.secho(f"URL: {repo_url}", fg="yellow") + try: + with request.urlopen(repo_url) as response: + if response.status == 200: + with ZipFile(BytesIO(response.read())) as zip_file: + zip_file.extractall(path) + # Move extracted contents to path + for item in os.listdir(path): + if item.endswith("-main"): + extracted_dir = os.path.join(path, item) + for filename in os.listdir(extracted_dir): + shutil.move(os.path.join(extracted_dir, filename), path) + shutil.rmtree(extracted_dir) + click.secho( + f"✅ Downloaded and extracted repository to {path}", fg="green" + ) + except error.HTTPError as e: + click.secho( + f"❌ Error: Failed to download repository.\n" f"Details: {e}\n", + fg="red", + bold=True, + err=True, + ) + sys.exit(1) + + +def _get_template_url(template_name: str) -> Optional[str]: + """ + Retrieves the template URL based on the provided template name. + + Args: + template_name (str): The name of the template. + + Returns: + Optional[str]: The URL of the template if found, else None. + """ + if template_name in TEMPLATES: + click.secho(f"Template selected: {template_name}", fg="green") + version_choice: int = click.prompt( + "Choose version (1 for Python 🐍, 2 for JS/TS 🌐)", type=int + ) + + if version_choice == 1: + return TEMPLATES[template_name]["python"] + elif version_choice == 2: + return TEMPLATES[template_name]["js"] + else: + click.secho("❌ Invalid choice. Please try again.", fg="red") + return None + else: + click.secho( + f"Template '{template_name}' not found. Please select from the available options.", + fg="red", + ) + return None + + +def create_new(path: Optional[str], template: Optional[str]) -> None: + """Create a new LangGraph project at the specified PATH using the chosen TEMPLATE. + + Args: + path (Optional[str]): The path where the new project will be created. + template (Optional[str]): The name of the template to use. + """ + # Prompt for path if not provided + if not path: + path = click.prompt( + "📂 Please specify the path to create the application", default="." + ) + + path = os.path.abspath(path) # Ensure path is absolute + + # Check if path exists and is not empty + if os.path.exists(path) and os.listdir(path): + click.secho( + "❌ The specified directory already exists and is not empty. " + "Aborting to prevent overwriting files.", + fg="red", + bold=True, + ) + sys.exit(1) + + # Get template URL either from command-line argument or + # through interactive selection + if template: + if template not in TEMPLATE_ID_TO_CONFIG: + # Format available options in a readable way with descriptions + template_options = "" + for id_ in TEMPLATE_IDS: + name, lang, _ = TEMPLATE_ID_TO_CONFIG[id_] + description = TEMPLATES[name]["description"] + + # Add each template option with color formatting + template_options += ( + click.style("- ", fg="yellow", bold=True) + + click.style(f"{id_}", fg="cyan") + + click.style(f": {description}", fg="white") + + "\n" + ) + + # Display error message with colors and formatting + click.secho("❌ Error:", fg="red", bold=True, nl=False) + click.secho(f" Template '{template}' not found.", fg="red") + click.secho( + "Please select from the available options:\n", fg="yellow", bold=True + ) + click.secho(template_options, fg="cyan") + sys.exit(1) + _, _, template_url = TEMPLATE_ID_TO_CONFIG[template] + else: + template_url = _choose_template() + + # Download and extract the template + _download_repo_with_requests(template_url, path) + + click.secho(f"🎉 New project created at {path}", fg="green", bold=True) diff --git a/libs/cli/poetry.lock b/libs/cli/poetry.lock index 101f65ea6..f2e6ebd23 100644 --- a/libs/cli/poetry.lock +++ b/libs/cli/poetry.lock @@ -1,4 +1,162 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = true +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "anyio" +version = "4.6.2.post1" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = true +python-versions = ">=3.9" +files = [ + {file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"}, + {file = "anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} + +[package.extras] +doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] +trio = ["trio (>=0.26.1)"] + +[[package]] +name = "certifi" +version = "2024.8.30" +description = "Python package for providing Mozilla's CA Bundle." +optional = true +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = true +python-versions = ">=3.7.0" +files = [ + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, +] [[package]] name = "click" @@ -42,6 +200,21 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "croniter" +version = "5.0.1" +description = "croniter provides iteration for datetime object with cron like format" +optional = true +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.6" +files = [ + {file = "croniter-5.0.1-py2.py3-none-any.whl", hash = "sha256:eb28439742291f6c10b181df1a5ecf421208b1fc62ef44501daec1780a0b09e9"}, + {file = "croniter-5.0.1.tar.gz", hash = "sha256:7d9b1ef25b10eece48fdf29d8ac52f9b6252abff983ac614ade4f3276294019e"}, +] + +[package.dependencies] +python-dateutil = "*" +pytz = ">2021.1" + [[package]] name = "docopt" version = "0.6.2" @@ -66,6 +239,143 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = true +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.7" +description = "A minimal low-level HTTP client." +optional = true +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, + {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] + +[[package]] +name = "httptools" +version = "0.6.4" +description = "A collection of framework independent HTTP protocol utils." +optional = true +python-versions = ">=3.8.0" +files = [ + {file = "httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0"}, + {file = "httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da"}, + {file = "httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1"}, + {file = "httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50"}, + {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959"}, + {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4"}, + {file = "httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c"}, + {file = "httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069"}, + {file = "httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a"}, + {file = "httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975"}, + {file = "httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636"}, + {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721"}, + {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988"}, + {file = "httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17"}, + {file = "httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2"}, + {file = "httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44"}, + {file = "httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1"}, + {file = "httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2"}, + {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81"}, + {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f"}, + {file = "httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970"}, + {file = "httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660"}, + {file = "httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083"}, + {file = "httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3"}, + {file = "httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071"}, + {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5"}, + {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0"}, + {file = "httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8"}, + {file = "httptools-0.6.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d3f0d369e7ffbe59c4b6116a44d6a8eb4783aae027f2c0b366cf0aa964185dba"}, + {file = "httptools-0.6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:94978a49b8f4569ad607cd4946b759d90b285e39c0d4640c6b36ca7a3ddf2efc"}, + {file = "httptools-0.6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dc6a8e399e15ea525305a2ddba998b0af5caa2566bcd79dcbe8948181eeaff"}, + {file = "httptools-0.6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab9ba8dcf59de5181f6be44a77458e45a578fc99c31510b8c65b7d5acc3cf490"}, + {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fc411e1c0a7dcd2f902c7c48cf079947a7e65b5485dea9decb82b9105ca71a43"}, + {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d54efd20338ac52ba31e7da78e4a72570cf729fac82bc31ff9199bedf1dc7440"}, + {file = "httptools-0.6.4-cp38-cp38-win_amd64.whl", hash = "sha256:df959752a0c2748a65ab5387d08287abf6779ae9165916fe053e68ae1fbdc47f"}, + {file = "httptools-0.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85797e37e8eeaa5439d33e556662cc370e474445d5fab24dcadc65a8ffb04003"}, + {file = "httptools-0.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:db353d22843cf1028f43c3651581e4bb49374d85692a85f95f7b9a130e1b2cab"}, + {file = "httptools-0.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ffd262a73d7c28424252381a5b854c19d9de5f56f075445d33919a637e3547"}, + {file = "httptools-0.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c346571fa50d2e9856a37d7cd9435a25e7fd15e236c397bf224afaa355fe9"}, + {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aafe0f1918ed07b67c1e838f950b1c1fabc683030477e60b335649b8020e1076"}, + {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0e563e54979e97b6d13f1bbc05a96109923e76b901f786a5eae36e99c01237bd"}, + {file = "httptools-0.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:b799de31416ecc589ad79dd85a0b2657a8fe39327944998dea368c1d4c9e55e6"}, + {file = "httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c"}, +] + +[package.extras] +test = ["Cython (>=0.29.24)"] + +[[package]] +name = "httpx" +version = "0.27.2" +description = "The next generation HTTP client." +optional = true +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, + {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "httpx-sse" +version = "0.4.0" +description = "Consume Server-Sent Event (SSE) messages with HTTPX." +optional = true +python-versions = ">=3.8" +files = [ + {file = "httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721"}, + {file = "httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f"}, +] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = true +python-versions = ">=3.6" +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + [[package]] name = "iniconfig" version = "2.0.0" @@ -77,6 +387,280 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "jsonpatch" +version = "1.33" +description = "Apply JSON-Patches (RFC 6902)" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, + {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, +] + +[package.dependencies] +jsonpointer = ">=1.9" + +[[package]] +name = "jsonpointer" +version = "3.0.0" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = true +python-versions = ">=3.7" +files = [ + {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, + {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, +] + +[[package]] +name = "jsonschema-rs" +version = "0.26.1" +description = "A high-performance JSON Schema validator for Python" +optional = true +python-versions = ">=3.8" +files = [ + {file = "jsonschema_rs-0.26.1-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5552075a161fd79e25dadd5a15f3708eb2a896e55e22b8622bb092250f3fb677"}, + {file = "jsonschema_rs-0.26.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:619fdc2ed6b771fd884ce717d2d0526e58b7c3ad5852d20dfd2387023b031a7b"}, + {file = "jsonschema_rs-0.26.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2ce2c6ac1f09418e87dafe35c43c21211be8ea60c1bb6c5c2e4c0f71da821bf9"}, + {file = "jsonschema_rs-0.26.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc06f403f15004f891d8dc5bad48b4e8b281c4fcf26c15489183a109e4cca478"}, + {file = "jsonschema_rs-0.26.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85457113acde9982d5be7017046294cc8b0ebcaa56b7b063bab32112396ba2bc"}, + {file = "jsonschema_rs-0.26.1-cp310-none-win32.whl", hash = "sha256:451d40262ce8dc529ec9d3dceb41073b70977b923cf5b4d9f96692f80f6230ad"}, + {file = "jsonschema_rs-0.26.1-cp310-none-win_amd64.whl", hash = "sha256:5c6ddccebae684603a56f647fbf4a348a3cd98841972affa9543220da7da14ac"}, + {file = "jsonschema_rs-0.26.1-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8fb8df6eba2df02347d3e8db88a8350d54a54c7722a3426dd2bf7ca06b7526e2"}, + {file = "jsonschema_rs-0.26.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f20a9aa908027782e005ec5557c90aec723aca98d05d7380183a49595ec1b907"}, + {file = "jsonschema_rs-0.26.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d85c8c05fe1dd8803df2819e1928fcf71e6c465ce22905841a4745cab6e88e22"}, + {file = "jsonschema_rs-0.26.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a593b80cc43eb3d80a908323b18972cbe9287e1e5fa4195f2e876dccb48e8d8"}, + {file = "jsonschema_rs-0.26.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af6a64a9c29b730d9dcb4b212a8dd32bb492aaeb5bac584a2bac7afb21b64ca0"}, + {file = "jsonschema_rs-0.26.1-cp311-none-win32.whl", hash = "sha256:63c494df42f8dd5a96ab89b0f485f0cf315d2a74d9f74d06ae441ca8f0b37aa6"}, + {file = "jsonschema_rs-0.26.1-cp311-none-win_amd64.whl", hash = "sha256:2a4650b87fc8e575716544fe59736c4bf122061dbe38e5c2804685728f922dd5"}, + {file = "jsonschema_rs-0.26.1-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e5fedc9c06832ce2e115fb29c8f812392eabc031f44b8fc998a17fd272b85078"}, + {file = "jsonschema_rs-0.26.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fea71b79b2bf33449571f042530f38c4fd955c221fb8344b1a174b3986a24ef1"}, + {file = "jsonschema_rs-0.26.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9582c29abdd4dd8f90f4ed5ccb4e3fca5e5d80faaf6ee7dd8b9cdfca474a1790"}, + {file = "jsonschema_rs-0.26.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58435b0f316d2a3d4ac8241367980fa66c8ad72783228531d5640e7be891845a"}, + {file = "jsonschema_rs-0.26.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69bf6258f9529ef468cd3b307fdec7216e0233a48b9fde9c6faa946e4024f684"}, + {file = "jsonschema_rs-0.26.1-cp312-none-win32.whl", hash = "sha256:2da5da5456b78488ab17aeef25cbd4ed74962568db4029aa74d024992e74b87a"}, + {file = "jsonschema_rs-0.26.1-cp312-none-win_amd64.whl", hash = "sha256:55ba30c2fcb8e13a9762c5028c779ef77c7e53e19b3ebdfbcd1bead17103dcd7"}, + {file = "jsonschema_rs-0.26.1-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:548be7473f0803a671aae7090c563e051ba1d6dec38d11e516fa1a75f397b9bf"}, + {file = "jsonschema_rs-0.26.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:874ed3bc52ca04490a53fca14f8c0f14c72522ca7359c7ee7e26facd605a4c2c"}, + {file = "jsonschema_rs-0.26.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3e761b451ba8869ff02a563f7302532937f9c6047ce8f85b205c5708a2b34c72"}, + {file = "jsonschema_rs-0.26.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cc08bcccf87fb22cbf70bd73ee181148829c2a21c62bff53e44967e7bce4f3b"}, + {file = "jsonschema_rs-0.26.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc08d65d799a0c877d15b2547a5aa4035a8c11fce4bc12894c4d5d8a64345e17"}, + {file = "jsonschema_rs-0.26.1-cp313-none-win32.whl", hash = "sha256:02b54cb3c6152686d031f8265e1241667a0813ed21073ada0b0f351b193d58ac"}, + {file = "jsonschema_rs-0.26.1-cp313-none-win_amd64.whl", hash = "sha256:fdde9a2bc9c3fba12d8addfce162af8415fadaf6842f733093ec88d8b3ba924f"}, + {file = "jsonschema_rs-0.26.1-cp38-cp38-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:25a65fd50a7a5c81a59deaedf70fd363635f807492be172bc91e3478f41e07b8"}, + {file = "jsonschema_rs-0.26.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b56931b4d4660725cb728fae2c3f4342c04c668e9c050e3ce39b8d1ff783b5a2"}, + {file = "jsonschema_rs-0.26.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8e25d79fc78bfdeed4892be365bf10218dc501996952b54a39985992f3a63021"}, + {file = "jsonschema_rs-0.26.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4708b364ba5308e7104b1991e0eae5092ffb46894817e74646faf0a1ac2eebd"}, + {file = "jsonschema_rs-0.26.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f9552eb5196d1a23ba9ad94c6d2138f4c769ff85a06a398c5bd56ad33c2dc34"}, + {file = "jsonschema_rs-0.26.1-cp38-none-win32.whl", hash = "sha256:0e296388e3a5f82464936c7d530565bfb17560af0bc60da13f7e06f620941d38"}, + {file = "jsonschema_rs-0.26.1-cp38-none-win_amd64.whl", hash = "sha256:000a15187ac8db38013ccdecee3f0dced1942787792a4d35a35d4840e605f842"}, + {file = "jsonschema_rs-0.26.1-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:651ef97aa4033675d45d68aea2276cee2f46c5f38ee938d6cc6a46901b4e9cab"}, + {file = "jsonschema_rs-0.26.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:f59f4bdbba1f74813f1888ecd80828c2a7547b11a5cdcd100e0065eb7a0995f7"}, + {file = "jsonschema_rs-0.26.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3893361d2a97e9d695ac43ff01e3665b5d2fa0e845cd8cfba15d36783a1e6fd4"}, + {file = "jsonschema_rs-0.26.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34b22b0b49565ea0fdf8e776a714c506c51134ca82f5d9b68d17f778075757fa"}, + {file = "jsonschema_rs-0.26.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba1bd5b5cbdc516032eb00f5e1f3b96f2b9fa47eb84fb09055000d0ea27fd61a"}, + {file = "jsonschema_rs-0.26.1-cp39-none-win32.whl", hash = "sha256:8596c26c1a73e58c87004f95892d61118121377d8b608b052c0a194778585b24"}, + {file = "jsonschema_rs-0.26.1-cp39-none-win_amd64.whl", hash = "sha256:4d94e995b870046bb08eaab9f1220dfeda5a1414d6a7016ef72b731349ea148d"}, + {file = "jsonschema_rs-0.26.1.tar.gz", hash = "sha256:c711aedbd1e6911b12780e2a937241946f270590e27de495aa482bc8ce49aaa4"}, +] + +[package.extras] +bench = ["fastjsonschema (>=2.20.0)", "jsonschema (>=4.23.0)", "pytest-benchmark (>=4.0.0)"] +tests = ["flask (>=2.2.5)", "hypothesis (>=6.79.4)", "pytest (>=7.4.4)"] + +[[package]] +name = "langchain-core" +version = "0.3.19" +description = "Building applications with LLMs through composability" +optional = true +python-versions = "<4.0,>=3.9" +files = [ + {file = "langchain_core-0.3.19-py3-none-any.whl", hash = "sha256:562b7cc3c15dfaa9270cb1496990c1f3b3e0b660c4d6a3236d7f693346f2a96c"}, + {file = "langchain_core-0.3.19.tar.gz", hash = "sha256:126d9e8cadb2a5b8d1793a228c0783a3b608e36064d5a2ef1a4d38d07a344523"}, +] + +[package.dependencies] +jsonpatch = ">=1.33,<2.0" +langsmith = ">=0.1.125,<0.2.0" +packaging = ">=23.2,<25" +pydantic = [ + {version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""}, + {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, +] +PyYAML = ">=5.3" +tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" +typing-extensions = ">=4.7" + +[[package]] +name = "langgraph" +version = "0.2.52" +description = "Building stateful, multi-actor applications with LLMs" +optional = true +python-versions = "<4.0,>=3.9.0" +files = [ + {file = "langgraph-0.2.52-py3-none-any.whl", hash = "sha256:4cec56629b833a4f61678d5395719f7a096b16438bf521c48744e9d0579f3559"}, + {file = "langgraph-0.2.52.tar.gz", hash = "sha256:d28b58787b44b880fcdd9479a61a35cc9c273d007eb756ee468fb2f5f72c7738"}, +] + +[package.dependencies] +langchain-core = ">=0.2.43,<0.3.0 || >0.3.0,<0.3.1 || >0.3.1,<0.3.2 || >0.3.2,<0.3.3 || >0.3.3,<0.3.4 || >0.3.4,<0.3.5 || >0.3.5,<0.3.6 || >0.3.6,<0.3.7 || >0.3.7,<0.3.8 || >0.3.8,<0.3.9 || >0.3.9,<0.3.10 || >0.3.10,<0.3.11 || >0.3.11,<0.3.12 || >0.3.12,<0.3.13 || >0.3.13,<0.3.14 || >0.3.14,<0.4.0" +langgraph-checkpoint = ">=2.0.4,<3.0.0" +langgraph-sdk = ">=0.1.32,<0.2.0" + +[[package]] +name = "langgraph-api-inmem" +version = "0.0.3" +description = "" +optional = true +python-versions = "<4.0.0,>=3.9.0" +files = [ + {file = "langgraph_api_inmem-0.0.3-py3-none-any.whl", hash = "sha256:161f76dd916048c59ae2007bea3ab3e066479aeeaef3d348e815908c18af82d0"}, + {file = "langgraph_api_inmem-0.0.3.tar.gz", hash = "sha256:9ca6a4065877967028909fb1b5b9085503d7b0021067a43e663e55c865ff7e2c"}, +] + +[package.dependencies] +croniter = ">=1.0.1" +httptools = ">=0.5.0" +httpx = ">=0.25.0" +jsonschema-rs = ">=0.20.0" +langchain-core = ">=0.2.38,<0.4.0" +langgraph = ">=0.2.52,<0.3.0" +langgraph-checkpoint = ">=2.0.5,<3.0" +langsmith = ">=0.1.63" +orjson = ">=3.9.7" +pycurl = ">=7.45.0" +sse-starlette = ">=2.1.0" +structlog = ">=23.1.0" +tenacity = ">=8.0.0" +tornado = ">=6.4.0" +uvicorn = ">=0.26.0" +uvloop = ">=0.18.0" + +[[package]] +name = "langgraph-checkpoint" +version = "2.0.5" +description = "Library with base interfaces for LangGraph checkpoint savers." +optional = true +python-versions = "<4.0.0,>=3.9.0" +files = [ + {file = "langgraph_checkpoint-2.0.5-py3-none-any.whl", hash = "sha256:0e7e730ea9358577bdcdeb6a17d8f340bad59770e2895a8a7fc853a76e08400b"}, + {file = "langgraph_checkpoint-2.0.5.tar.gz", hash = "sha256:48612cdaf98c40a998079d222abb196a61e504d04dea65c7820d738d42150cac"}, +] + +[package.dependencies] +langchain-core = ">=0.2.38,<0.4" +msgpack = ">=1.1.0,<2.0.0" + +[[package]] +name = "langgraph-sdk" +version = "0.1.36" +description = "SDK for interacting with LangGraph API" +optional = true +python-versions = "<4.0.0,>=3.9.0" +files = [ + {file = "langgraph_sdk-0.1.36-py3-none-any.whl", hash = "sha256:b11e1f0bc67631134d09d50c812dc73f9eb30394764ae1144d7d2a786a715355"}, + {file = "langgraph_sdk-0.1.36.tar.gz", hash = "sha256:2a2c651b7851ba15aeaab7e4e3ea7fd8357ef1cb0b592f264916fa990cdda6e7"}, +] + +[package.dependencies] +httpx = ">=0.25.2" +httpx-sse = ">=0.4.0" +orjson = ">=3.10.1" + +[[package]] +name = "langsmith" +version = "0.1.143" +description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." +optional = true +python-versions = "<4.0,>=3.8.1" +files = [ + {file = "langsmith-0.1.143-py3-none-any.whl", hash = "sha256:ba0d827269e9b03a90fababe41fa3e4e3f833300b95add10184f7e67167dde6f"}, + {file = "langsmith-0.1.143.tar.gz", hash = "sha256:4c5159e5cd84b3f8499433009e72d2076dd2daf6c044ac8a3611b30d0d0161c5"}, +] + +[package.dependencies] +httpx = ">=0.23.0,<1" +orjson = ">=3.9.14,<4.0.0" +pydantic = [ + {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""}, + {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, +] +requests = ">=2,<3" +requests-toolbelt = ">=1.0.0,<2.0.0" + +[[package]] +name = "msgpack" +version = "1.1.0" +description = "MessagePack serializer" +optional = true +python-versions = ">=3.8" +files = [ + {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, + {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, + {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"}, + {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"}, + {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"}, + {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"}, + {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"}, + {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"}, + {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"}, + {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"}, + {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"}, + {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"}, + {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"}, + {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"}, + {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"}, + {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"}, +] + [[package]] name = "mypy" version = "1.10.0" @@ -135,6 +719,73 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "orjson" +version = "3.10.11" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +optional = true +python-versions = ">=3.8" +files = [ + {file = "orjson-3.10.11-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6dade64687f2bd7c090281652fe18f1151292d567a9302b34c2dbb92a3872f1f"}, + {file = "orjson-3.10.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82f07c550a6ccd2b9290849b22316a609023ed851a87ea888c0456485a7d196a"}, + {file = "orjson-3.10.11-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd9a187742d3ead9df2e49240234d728c67c356516cf4db018833a86f20ec18c"}, + {file = "orjson-3.10.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77b0fed6f209d76c1c39f032a70df2d7acf24b1812ca3e6078fd04e8972685a3"}, + {file = "orjson-3.10.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63fc9d5fe1d4e8868f6aae547a7b8ba0a2e592929245fff61d633f4caccdcdd6"}, + {file = "orjson-3.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65cd3e3bb4fbb4eddc3c1e8dce10dc0b73e808fcb875f9fab40c81903dd9323e"}, + {file = "orjson-3.10.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6f67c570602300c4befbda12d153113b8974a3340fdcf3d6de095ede86c06d92"}, + {file = "orjson-3.10.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1f39728c7f7d766f1f5a769ce4d54b5aaa4c3f92d5b84817053cc9995b977acc"}, + {file = "orjson-3.10.11-cp310-none-win32.whl", hash = "sha256:1789d9db7968d805f3d94aae2c25d04014aae3a2fa65b1443117cd462c6da647"}, + {file = "orjson-3.10.11-cp310-none-win_amd64.whl", hash = "sha256:5576b1e5a53a5ba8f8df81872bb0878a112b3ebb1d392155f00f54dd86c83ff6"}, + {file = "orjson-3.10.11-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1444f9cb7c14055d595de1036f74ecd6ce15f04a715e73f33bb6326c9cef01b6"}, + {file = "orjson-3.10.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdec57fe3b4bdebcc08a946db3365630332dbe575125ff3d80a3272ebd0ddafe"}, + {file = "orjson-3.10.11-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4eed32f33a0ea6ef36ccc1d37f8d17f28a1d6e8eefae5928f76aff8f1df85e67"}, + {file = "orjson-3.10.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80df27dd8697242b904f4ea54820e2d98d3f51f91e97e358fc13359721233e4b"}, + {file = "orjson-3.10.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:705f03cee0cb797256d54de6695ef219e5bc8c8120b6654dd460848d57a9af3d"}, + {file = "orjson-3.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03246774131701de8e7059b2e382597da43144a9a7400f178b2a32feafc54bd5"}, + {file = "orjson-3.10.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8b5759063a6c940a69c728ea70d7c33583991c6982915a839c8da5f957e0103a"}, + {file = "orjson-3.10.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:677f23e32491520eebb19c99bb34675daf5410c449c13416f7f0d93e2cf5f981"}, + {file = "orjson-3.10.11-cp311-none-win32.whl", hash = "sha256:a11225d7b30468dcb099498296ffac36b4673a8398ca30fdaec1e6c20df6aa55"}, + {file = "orjson-3.10.11-cp311-none-win_amd64.whl", hash = "sha256:df8c677df2f9f385fcc85ab859704045fa88d4668bc9991a527c86e710392bec"}, + {file = "orjson-3.10.11-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:360a4e2c0943da7c21505e47cf6bd725588962ff1d739b99b14e2f7f3545ba51"}, + {file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:496e2cb45de21c369079ef2d662670a4892c81573bcc143c4205cae98282ba97"}, + {file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7dfa8db55c9792d53c5952900c6a919cfa377b4f4534c7a786484a6a4a350c19"}, + {file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51f3382415747e0dbda9dade6f1e1a01a9d37f630d8c9049a8ed0e385b7a90c0"}, + {file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f35a1b9f50a219f470e0e497ca30b285c9f34948d3c8160d5ad3a755d9299433"}, + {file = "orjson-3.10.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f3b7c5803138e67028dde33450e054c87e0703afbe730c105f1fcd873496d5"}, + {file = "orjson-3.10.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f91d9eb554310472bd09f5347950b24442600594c2edc1421403d7610a0998fd"}, + {file = "orjson-3.10.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dfbb2d460a855c9744bbc8e36f9c3a997c4b27d842f3d5559ed54326e6911f9b"}, + {file = "orjson-3.10.11-cp312-none-win32.whl", hash = "sha256:d4a62c49c506d4d73f59514986cadebb7e8d186ad510c518f439176cf8d5359d"}, + {file = "orjson-3.10.11-cp312-none-win_amd64.whl", hash = "sha256:f1eec3421a558ff7a9b010a6c7effcfa0ade65327a71bb9b02a1c3b77a247284"}, + {file = "orjson-3.10.11-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c46294faa4e4d0eb73ab68f1a794d2cbf7bab33b1dda2ac2959ffb7c61591899"}, + {file = "orjson-3.10.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52e5834d7d6e58a36846e059d00559cb9ed20410664f3ad156cd2cc239a11230"}, + {file = "orjson-3.10.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2fc947e5350fdce548bfc94f434e8760d5cafa97fb9c495d2fef6757aa02ec0"}, + {file = "orjson-3.10.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0efabbf839388a1dab5b72b5d3baedbd6039ac83f3b55736eb9934ea5494d258"}, + {file = "orjson-3.10.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a3f29634260708c200c4fe148e42b4aae97d7b9fee417fbdd74f8cfc265f15b0"}, + {file = "orjson-3.10.11-cp313-none-win32.whl", hash = "sha256:1a1222ffcee8a09476bbdd5d4f6f33d06d0d6642df2a3d78b7a195ca880d669b"}, + {file = "orjson-3.10.11-cp313-none-win_amd64.whl", hash = "sha256:bc274ac261cc69260913b2d1610760e55d3c0801bb3457ba7b9004420b6b4270"}, + {file = "orjson-3.10.11-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:19b3763e8bbf8ad797df6b6b5e0fc7c843ec2e2fc0621398534e0c6400098f87"}, + {file = "orjson-3.10.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1be83a13312e5e58d633580c5eb8d0495ae61f180da2722f20562974188af205"}, + {file = "orjson-3.10.11-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:afacfd1ab81f46dedd7f6001b6d4e8de23396e4884cd3c3436bd05defb1a6446"}, + {file = "orjson-3.10.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb4d0bea56bba596723d73f074c420aec3b2e5d7d30698bc56e6048066bd560c"}, + {file = "orjson-3.10.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96ed1de70fcb15d5fed529a656df29f768187628727ee2788344e8a51e1c1350"}, + {file = "orjson-3.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4bfb30c891b530f3f80e801e3ad82ef150b964e5c38e1fb8482441c69c35c61c"}, + {file = "orjson-3.10.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d496c74fc2b61341e3cefda7eec21b7854c5f672ee350bc55d9a4997a8a95204"}, + {file = "orjson-3.10.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:655a493bac606655db9a47fe94d3d84fc7f3ad766d894197c94ccf0c5408e7d3"}, + {file = "orjson-3.10.11-cp38-none-win32.whl", hash = "sha256:b9546b278c9fb5d45380f4809e11b4dd9844ca7aaf1134024503e134ed226161"}, + {file = "orjson-3.10.11-cp38-none-win_amd64.whl", hash = "sha256:b592597fe551d518f42c5a2eb07422eb475aa8cfdc8c51e6da7054b836b26782"}, + {file = "orjson-3.10.11-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c95f2ecafe709b4e5c733b5e2768ac569bed308623c85806c395d9cca00e08af"}, + {file = "orjson-3.10.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80c00d4acded0c51c98754fe8218cb49cb854f0f7eb39ea4641b7f71732d2cb7"}, + {file = "orjson-3.10.11-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:461311b693d3d0a060439aa669c74f3603264d4e7a08faa68c47ae5a863f352d"}, + {file = "orjson-3.10.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52ca832f17d86a78cbab86cdc25f8c13756ebe182b6fc1a97d534051c18a08de"}, + {file = "orjson-3.10.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c57ea78a753812f528178aa2f1c57da633754c91d2124cb28991dab4c79a54"}, + {file = "orjson-3.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7fcfc6f7ca046383fb954ba528587e0f9336828b568282b27579c49f8e16aad"}, + {file = "orjson-3.10.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:86b9dd983857970c29e4c71bb3e95ff085c07d3e83e7c46ebe959bac07ebd80b"}, + {file = "orjson-3.10.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4d83f87582d223e54efb2242a79547611ba4ebae3af8bae1e80fa9a0af83bb7f"}, + {file = "orjson-3.10.11-cp39-none-win32.whl", hash = "sha256:9fd0ad1c129bc9beb1154c2655f177620b5beaf9a11e0d10bac63ef3fce96950"}, + {file = "orjson-3.10.11-cp39-none-win_amd64.whl", hash = "sha256:10f416b2a017c8bd17f325fb9dee1fb5cdd7a54e814284896b7c3f2763faa017"}, + {file = "orjson-3.10.11.tar.gz", hash = "sha256:e35b6d730de6384d5b2dab5fd23f0d76fae8bbc8c353c2f78210aa5fa4beb3ef"}, +] + [[package]] name = "packaging" version = "23.2" @@ -161,6 +812,282 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pycurl" +version = "7.45.3" +description = "PycURL -- A Python Interface To The cURL library" +optional = true +python-versions = ">=3.5" +files = [ + {file = "pycurl-7.45.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86f66d334deaaab20a576fb785587566081407adc703318203fe26e43277ef12"}, + {file = "pycurl-7.45.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:205983e87d6aa0b6e93ec7320060de44efaa905ecc5d13f70cbe38c65684c5c4"}, + {file = "pycurl-7.45.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fbd4a6b8654b779089c5a44af1c65c1419c2cd60718780df6d8f354eb35d6d55"}, + {file = "pycurl-7.45.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:5ebc6a0ac60c371a9efaf7d55dec5820f76fdafb43a3be1e390011339dc329ae"}, + {file = "pycurl-7.45.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:2facab1c35600088cb82b5b093bd700bfbd1e3191deab24f7d1803d9dc5b76fc"}, + {file = "pycurl-7.45.3-cp310-cp310-win32.whl", hash = "sha256:7cfca02d70579853041063e53ca713d31161b8831b98d4f68c3554dc0448beec"}, + {file = "pycurl-7.45.3-cp310-cp310-win_amd64.whl", hash = "sha256:8451e8475051f16eb4776380384699cb8ddd10ea8410bcbfaee5a6fc4c046de6"}, + {file = "pycurl-7.45.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1610cc45b5bc8b39bc18b981d0473e59ef41226ee467eaa8fbfc7276603ef5af"}, + {file = "pycurl-7.45.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c854885398410fa6e88fc29f7a420a3c13b88bae9b4e10a804437b582e24f58b"}, + {file = "pycurl-7.45.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:921c9db0c3128481954f625b3b1bc10c730100aa944d54643528f716676439ee"}, + {file = "pycurl-7.45.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:483f3aa5d1bc8cff5657ad96f68e1d89281f971a7b6aa93408a31e3199981ea9"}, + {file = "pycurl-7.45.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1e0d32d6ed3a7ba13dbbd3a6fb50ca76c40c70e6bc6fe347f90677478d3422c7"}, + {file = "pycurl-7.45.3-cp311-cp311-win32.whl", hash = "sha256:beaaa4450e23d41dd0c2f2f47a4f8a171210271543550c2c556090c7eeea88f5"}, + {file = "pycurl-7.45.3-cp311-cp311-win_amd64.whl", hash = "sha256:dd33fd9de8907a6275c70113124aeb7eea672c1324f5d5423f203738b341697d"}, + {file = "pycurl-7.45.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0c41a172d5e8a5cdd8328cc8134f47b2a57960ac677f7cda8520eaa9fbe7d990"}, + {file = "pycurl-7.45.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:13006b62c157bb4483c58e1abdced6df723c9399255a4f5f6bb7f8e425106679"}, + {file = "pycurl-7.45.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27f4c5c20c86a9a823677316724306fb1ce3b25ec568efd52026dc6c563e5b29"}, + {file = "pycurl-7.45.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c2c246bc29e8762ff4c8a833ac5b4da4c797d16ab138286e8aec9b0c0a0da2d4"}, + {file = "pycurl-7.45.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3d07c5daef2d0d85949e32ec254ee44232bb57febb0634194379dd14d1ff4f87"}, + {file = "pycurl-7.45.3-cp312-cp312-win32.whl", hash = "sha256:9f7afe5ef0e4750ac4515baebc251ee94aaefe5de6e2e8a24668473128d69904"}, + {file = "pycurl-7.45.3-cp312-cp312-win_amd64.whl", hash = "sha256:3648ed9a57a6b704673faeab3dc64d1469cc69f2bc1ed8227ffa0f84e147c500"}, + {file = "pycurl-7.45.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c0915ea139f66a289edc4f9de10cb45078af1bb950491c5612969864236a2e7e"}, + {file = "pycurl-7.45.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:43c5e61a58783ddf78ef84949f6bb6e52e092a13ec67678e9a9e21071ecf5b80"}, + {file = "pycurl-7.45.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bf613844a1647fe3d2bba1f5c9c96a62a85280123a57a8a0c8d2f37d518bc10a"}, + {file = "pycurl-7.45.3-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:936afd9c5ff7fe7457065e878a279811787778f472f9a4e8c5df79e7728358e2"}, + {file = "pycurl-7.45.3-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:dbf816a6d0cb71e7fd06609246bbea4eaf100649d9decf49e4eb329594f70be7"}, + {file = "pycurl-7.45.3-cp38-cp38-win32.whl", hash = "sha256:2c8a2ce568193f9f84763717d8961cec0db4ec1aa08c6bcf4d90da5eb72bec86"}, + {file = "pycurl-7.45.3-cp38-cp38-win_amd64.whl", hash = "sha256:80ac7c17e69ca6b76ccccb4255f7c29a2a36e5b69eb10c2adba82135d43afe8c"}, + {file = "pycurl-7.45.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fa7751b614d9aa82d7a0f49ca90924c29c6cedf85a2f8687fb6a772dbfe48711"}, + {file = "pycurl-7.45.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b129e9ee07f80b4af957607917af46ab517b0c4e746692f6d9e50e973edba8d8"}, + {file = "pycurl-7.45.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a0f920582b8713ca87d5a288a7532607bc4454275d733fc880650d602dbe3c67"}, + {file = "pycurl-7.45.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c7c13e4268550cde14a6f4743cc8bd8c035d4cd36514d58eff70276d68954b6f"}, + {file = "pycurl-7.45.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:0f0e1251a608ffd75fc502f4014442e554c67d3d7a1b0a839c35efb6ad2f8bf8"}, + {file = "pycurl-7.45.3-cp39-cp39-win32.whl", hash = "sha256:51a40a56c58e63dac6145829f9e9bd66e5867a9f0741bcb9ffefab619851d44f"}, + {file = "pycurl-7.45.3-cp39-cp39-win_amd64.whl", hash = "sha256:e08a06802c8c8a9d04cf3319f9230ec09062c55d2550bd48f8ada1df1431adcf"}, + {file = "pycurl-7.45.3.tar.gz", hash = "sha256:8c2471af9079ad798e1645ec0b0d3d4223db687379d17dd36a70637449f81d6b"}, +] + +[[package]] +name = "pydantic" +version = "2.7.4" +description = "Data validation using Python type hints" +optional = true +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.7.4-py3-none-any.whl", hash = "sha256:ee8538d41ccb9c0a9ad3e0e5f07bf15ed8015b481ced539a1759d8cc89ae90d0"}, + {file = "pydantic-2.7.4.tar.gz", hash = "sha256:0c84efd9548d545f63ac0060c1e4d39bb9b14db8b3c0652338aecc07b5adec52"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.18.4" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic" +version = "2.9.2" +description = "Data validation using Python type hints" +optional = true +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, + {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.23.4" +typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] + +[[package]] +name = "pydantic-core" +version = "2.18.4" +description = "Core functionality for Pydantic validation and serialization" +optional = true +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.18.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f76d0ad001edd426b92233d45c746fd08f467d56100fd8f30e9ace4b005266e4"}, + {file = "pydantic_core-2.18.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59ff3e89f4eaf14050c8022011862df275b552caef8082e37b542b066ce1ff26"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a55b5b16c839df1070bc113c1f7f94a0af4433fcfa1b41799ce7606e5c79ce0a"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d0dcc59664fcb8974b356fe0a18a672d6d7cf9f54746c05f43275fc48636851"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8951eee36c57cd128f779e641e21eb40bc5073eb28b2d23f33eb0ef14ffb3f5d"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4701b19f7e3a06ea655513f7938de6f108123bf7c86bbebb1196eb9bd35cf724"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00a3f196329e08e43d99b79b286d60ce46bed10f2280d25a1718399457e06be"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:97736815b9cc893b2b7f663628e63f436018b75f44854c8027040e05230eeddb"}, + {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6891a2ae0e8692679c07728819b6e2b822fb30ca7445f67bbf6509b25a96332c"}, + {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bc4ff9805858bd54d1a20efff925ccd89c9d2e7cf4986144b30802bf78091c3e"}, + {file = "pydantic_core-2.18.4-cp310-none-win32.whl", hash = "sha256:1b4de2e51bbcb61fdebd0ab86ef28062704f62c82bbf4addc4e37fa4b00b7cbc"}, + {file = "pydantic_core-2.18.4-cp310-none-win_amd64.whl", hash = "sha256:6a750aec7bf431517a9fd78cb93c97b9b0c496090fee84a47a0d23668976b4b0"}, + {file = "pydantic_core-2.18.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:942ba11e7dfb66dc70f9ae66b33452f51ac7bb90676da39a7345e99ffb55402d"}, + {file = "pydantic_core-2.18.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b2ebef0e0b4454320274f5e83a41844c63438fdc874ea40a8b5b4ecb7693f1c4"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a642295cd0c8df1b86fc3dced1d067874c353a188dc8e0f744626d49e9aa51c4"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f09baa656c904807e832cf9cce799c6460c450c4ad80803517032da0cd062e2"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98906207f29bc2c459ff64fa007afd10a8c8ac080f7e4d5beff4c97086a3dabd"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19894b95aacfa98e7cb093cd7881a0c76f55731efad31073db4521e2b6ff5b7d"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fbbdc827fe5e42e4d196c746b890b3d72876bdbf160b0eafe9f0334525119c8"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f85d05aa0918283cf29a30b547b4df2fbb56b45b135f9e35b6807cb28bc47951"}, + {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e85637bc8fe81ddb73fda9e56bab24560bdddfa98aa64f87aaa4e4b6730c23d2"}, + {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2f5966897e5461f818e136b8451d0551a2e77259eb0f73a837027b47dc95dab9"}, + {file = "pydantic_core-2.18.4-cp311-none-win32.whl", hash = "sha256:44c7486a4228413c317952e9d89598bcdfb06399735e49e0f8df643e1ccd0558"}, + {file = "pydantic_core-2.18.4-cp311-none-win_amd64.whl", hash = "sha256:8a7164fe2005d03c64fd3b85649891cd4953a8de53107940bf272500ba8a788b"}, + {file = "pydantic_core-2.18.4-cp311-none-win_arm64.whl", hash = "sha256:4e99bc050fe65c450344421017f98298a97cefc18c53bb2f7b3531eb39bc7805"}, + {file = "pydantic_core-2.18.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6f5c4d41b2771c730ea1c34e458e781b18cc668d194958e0112455fff4e402b2"}, + {file = "pydantic_core-2.18.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fdf2156aa3d017fddf8aea5adfba9f777db1d6022d392b682d2a8329e087cef"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4748321b5078216070b151d5271ef3e7cc905ab170bbfd27d5c83ee3ec436695"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847a35c4d58721c5dc3dba599878ebbdfd96784f3fb8bb2c356e123bdcd73f34"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c40d4eaad41f78e3bbda31b89edc46a3f3dc6e171bf0ecf097ff7a0ffff7cb1"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21a5e440dbe315ab9825fcd459b8814bb92b27c974cbc23c3e8baa2b76890077"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01dd777215e2aa86dfd664daed5957704b769e726626393438f9c87690ce78c3"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b06beb3b3f1479d32befd1f3079cc47b34fa2da62457cdf6c963393340b56e9"}, + {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:564d7922e4b13a16b98772441879fcdcbe82ff50daa622d681dd682175ea918c"}, + {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0eb2a4f660fcd8e2b1c90ad566db2b98d7f3f4717c64fe0a83e0adb39766d5b8"}, + {file = "pydantic_core-2.18.4-cp312-none-win32.whl", hash = "sha256:8b8bab4c97248095ae0c4455b5a1cd1cdd96e4e4769306ab19dda135ea4cdb07"}, + {file = "pydantic_core-2.18.4-cp312-none-win_amd64.whl", hash = "sha256:14601cdb733d741b8958224030e2bfe21a4a881fb3dd6fbb21f071cabd48fa0a"}, + {file = "pydantic_core-2.18.4-cp312-none-win_arm64.whl", hash = "sha256:c1322d7dd74713dcc157a2b7898a564ab091ca6c58302d5c7b4c07296e3fd00f"}, + {file = "pydantic_core-2.18.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:823be1deb01793da05ecb0484d6c9e20baebb39bd42b5d72636ae9cf8350dbd2"}, + {file = "pydantic_core-2.18.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebef0dd9bf9b812bf75bda96743f2a6c5734a02092ae7f721c048d156d5fabae"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae1d6df168efb88d7d522664693607b80b4080be6750c913eefb77e34c12c71a"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9899c94762343f2cc2fc64c13e7cae4c3cc65cdfc87dd810a31654c9b7358cc"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99457f184ad90235cfe8461c4d70ab7dd2680e28821c29eca00252ba90308c78"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18f469a3d2a2fdafe99296a87e8a4c37748b5080a26b806a707f25a902c040a8"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cdf28938ac6b8b49ae5e92f2735056a7ba99c9b110a474473fd71185c1af5d"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:938cb21650855054dc54dfd9120a851c974f95450f00683399006aa6e8abb057"}, + {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:44cd83ab6a51da80fb5adbd9560e26018e2ac7826f9626bc06ca3dc074cd198b"}, + {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:972658f4a72d02b8abfa2581d92d59f59897d2e9f7e708fdabe922f9087773af"}, + {file = "pydantic_core-2.18.4-cp38-none-win32.whl", hash = "sha256:1d886dc848e60cb7666f771e406acae54ab279b9f1e4143babc9c2258213daa2"}, + {file = "pydantic_core-2.18.4-cp38-none-win_amd64.whl", hash = "sha256:bb4462bd43c2460774914b8525f79b00f8f407c945d50881568f294c1d9b4443"}, + {file = "pydantic_core-2.18.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:44a688331d4a4e2129140a8118479443bd6f1905231138971372fcde37e43528"}, + {file = "pydantic_core-2.18.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a2fdd81edd64342c85ac7cf2753ccae0b79bf2dfa063785503cb85a7d3593223"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86110d7e1907ab36691f80b33eb2da87d780f4739ae773e5fc83fb272f88825f"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46387e38bd641b3ee5ce247563b60c5ca098da9c56c75c157a05eaa0933ed154"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:123c3cec203e3f5ac7b000bd82235f1a3eced8665b63d18be751f115588fea30"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc1803ac5c32ec324c5261c7209e8f8ce88e83254c4e1aebdc8b0a39f9ddb443"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53db086f9f6ab2b4061958d9c276d1dbe3690e8dd727d6abf2321d6cce37fa94"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abc267fa9837245cc28ea6929f19fa335f3dc330a35d2e45509b6566dc18be23"}, + {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0d829524aaefdebccb869eed855e2d04c21d2d7479b6cada7ace5448416597b"}, + {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:509daade3b8649f80d4e5ff21aa5673e4ebe58590b25fe42fac5f0f52c6f034a"}, + {file = "pydantic_core-2.18.4-cp39-none-win32.whl", hash = "sha256:ca26a1e73c48cfc54c4a76ff78df3727b9d9f4ccc8dbee4ae3f73306a591676d"}, + {file = "pydantic_core-2.18.4-cp39-none-win_amd64.whl", hash = "sha256:c67598100338d5d985db1b3d21f3619ef392e185e71b8d52bceacc4a7771ea7e"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:574d92eac874f7f4db0ca653514d823a0d22e2354359d0759e3f6a406db5d55d"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1f4d26ceb5eb9eed4af91bebeae4b06c3fb28966ca3a8fb765208cf6b51102ab"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77450e6d20016ec41f43ca4a6c63e9fdde03f0ae3fe90e7c27bdbeaece8b1ed4"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d323a01da91851a4f17bf592faf46149c9169d68430b3146dcba2bb5e5719abc"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43d447dd2ae072a0065389092a231283f62d960030ecd27565672bd40746c507"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:578e24f761f3b425834f297b9935e1ce2e30f51400964ce4801002435a1b41ef"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:81b5efb2f126454586d0f40c4d834010979cb80785173d1586df845a632e4e6d"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ab86ce7c8f9bea87b9d12c7f0af71102acbf5ecbc66c17796cff45dae54ef9a5"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:90afc12421df2b1b4dcc975f814e21bc1754640d502a2fbcc6d41e77af5ec312"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:51991a89639a912c17bef4b45c87bd83593aee0437d8102556af4885811d59f5"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:293afe532740370aba8c060882f7d26cfd00c94cae32fd2e212a3a6e3b7bc15e"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48ece5bde2e768197a2d0f6e925f9d7e3e826f0ad2271120f8144a9db18d5c8"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eae237477a873ab46e8dd748e515c72c0c804fb380fbe6c85533c7de51f23a8f"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:834b5230b5dfc0c1ec37b2fda433b271cbbc0e507560b5d1588e2cc1148cf1ce"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e858ac0a25074ba4bce653f9b5d0a85b7456eaddadc0ce82d3878c22489fa4ee"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2fd41f6eff4c20778d717af1cc50eca52f5afe7805ee530a4fbd0bae284f16e9"}, + {file = "pydantic_core-2.18.4.tar.gz", hash = "sha256:ec3beeada09ff865c344ff3bc2f427f5e6c26401cc6113d77e372c3fdac73864"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pydantic-core" +version = "2.23.4" +description = "Core functionality for Pydantic validation and serialization" +optional = true +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, + {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, + {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, + {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, + {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, + {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, + {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, + {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, + {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, + {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, + {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, + {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, + {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, + {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, + {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, + {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, + {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + [[package]] name = "pytest" version = "7.4.3" @@ -234,6 +1161,128 @@ docopt = ">=0.4.0" pytest = ">=2.6.4" watchdog = ">=0.6.0" +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = true +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2024.2" +description = "World timezone definitions, modern and historical" +optional = true +python-versions = "*" +files = [ + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = true +python-versions = ">=3.8" +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = true +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + [[package]] name = "ruff" version = "0.6.2" @@ -261,6 +1310,97 @@ files = [ {file = "ruff-0.6.2.tar.gz", hash = "sha256:239ee6beb9e91feb8e0ec384204a763f36cb53fb895a1a364618c6abb076b3be"}, ] +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = true +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "sse-starlette" +version = "2.1.3" +description = "SSE plugin for Starlette" +optional = true +python-versions = ">=3.8" +files = [ + {file = "sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772"}, + {file = "sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169"}, +] + +[package.dependencies] +anyio = "*" +starlette = "*" +uvicorn = "*" + +[package.extras] +examples = ["fastapi"] + +[[package]] +name = "starlette" +version = "0.41.3" +description = "The little ASGI library that shines." +optional = true +python-versions = ">=3.8" +files = [ + {file = "starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7"}, + {file = "starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] + +[[package]] +name = "structlog" +version = "24.4.0" +description = "Structured Logging for Python" +optional = true +python-versions = ">=3.8" +files = [ + {file = "structlog-24.4.0-py3-none-any.whl", hash = "sha256:597f61e80a91cc0749a9fd2a098ed76715a1c8a01f73e336b746504d1aad7610"}, + {file = "structlog-24.4.0.tar.gz", hash = "sha256:b27bfecede327a6d2da5fbc96bd859f114ecc398a6389d664f62085ee7ae6fc4"}, +] + +[package.extras] +dev = ["freezegun (>=0.2.8)", "mypy (>=1.4)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "rich", "simplejson", "twisted"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "sphinxext-opengraph", "twisted"] +tests = ["freezegun (>=0.2.8)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "simplejson"] +typing = ["mypy (>=1.4)", "rich", "twisted"] + +[[package]] +name = "tenacity" +version = "9.0.0" +description = "Retry code until it succeeds" +optional = true +python-versions = ">=3.8" +files = [ + {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, + {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, +] + +[package.extras] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] + [[package]] name = "tomli" version = "2.0.1" @@ -272,6 +1412,26 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "tornado" +version = "6.4.1" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = true +python-versions = ">=3.8" +files = [ + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"}, + {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"}, + {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"}, + {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"}, +] + [[package]] name = "typing-extensions" version = "4.12.0" @@ -283,6 +1443,93 @@ files = [ {file = "typing_extensions-4.12.0.tar.gz", hash = "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8"}, ] +[[package]] +name = "urllib3" +version = "2.2.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = true +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "uvicorn" +version = "0.32.0" +description = "The lightning-fast ASGI server." +optional = true +python-versions = ">=3.8" +files = [ + {file = "uvicorn-0.32.0-py3-none-any.whl", hash = "sha256:60b8f3a5ac027dcd31448f411ced12b5ef452c646f76f02f8cc3f25d8d26fd82"}, + {file = "uvicorn-0.32.0.tar.gz", hash = "sha256:f78b36b143c16f54ccdb8190d0a26b5f1901fe5a3c777e1ab29f26391af8551e"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" +typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "uvloop" +version = "0.21.0" +description = "Fast implementation of asyncio event loop on top of libuv" +optional = true +python-versions = ">=3.8.0" +files = [ + {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f"}, + {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d"}, + {file = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26"}, + {file = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb"}, + {file = "uvloop-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f"}, + {file = "uvloop-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c"}, + {file = "uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8"}, + {file = "uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0"}, + {file = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e"}, + {file = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb"}, + {file = "uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6"}, + {file = "uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d"}, + {file = "uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c"}, + {file = "uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2"}, + {file = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d"}, + {file = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc"}, + {file = "uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb"}, + {file = "uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f"}, + {file = "uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281"}, + {file = "uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af"}, + {file = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6"}, + {file = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816"}, + {file = "uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc"}, + {file = "uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553"}, + {file = "uvloop-0.21.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:17df489689befc72c39a08359efac29bbee8eee5209650d4b9f34df73d22e414"}, + {file = "uvloop-0.21.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc09f0ff191e61c2d592a752423c767b4ebb2986daa9ed62908e2b1b9a9ae206"}, + {file = "uvloop-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0ce1b49560b1d2d8a2977e3ba4afb2414fb46b86a1b64056bc4ab929efdafbe"}, + {file = "uvloop-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e678ad6fe52af2c58d2ae3c73dc85524ba8abe637f134bf3564ed07f555c5e79"}, + {file = "uvloop-0.21.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:460def4412e473896ef179a1671b40c039c7012184b627898eea5072ef6f017a"}, + {file = "uvloop-0.21.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:10da8046cc4a8f12c91a1c39d1dd1585c41162a15caaef165c2174db9ef18bdc"}, + {file = "uvloop-0.21.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c097078b8031190c934ed0ebfee8cc5f9ba9642e6eb88322b9958b649750f72b"}, + {file = "uvloop-0.21.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46923b0b5ee7fc0020bef24afe7836cb068f5050ca04caf6b487c513dc1a20b2"}, + {file = "uvloop-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53e420a3afe22cdcf2a0f4846e377d16e718bc70103d7088a4f7623567ba5fb0"}, + {file = "uvloop-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88cb67cdbc0e483da00af0b2c3cdad4b7c61ceb1ee0f33fe00e09c81e3a6cb75"}, + {file = "uvloop-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:221f4f2a1f46032b403bf3be628011caf75428ee3cc204a22addf96f586b19fd"}, + {file = "uvloop-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2d1f581393673ce119355d56da84fe1dd9d2bb8b3d13ce792524e1607139feff"}, + {file = "uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3"}, +] + +[package.extras] +dev = ["Cython (>=3.0,<4.0)", "setuptools (>=60)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["aiohttp (>=3.10.5)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] + [[package]] name = "watchdog" version = "3.0.0" @@ -322,7 +1569,10 @@ files = [ [package.extras] watchmedo = ["PyYAML (>=3.10)"] +[extras] +inmem = ["langgraph-api-inmem"] + [metadata] lock-version = "2.0" python-versions = "^3.9.0,<4.0" -content-hash = "a1b0cc1de3e63b8d419342e311cd1e32b81606e89d3f8cf448be560b5727c202" +content-hash = "5a3dc8012db6a4cd103de557563e23f92b825caddd1d83c838282211a7aab4e3" diff --git a/libs/cli/pyproject.toml b/libs/cli/pyproject.toml index 20fe010d9..217e3762e 100644 --- a/libs/cli/pyproject.toml +++ b/libs/cli/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langgraph-cli" -version = "0.1.52" +version = "0.1.55" description = "CLI for interacting with LangGraph API" authors = [] license = "MIT" @@ -14,6 +14,7 @@ langgraph = "langgraph_cli.cli:cli" [tool.poetry.dependencies] python = "^3.9.0,<4.0" click = "^8.1.7" +langgraph-api-inmem = { version = ">=0.0.3,<0.1.0", optional = true } [tool.poetry.group.dev.dependencies] ruff = "^0.6.2" @@ -24,6 +25,9 @@ pytest-mock = "^3.11.1" pytest-watch = "^4.2.0" mypy = "^1.10.0" +[tool.poetry.extras] +inmem = ["langgraph-api-inmem"] + [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 diff --git a/libs/cli/tests/integration_tests/__init__.py b/libs/cli/tests/integration_tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/libs/cli/tests/integration_tests/test_cli.py b/libs/cli/tests/integration_tests/test_cli.py new file mode 100644 index 000000000..7cb41b47b --- /dev/null +++ b/libs/cli/tests/integration_tests/test_cli.py @@ -0,0 +1,13 @@ +import pytest +import requests + +from langgraph_cli.templates import TEMPLATE_ID_TO_CONFIG + + +@pytest.mark.parametrize("template_key", TEMPLATE_ID_TO_CONFIG.keys()) +def test_template_urls_work(template_key: str) -> None: + """Integration test to verify that all template URLs are reachable.""" + _, _, template_url = TEMPLATE_ID_TO_CONFIG[template_key] + response = requests.head(template_url) + # Returns 302 on a successful HEAD request + assert response.status_code == 302, f"URL {template_url} is not reachable." diff --git a/libs/cli/tests/unit_tests/cli/__init__.py b/libs/cli/tests/unit_tests/cli/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/libs/cli/tests/unit_tests/cli/test_cli.py b/libs/cli/tests/unit_tests/cli/test_cli.py new file mode 100644 index 000000000..52e2b682f --- /dev/null +++ b/libs/cli/tests/unit_tests/cli/test_cli.py @@ -0,0 +1,244 @@ +import json +import pathlib +import shutil +import tempfile +from contextlib import contextmanager +from pathlib import Path + +from click.testing import CliRunner + +from langgraph_cli.cli import cli, prepare_args_and_stdin +from langgraph_cli.config import Config, validate_config +from langgraph_cli.docker import DEFAULT_POSTGRES_URI, DockerCapabilities, Version +from langgraph_cli.util import clean_empty_lines + +DEFAULT_DOCKER_CAPABILITIES = DockerCapabilities( + version_docker=Version(26, 1, 1), + version_compose=Version(2, 27, 0), + healthcheck_start_interval=True, +) + + +@contextmanager +def temporary_config_folder(config_content: dict): + # Create a temporary directory + temp_dir = tempfile.mkdtemp() + try: + # Define the path for the config.json file + config_path = Path(temp_dir) / "config.json" + + # Write the provided dictionary content to config.json + with open(config_path, "w", encoding="utf-8") as config_file: + json.dump(config_content, config_file) + + # Yield the temporary directory path for use within the context + yield config_path.parent + finally: + # Cleanup the temporary directory and its contents + shutil.rmtree(temp_dir) + + +def test_prepare_args_and_stdin() -> None: + # this basically serves as an end-to-end test for using config and docker helpers + config_path = pathlib.Path("./langgraph.json") + config = validate_config( + Config(dependencies=["."], graphs={"agent": "agent.py:graph"}) + ) + port = 8000 + debugger_port = 8001 + debugger_graph_url = f"http://127.0.0.1:{port}" + + actual_args, actual_stdin = prepare_args_and_stdin( + capabilities=DEFAULT_DOCKER_CAPABILITIES, + config_path=config_path, + config=config, + docker_compose=pathlib.Path("custom-docker-compose.yml"), + port=port, + debugger_port=debugger_port, + debugger_base_url=debugger_graph_url, + watch=True, + ) + + expected_args = [ + "--project-directory", + ".", + "-f", + "custom-docker-compose.yml", + "-f", + "-", + ] + expected_stdin = f"""volumes: + langgraph-data: + driver: local +services: + langgraph-redis: + image: redis:6 + healthcheck: + test: redis-cli ping + interval: 5s + timeout: 1s + retries: 5 + langgraph-postgres: + image: postgres:16 + ports: + - "5433:5432" + environment: + POSTGRES_DB: postgres + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + volumes: + - langgraph-data:/var/lib/postgresql/data + healthcheck: + test: pg_isready -U postgres + start_period: 10s + timeout: 1s + retries: 5 + interval: 60s + start_interval: 1s + langgraph-debugger: + image: langchain/langgraph-debugger + restart: on-failure + depends_on: + langgraph-postgres: + condition: service_healthy + ports: + - "{debugger_port}:3968" + environment: + VITE_STUDIO_LOCAL_GRAPH_URL: {debugger_graph_url} + langgraph-api: + ports: + - "8000:8000" + depends_on: + langgraph-redis: + condition: service_healthy + langgraph-postgres: + condition: service_healthy + environment: + REDIS_URI: redis://langgraph-redis:6379 + POSTGRES_URI: {DEFAULT_POSTGRES_URI} + healthcheck: + test: python /api/healthcheck.py + interval: 60s + start_interval: 1s + start_period: 10s + + pull_policy: build + build: + context: . + dockerfile_inline: | + FROM langchain/langgraph-api:3.11 + ADD . /deps/ + RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/* + ENV LANGSERVE_GRAPHS='{{"agent": "agent.py:graph"}}' + WORKDIR /deps/ + + develop: + watch: + - path: langgraph.json + action: rebuild + - path: . + action: rebuild\ +""" + assert actual_args == expected_args + assert clean_empty_lines(actual_stdin) == expected_stdin + + +def test_version_option() -> None: + """Test the --version option of the CLI.""" + runner = CliRunner() + result = runner.invoke(cli, ["--version"]) + + # Verify that the command executed successfully + assert result.exit_code == 0, "Expected exit code 0 for --version option" + + # Check that the output contains the correct version information + assert ( + "LangGraph CLI, version" in result.output + ), "Expected version information in output" + + +def test_dockerfile_command_basic() -> None: + """Test the 'dockerfile' command with basic configuration.""" + runner = CliRunner() + config_content = { + "node_version": "20", # Add any other necessary configuration fields + "graphs": {"agent": "agent.py:graph"}, + } + + with temporary_config_folder(config_content) as temp_dir: + save_path = temp_dir / "Dockerfile" + + result = runner.invoke( + cli, + ["dockerfile", str(save_path), "--config", str(temp_dir / "config.json")], + ) + + # Assert command was successful + assert result.exit_code == 0, result.output + assert "✅ Created: Dockerfile" in result.output + + # Check if Dockerfile was created + assert save_path.exists() + + +def test_dockerfile_command_with_docker_compose() -> None: + """Test the 'dockerfile' command with Docker Compose configuration.""" + runner = CliRunner() + config_content = { + "dependencies": ["./my_agent"], + "graphs": {"agent": "./my_agent/agent.py:graph"}, + "env": ".env", + } + with temporary_config_folder(config_content) as temp_dir: + save_path = temp_dir / "Dockerfile" + # Add agent.py file + agent_path = temp_dir / "my_agent" / "agent.py" + agent_path.parent.mkdir(parents=True, exist_ok=True) + agent_path.touch() + + result = runner.invoke( + cli, + [ + "dockerfile", + str(save_path), + "--config", + str(temp_dir / "config.json"), + "--add-docker-compose", + ], + ) + + # Assert command was successful + assert result.exit_code == 0 + assert "✅ Created: Dockerfile" in result.output + assert "✅ Created: .dockerignore" in result.output + assert "✅ Created: docker-compose.yml" in result.output + assert ( + "✅ Created: .env" in result.output or "➖ Skipped: .env" in result.output + ) + assert "🎉 Files generated successfully" in result.output + + # Check if Dockerfile, .dockerignore, docker-compose.yml, and .env were created + assert save_path.exists() + assert (temp_dir / ".dockerignore").exists() + assert (temp_dir / "docker-compose.yml").exists() + assert (temp_dir / ".env").exists() or "➖ Skipped: .env" in result.output + + +def test_dockerfile_command_with_bad_config() -> None: + """Test the 'dockerfile' command with basic configuration.""" + runner = CliRunner() + config_content = { + "node_version": "20" # Add any other necessary configuration fields + } + + with temporary_config_folder(config_content) as temp_dir: + save_path = temp_dir / "Dockerfile" + + result = runner.invoke( + cli, + ["dockerfile", str(save_path), "--config", str(temp_dir / "conf.json")], + ) + + # Assert command was successful + assert result.exit_code == 2 + assert "conf.json' does not exist" in result.output diff --git a/libs/cli/tests/unit_tests/cli/test_templates.py b/libs/cli/tests/unit_tests/cli/test_templates.py new file mode 100644 index 000000000..acd7651d0 --- /dev/null +++ b/libs/cli/tests/unit_tests/cli/test_templates.py @@ -0,0 +1,70 @@ +"""Unit tests for the 'new' CLI command. + +This command creates a new LangGraph project using a specified template. +""" + +import os +from io import BytesIO +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest.mock import MagicMock, patch +from urllib import request +from zipfile import ZipFile + +from click.testing import CliRunner + +from langgraph_cli.cli import cli +from langgraph_cli.templates import TEMPLATE_ID_TO_CONFIG + + +@patch.object(request, "urlopen") +def test_create_new_with_mocked_download(mock_urlopen: MagicMock) -> None: + """Test the 'new' CLI command with a mocked download response using urllib.""" + # Mock the response content to simulate a ZIP file + mock_zip_content = BytesIO() + with ZipFile(mock_zip_content, "w") as mock_zip: + mock_zip.writestr("test-file.txt", "Test content.") + + # Create a mock response that behaves like a context manager + mock_response = MagicMock() + mock_response.read.return_value = mock_zip_content.getvalue() + mock_response.__enter__.return_value = mock_response # Setup enter context + mock_response.status = 200 + + mock_urlopen.return_value = mock_response + + with TemporaryDirectory() as temp_dir: + runner = CliRunner() + template = next( + iter(TEMPLATE_ID_TO_CONFIG) + ) # Select the first template for the test + result = runner.invoke(cli, ["new", temp_dir, "--template", template]) + + # Verify CLI command execution and success + assert result.exit_code == 0, result.output + assert ( + "New project created" in result.output + ), "Expected success message in output." + + # Verify that the directory is not empty + assert os.listdir(temp_dir), "Expected files to be created in temp directory." + + # Check for a known file in the extracted content + extracted_files = [f.name for f in Path(temp_dir).glob("*")] + assert ( + "test-file.txt" in extracted_files + ), "Expected 'test-file.txt' in the extracted content." + + +def test_invalid_template_id() -> None: + """Test that an invalid template ID passed via CLI results in a graceful error.""" + runner = CliRunner() + result = runner.invoke( + cli, ["new", "dummy_path", "--template", "invalid-template-id"] + ) + + # Verify the command failed and proper message is displayed + assert result.exit_code != 0, "Expected non-zero exit code for invalid template." + assert ( + "Template 'invalid-template-id' not found" in result.output + ), "Expected error message in output." diff --git a/libs/cli/tests/unit_tests/conftest.py b/libs/cli/tests/unit_tests/conftest.py new file mode 100644 index 000000000..5caaa6c81 --- /dev/null +++ b/libs/cli/tests/unit_tests/conftest.py @@ -0,0 +1,16 @@ +import os +from unittest.mock import patch + +import pytest + + +@pytest.fixture(autouse=True) +def disable_analytics_env() -> None: + """Disable analytics for unit tests LANGGRAPH_CLI_NO_ANALYTICS.""" + # First check if the environment variable is already set, if so, log a warning prior + # to overriding it. + if "LANGGRAPH_CLI_NO_ANALYTICS" in os.environ: + print("⚠️ LANGGRAPH_CLI_NO_ANALYTICS is set. Overriding it for the test.") + + with patch.dict(os.environ, {"LANGGRAPH_CLI_NO_ANALYTICS": "0"}): + yield diff --git a/libs/cli/tests/unit_tests/test_cli.py b/libs/cli/tests/unit_tests/test_cli.py deleted file mode 100644 index a4972f393..000000000 --- a/libs/cli/tests/unit_tests/test_cli.py +++ /dev/null @@ -1,117 +0,0 @@ -import pathlib - -from langgraph_cli.cli import prepare_args_and_stdin -from langgraph_cli.config import Config, validate_config -from langgraph_cli.docker import DEFAULT_POSTGRES_URI, DockerCapabilities, Version -from langgraph_cli.util import clean_empty_lines - -DEFAULT_DOCKER_CAPABILITIES = DockerCapabilities( - version_docker=Version(26, 1, 1), - version_compose=Version(2, 27, 0), - healthcheck_start_interval=True, -) - - -def test_prepare_args_and_stdin(): - # this basically serves as an end-to-end test for using config and docker helpers - config_path = pathlib.Path("./langgraph.json") - config = validate_config( - Config(dependencies=["."], graphs={"agent": "agent.py:graph"}) - ) - port = 8000 - debugger_port = 8001 - debugger_graph_url = f"http://127.0.0.1:{port}" - - actual_args, actual_stdin = prepare_args_and_stdin( - capabilities=DEFAULT_DOCKER_CAPABILITIES, - config_path=config_path, - config=config, - docker_compose="custom-docker-compose.yml", - port=port, - debugger_port=debugger_port, - debugger_base_url=debugger_graph_url, - watch=True, - ) - - expected_args = [ - "--project-directory", - ".", - "-f", - "custom-docker-compose.yml", - "-f", - "-", - ] - expected_stdin = f"""volumes: - langgraph-data: - driver: local -services: - langgraph-redis: - image: redis:6 - healthcheck: - test: redis-cli ping - interval: 5s - timeout: 1s - retries: 5 - langgraph-postgres: - image: postgres:16 - ports: - - "5433:5432" - environment: - POSTGRES_DB: postgres - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - volumes: - - langgraph-data:/var/lib/postgresql/data - healthcheck: - test: pg_isready -U postgres - start_period: 10s - timeout: 1s - retries: 5 - interval: 60s - start_interval: 1s - langgraph-debugger: - image: langchain/langgraph-debugger - restart: on-failure - depends_on: - langgraph-postgres: - condition: service_healthy - ports: - - "{debugger_port}:3968" - environment: - VITE_STUDIO_LOCAL_GRAPH_URL: {debugger_graph_url} - langgraph-api: - ports: - - "8000:8000" - depends_on: - langgraph-redis: - condition: service_healthy - langgraph-postgres: - condition: service_healthy - environment: - REDIS_URI: redis://langgraph-redis:6379 - POSTGRES_URI: {DEFAULT_POSTGRES_URI} - healthcheck: - test: python /api/healthcheck.py - interval: 60s - start_interval: 1s - start_period: 10s - - pull_policy: build - build: - context: . - dockerfile_inline: | - FROM langchain/langgraph-api:3.11 - ADD . /deps/ - RUN PYTHONDONTWRITEBYTECODE=1 pip install --no-cache-dir -c /api/constraints.txt -e /deps/* - ENV LANGSERVE_GRAPHS='{{"agent": "agent.py:graph"}}' - WORKDIR /deps/ - - develop: - watch: - - path: langgraph.json - action: rebuild - - path: . - action: rebuild\ -""" - assert actual_args == expected_args - assert clean_empty_lines(actual_stdin) == expected_stdin diff --git a/libs/cli/tests/unit_tests/test_config.py b/libs/cli/tests/unit_tests/test_config.py index f53313986..cff16ead6 100644 --- a/libs/cli/tests/unit_tests/test_config.py +++ b/libs/cli/tests/unit_tests/test_config.py @@ -250,9 +250,10 @@ def test_config_to_docker_nodejs(): ARG meow ARG foo ADD . /deps/unit_tests -RUN cd /deps/unit_tests && yarn install --frozen-lockfile +RUN cd /deps/unit_tests && npm i ENV LANGSERVE_GRAPHS='{"agent": "./graphs/agent.js:graph"}' -WORKDIR /deps/unit_tests""" +WORKDIR /deps/unit_tests +RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts""" assert clean_empty_lines(actual_docker_stdin) == expected_docker_stdin diff --git a/libs/langgraph/Makefile b/libs/langgraph/Makefile index 1e249a0cd..2aacf6db8 100644 --- a/libs/langgraph/Makefile +++ b/libs/langgraph/Makefile @@ -49,7 +49,7 @@ test: exit $$EXIT_CODE test_watch: - make start-postgres && poetry run ptw . -- --ff -v -x -n auto --dist worksteal --snapshot-update --tb short $(TEST); \ + make start-postgres && poetry run ptw . -- --ff -vv -x -n auto --dist worksteal --snapshot-update --tb short $(TEST); \ EXIT_CODE=$$?; \ make stop-postgres; \ exit $$EXIT_CODE diff --git a/libs/langgraph/README.md b/libs/langgraph/README.md index a375771f0..6f7b62676 100644 --- a/libs/langgraph/README.md +++ b/libs/langgraph/README.md @@ -16,6 +16,8 @@ LangGraph is inspired by [Pregel](https://research.google/pubs/pub37252/) and [Apache Beam](https://beam.apache.org/). The public interface draws inspiration from [NetworkX](https://networkx.org/documentation/latest/). LangGraph is built by LangChain Inc, the creators of LangChain, but can be used without LangChain. +[LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform) is infrastructure for deploying LangGraph agents. It is a commercial solution for deploying agentic applications to production, built on the open-source LangGraph framework. The LangGraph Platform consists of several components that work together to support the development, deployment, debugging, and monitoring of LangGraph applications: [LangGraph Server](https://langchain-ai.github.io/langgraph/concepts/langgraph_server) (APIs), [LangGraph SDKs](https://langchain-ai.github.io/langgraph/concepts/sdk) (clients for the APIs), [LangGraph CLI](https://langchain-ai.github.io/langgraph/concepts/langgraph_cli) (command line tool for building the server), [LangGraph Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio) (UI/debugger), + To learn more about LangGraph, check out our first LangChain Academy course, *Introduction to LangGraph*, available for free [here](https://academy.langchain.com/courses/intro-to-langgraph). ### Key Features @@ -26,6 +28,16 @@ To learn more about LangGraph, check out our first LangChain Academy course, *In - **Streaming Support**: Stream outputs as they are produced by each node (including token streaming). - **Integration with LangChain**: LangGraph integrates seamlessly with [LangChain](https://github.com/langchain-ai/langchain/) and [LangSmith](https://docs.smith.langchain.com/) (but does not require them). +### LangGraph Platform + +LangGraph Platform is a commercial solution for deploying agentic applications to production, built on the open-source LangGraph framework. +Here are some common issues that arise in complex deployments, which LangGraph Platform addresses: + +- **Streaming support**: LangGraph Server provides [multiple streaming modes](https://langchain-ai.github.io/langgraph/concepts/streaming) optimized for various application needs +- **Background runs**: Runs agents asynchronously in the background +- **Support for long running agents**: Infrastructure that can handle long running processes +- **[Double texting](https://langchain-ai.github.io/langgraph/concepts/double_texting)**: Handle the case where you get two messages from the user before the agent can respond +- **Handle burstiness**: Task queue for ensuring requests are handled consistently without loss, even under heavy loads ## Installation diff --git a/libs/langgraph/langgraph/channels/last_value.py b/libs/langgraph/langgraph/channels/last_value.py index 3f5b35e3b..5065f4fc5 100644 --- a/libs/langgraph/langgraph/channels/last_value.py +++ b/libs/langgraph/langgraph/channels/last_value.py @@ -3,7 +3,12 @@ from typing import Generic, Optional, Sequence, Type from typing_extensions import Self from langgraph.channels.base import BaseChannel, Value -from langgraph.errors import EmptyChannelError, InvalidUpdateError +from langgraph.errors import ( + EmptyChannelError, + ErrorCode, + InvalidUpdateError, + create_error_message, +) class LastValue(Generic[Value], BaseChannel[Value, Value, Value]): @@ -35,9 +40,11 @@ class LastValue(Generic[Value], BaseChannel[Value, Value, Value]): if len(values) == 0: return False if len(values) != 1: - raise InvalidUpdateError( - f"At key '{self.key}': Can receive only one value per step. Use an Annotated key to handle multiple values." + msg = create_error_message( + message=f"At key '{self.key}': Can receive only one value per step. Use an Annotated key to handle multiple values.", + error_code=ErrorCode.INVALID_CONCURRENT_GRAPH_UPDATE, ) + raise InvalidUpdateError(msg) self.value = values[-1] return True diff --git a/libs/langgraph/langgraph/constants.py b/libs/langgraph/langgraph/constants.py index 564d2cac7..478f23d68 100644 --- a/libs/langgraph/langgraph/constants.py +++ b/libs/langgraph/langgraph/constants.py @@ -1,4 +1,5 @@ import sys +from os import getenv from types import MappingProxyType from typing import Any, Literal, Mapping, cast @@ -10,20 +11,27 @@ from langgraph.types import Interrupt, Send # noqa: F401 # --- Empty read-only containers --- EMPTY_MAP: Mapping[str, Any] = MappingProxyType({}) EMPTY_SEQ: tuple[str, ...] = tuple() +MISSING = object() # --- Public constants --- +TAG_NOSTREAM = sys.intern("langsmith:nostream") +"""Tag to disable streaming for a chat model.""" TAG_HIDDEN = sys.intern("langsmith:hidden") """Tag to hide a node/edge from certain tracing/streaming environments.""" START = sys.intern("__start__") """The first (maybe virtual) node in graph-style Pregel.""" END = sys.intern("__end__") """The last (maybe virtual) node in graph-style Pregel.""" +SELF = sys.intern("__self__") +"""The implicit branch that handles each node's Control values.""" # --- Reserved write keys --- INPUT = sys.intern("__input__") # for values passed as input to the graph INTERRUPT = sys.intern("__interrupt__") # for dynamic interrupts raised by nodes +RESUME = sys.intern("__resume__") +# for values passed to resume a node after an interrupt ERROR = sys.intern("__error__") # for errors raised by nodes NO_WRITES = sys.intern("__no_writes__") @@ -63,6 +71,10 @@ CONFIG_KEY_CHECKPOINT_ID = sys.intern("checkpoint_id") # holds the current checkpoint_id, if any CONFIG_KEY_CHECKPOINT_NS = sys.intern("checkpoint_ns") # holds the current checkpoint_ns, "" for root graph +CONFIG_KEY_NODE_FINISHED = sys.intern("__pregel_node_finished") +# callback to be called when a node is finished +CONFIG_KEY_RESUME_VALUE = sys.intern("__pregel_resume_value") +# holds the value that "answers" an interrupt() call # --- Other constants --- PUSH = sys.intern("__pregel_push") @@ -75,12 +87,17 @@ NS_END = sys.intern(":") # for checkpoint_ns, for each level, separates the namespace from the task_id CONF = cast(Literal["configurable"], sys.intern("configurable")) # key for the configurable dict in RunnableConfig +FF_SEND_V2 = getenv("LANGGRAPH_FF_SEND_V2", "false").lower() == "true" +# temporary flag to enable new Send semantics +NULL_TASK_ID = sys.intern("00000000-0000-0000-0000-000000000000") +# the task_id to use for writes that are not associated with a task RESERVED = { TAG_HIDDEN, # reserved write keys INPUT, INTERRUPT, + RESUME, ERROR, NO_WRITES, SCHEDULED, diff --git a/libs/langgraph/langgraph/errors.py b/libs/langgraph/langgraph/errors.py index 63bc8aff6..2450b42b1 100644 --- a/libs/langgraph/langgraph/errors.py +++ b/libs/langgraph/langgraph/errors.py @@ -1,3 +1,4 @@ +from enum import Enum from typing import Any, Sequence from langgraph.checkpoint.base import EmptyChannelError # noqa: F401 @@ -6,12 +7,32 @@ from langgraph.types import Interrupt # EmptyChannelError re-exported for backwards compatibility +class ErrorCode(Enum): + GRAPH_RECURSION_LIMIT = "GRAPH_RECURSION_LIMIT" + INVALID_CONCURRENT_GRAPH_UPDATE = "INVALID_CONCURRENT_GRAPH_UPDATE" + INVALID_GRAPH_NODE_RETURN_VALUE = "INVALID_GRAPH_NODE_RETURN_VALUE" + MULTIPLE_SUBGRAPHS = "MULTIPLE_SUBGRAPHS" + INVALID_CHAT_HISTORY = "INVALID_CHAT_HISTORY" + + +def create_error_message(*, message: str, error_code: ErrorCode) -> str: + return ( + f"{message}\n" + "For troubleshooting, visit: https://python.langchain.com/docs/" + f"troubleshooting/errors/{error_code.value}" + ) + + class GraphRecursionError(RecursionError): """Raised when the graph has exhausted the maximum number of steps. This prevents infinite loops. To increase the maximum number of steps, run your graph with a config specifying a higher `recursion_limit`. + Troubleshooting Guides: + + - [GRAPH_RECURSION_LIMIT](https://python.langchain.com/docs/troubleshooting/errors/GRAPH_RECURSION_LIMIT) + Examples: graph = builder.compile() @@ -26,7 +47,13 @@ class GraphRecursionError(RecursionError): class InvalidUpdateError(Exception): - """Raised when attempting to update a channel with an invalid set of updates.""" + """Raised when attempting to update a channel with an invalid set of updates. + + Troubleshooting Guides: + + - [INVALID_CONCURRENT_GRAPH_UPDATE](https://python.langchain.com/docs/troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE) + - [INVALID_GRAPH_NODE_RETURN_VALUE](https://python.langchain.com/docs/troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE) + """ pass @@ -43,7 +70,7 @@ class NodeInterrupt(GraphInterrupt): """Raised by a node to interrupt execution.""" def __init__(self, value: Any) -> None: - super().__init__([Interrupt(value)]) + super().__init__([Interrupt(value=value)]) class GraphDelegate(Exception): @@ -72,7 +99,12 @@ class CheckpointNotLatest(Exception): class MultipleSubgraphsError(Exception): - """Raised when multiple subgraphs are called inside the same node.""" + """Raised when multiple subgraphs are called inside the same node. + + Troubleshooting guides: + + - [MULTIPLE_SUBGRAPHS](https://python.langchain.com/docs/troubleshooting/errors/MULTIPLE_SUBGRAPHS) + """ pass diff --git a/libs/langgraph/langgraph/graph/__init__.py b/libs/langgraph/langgraph/graph/__init__.py index c81ad9903..241106a3a 100644 --- a/libs/langgraph/langgraph/graph/__init__.py +++ b/libs/langgraph/langgraph/graph/__init__.py @@ -1,12 +1,13 @@ from langgraph.graph.graph import END, START, Graph from langgraph.graph.message import MessageGraph, MessagesState, add_messages -from langgraph.graph.state import StateGraph +from langgraph.graph.state import GraphCommand, StateGraph __all__ = [ "END", "START", "Graph", "StateGraph", + "GraphCommand", "MessageGraph", "add_messages", "MessagesState", diff --git a/libs/langgraph/langgraph/graph/graph.py b/libs/langgraph/langgraph/graph/graph.py index 7c0923751..e91ac4a47 100644 --- a/libs/langgraph/langgraph/graph/graph.py +++ b/libs/langgraph/langgraph/graph/graph.py @@ -27,6 +27,7 @@ from typing_extensions import Self from langgraph.channels.ephemeral_value import EphemeralValue from langgraph.constants import ( + EMPTY_SEQ, END, NS_END, NS_SEP, @@ -47,6 +48,7 @@ logger = logging.getLogger(__name__) class NodeSpec(NamedTuple): runnable: Runnable metadata: Optional[dict[str, Any]] = None + ends: Optional[tuple[str, ...]] = EMPTY_SEQ class Branch(NamedTuple): @@ -123,7 +125,7 @@ class Branch(NamedTuple): result: Any, config: RunnableConfig, ) -> Union[Runnable, Any]: - if not isinstance(result, list): + if not isinstance(result, (list, tuple)): result = [result] if self.ends: destinations: Sequence[Union[Send, str]] = [ @@ -364,6 +366,9 @@ class Graph: for node in self.nodes: if node != start and node != branch.then: all_sources.add(node) + for name, spec in self.nodes.items(): + if spec.ends: + all_sources.add(name) # validate sources for source in all_sources: if source not in self.nodes and source != START: @@ -387,6 +392,9 @@ class Graph: for node in self.nodes: if node != start and node != branch.then: all_targets.add(node) + for name, spec in self.nodes.items(): + if spec.ends: + all_targets.update(spec.ends) # validate targets for node in self.nodes: if node not in all_targets: @@ -514,6 +522,14 @@ class CompiledGraph(Pregel): self.nodes[end].triggers.append(channel_name) cast(list[str], self.nodes[end].channels).append(channel_name) + async def aget_graph( + self, + config: Optional[RunnableConfig] = None, + *, + xray: Union[int, bool] = False, + ) -> DrawableGraph: + return self.get_graph(config, xray=xray) + def get_graph( self, config: Optional[RunnableConfig] = None, @@ -557,18 +573,12 @@ class CompiledGraph(Pregel): metadata["__interrupt"] = "before" elif key in self.interrupt_after_nodes: metadata["__interrupt"] = "after" - if xray: - subgraph = ( - subgraphs[key].get_graph( - config=config, - xray=xray - 1 - if isinstance(xray, int) - and not isinstance(xray, bool) - and xray > 0 - else xray, - ) - if key in subgraphs - else node.get_graph(config=config) + if xray and key in subgraphs: + subgraph = subgraphs[key].get_graph( + config=config, + xray=xray - 1 + if isinstance(xray, int) and not isinstance(xray, bool) and xray > 0 + else xray, ) subgraph.trim_first_node() subgraph.trim_last_node() @@ -612,5 +622,9 @@ class CompiledGraph(Pregel): ) if branch.then is not None: add_edge(end, branch.then) + for key, n in self.builder.nodes.items(): + if n.ends: + for end in n.ends: + add_edge(key, end, conditional=True) return graph diff --git a/libs/langgraph/langgraph/graph/state.py b/libs/langgraph/langgraph/graph/state.py index bc0762c80..c581d2259 100644 --- a/libs/langgraph/langgraph/graph/state.py +++ b/libs/langgraph/langgraph/graph/state.py @@ -1,12 +1,15 @@ +import dataclasses import inspect import logging import typing import warnings from functools import partial -from inspect import isclass, isfunction, signature +from inspect import isclass, isfunction, ismethod, signature +from types import FunctionType from typing import ( Any, Callable, + Generic, Literal, NamedTuple, Optional, @@ -14,6 +17,7 @@ from typing import ( Type, Union, cast, + get_args, get_origin, get_type_hints, overload, @@ -32,8 +36,8 @@ from langgraph.channels.dynamic_barrier_value import DynamicBarrierValue, WaitFo from langgraph.channels.ephemeral_value import EphemeralValue from langgraph.channels.last_value import LastValue from langgraph.channels.named_barrier_value import NamedBarrierValue -from langgraph.constants import NS_END, NS_SEP, TAG_HIDDEN -from langgraph.errors import InvalidUpdateError +from langgraph.constants import EMPTY_SEQ, NS_END, NS_SEP, SELF, TAG_HIDDEN +from langgraph.errors import ErrorCode, InvalidUpdateError, create_error_message from langgraph.graph.graph import END, START, Branch, CompiledGraph, Graph, Send from langgraph.managed.base import ( ChannelKeyPlaceholder, @@ -46,10 +50,10 @@ from langgraph.managed.base import ( from langgraph.pregel.read import ChannelRead, PregelNode from langgraph.pregel.write import SKIP_WRITE, ChannelWrite, ChannelWriteEntry from langgraph.store.base import BaseStore -from langgraph.types import All, Checkpointer, RetryPolicy +from langgraph.types import _DC_KWARGS, All, Checkpointer, Command, N, RetryPolicy from langgraph.utils.fields import get_field_default from langgraph.utils.pydantic import create_model -from langgraph.utils.runnable import coerce_to_runnable +from langgraph.utils.runnable import RunnableCallable, coerce_to_runnable logger = logging.getLogger(__name__) @@ -66,11 +70,37 @@ def _warn_invalid_state_schema(schema: Union[Type[Any], Any]) -> None: ) +def _get_node_name(node: RunnableLike) -> str: + if isinstance(node, Runnable): + return node.get_name() + elif callable(node): + return getattr(node, "__name__", node.__class__.__name__) + else: + raise TypeError(f"Unsupported node type: {type(node)}") + + +@dataclasses.dataclass(**_DC_KWARGS) +class GraphCommand(Generic[N], Command[N]): + """One or more commands to update a StateGraph's state and go to, or send messages to nodes.""" + + goto: Union[str, Sequence[str]] = () + + def __repr__(self) -> str: + # get all non-None values + contents = ", ".join( + f"{key}={value!r}" + for key, value in dataclasses.asdict(self).items() + if value + ) + return f"Command({contents})" + + class StateNodeSpec(NamedTuple): runnable: Runnable metadata: Optional[dict[str, Any]] input: Type[Any] retry_policy: Optional[RetryPolicy] + ends: Optional[tuple[str, ...]] = EMPTY_SEQ class StateGraph(Graph): @@ -222,7 +252,7 @@ class StateGraph(Graph): ValueError: If the key is already being used as a state key. Returns: - None + StateGraph """ ... @@ -246,7 +276,7 @@ class StateGraph(Graph): ValueError: If the key is already being used as a state key. Returns: - None + StateGraph """ ... @@ -299,7 +329,7 @@ class StateGraph(Graph): ``` Returns: - None + StateGraph """ if not isinstance(node, str): action = node @@ -338,17 +368,31 @@ class StateGraph(Graph): f"'{character}' is a reserved character and is not allowed in the node names." ) + ends = EMPTY_SEQ try: - if isfunction(action) and ( - hints := get_type_hints(action.__call__) or get_type_hints(action) + if (isfunction(action) or ismethod(getattr(action, "__call__", None))) and ( + hints := get_type_hints(getattr(action, "__call__")) + or get_type_hints(action) ): if input is None: first_parameter_name = next( - iter(inspect.signature(action).parameters.keys()) + iter( + inspect.signature( + cast(FunctionType, action) + ).parameters.keys() + ) ) if input_hint := hints.get(first_parameter_name): if isinstance(input_hint, type) and get_type_hints(input_hint): input = input_hint + if ( + (rtn := hints.get("return")) + and get_origin(rtn) in (Command, GraphCommand) + and (rargs := get_args(rtn)) + and get_origin(rargs[0]) is Literal + and (vals := get_args(rargs[0])) + ): + ends = vals except (TypeError, StopIteration): pass if input is not None: @@ -358,6 +402,7 @@ class StateGraph(Graph): metadata, input=input or self.schema, retry_policy=retry, + ends=ends, ) return self @@ -374,7 +419,7 @@ class StateGraph(Graph): ValueError: If the start key is 'END' or if the start key or end key is not present in the graph. Returns: - None + StateGraph """ if isinstance(start_key, str): return super().add_edge(start_key, end_key) @@ -397,6 +442,48 @@ class StateGraph(Graph): self.waiting_edges.add((tuple(start_key), end_key)) return self + def add_sequence( + self, + nodes: Sequence[Union[RunnableLike, tuple[str, RunnableLike]]], + ) -> Self: + """Add a sequence of nodes that will be executed in the provided order. + + Args: + nodes: A sequence of RunnableLike objects (e.g. a LangChain Runnable or a callable) or (name, RunnableLike) tuples. + If no names are provided, the name will be inferred from the node object (e.g. a runnable or a callable name). + Each node will be executed in the order provided. + + Raises: + ValueError: if the sequence is empty. + ValueError: if the sequence contains duplicate node names. + + Returns: + StateGraph + """ + if len(nodes) < 1: + raise ValueError("Sequence requires at least one node.") + + previous_name: Optional[str] = None + for node in nodes: + if isinstance(node, tuple) and len(node) == 2: + name, node = node + else: + name = _get_node_name(node) + + if name in self.nodes: + raise ValueError( + f"Node names must be unique: node with the name '{name}' already exists. " + "If you need to use two different runnables/callables with the same name (for example, using `lambda`), please provide them as tuples (name, runnable/callable)." + ) + + self.add_node(name, node) + if previous_name is not None: + self.add_edge(previous_name, name) + + previous_name = name + + return self + def compile( self, checkpointer: Checkpointer = None, @@ -412,9 +499,11 @@ class StateGraph(Graph): streamed, batched, and run asynchronously. Args: - checkpointer (Checkpointer): An optional checkpoint saver object. - This serves as a fully versioned "memory" for the graph, allowing - the graph to be paused and resumed, and replayed from any point. + checkpointer (Optional[Union[Checkpointer, Literal[False]]]): A checkpoint saver object or flag. + If provided, this Checkpointer serves as a fully versioned "short-term memory" for the graph, + allowing it to be paused, resumed, and replayed from any point. + If None, it may inherit the parent graph's checkpointer when used as a subgraph. + If False, it will not use or inherit any checkpointer. interrupt_before (Optional[Sequence[str]]): An optional list of node names to interrupt before. interrupt_after (Optional[Sequence[str]]): An optional list of node names to interrupt after. debug (bool): A flag indicating whether to enable debug mode. @@ -479,6 +568,9 @@ class StateGraph(Graph): for key, node in self.nodes.items(): compiled.attach_node(key, node) + for key, node in self.nodes.items(): + compiled.attach_branch(key, SELF, CONTROL_BRANCH, with_reader=False) + for start, end in self.edges: compiled.attach_edge(start, end) @@ -529,20 +621,36 @@ class CompiledStateGraph(CompiledGraph): if is_writable_managed_value(v) ] + def _get_root(input: Any) -> Any: + if isinstance(input, Command): + return input.update + else: + return input + def _get_state_key(input: Union[None, dict, Any], *, key: str) -> Any: if input is None: return SKIP_WRITE elif isinstance(input, dict): + if all(k not in output_keys for k in input): + raise InvalidUpdateError( + f"Expected node {key} to update at least one of {output_keys}, got {input}" + ) return input.get(key, SKIP_WRITE) + elif isinstance(input, Command): + return _get_state_key(input.update, key=key) elif get_type_hints(type(input)): value = getattr(input, key, SKIP_WRITE) return value if value is not None else SKIP_WRITE else: - raise InvalidUpdateError(f"Expected dict, got {input}") + msg = create_error_message( + message=f"Expected dict, got {input}", + error_code=ErrorCode.INVALID_GRAPH_NODE_RETURN_VALUE, + ) + raise InvalidUpdateError(msg) # state updaters write_entries = ( - [ChannelWriteEntry("__root__", skip_none=True)] + [ChannelWriteEntry("__root__", skip_none=True, mapper=_get_root)] if output_keys == ["__root__"] else [ ChannelWriteEntry(key, mapper=partial(_get_state_key, key=key)) @@ -585,7 +693,6 @@ class CompiledStateGraph(CompiledGraph): ChannelWrite( [ChannelWriteEntry(key, key)] + write_entries, tags=[TAG_HIDDEN], - require_at_least_one_of=output_keys, ), ], metadata=node.metadata, @@ -622,7 +729,9 @@ class CompiledStateGraph(CompiledGraph): [ChannelWriteEntry(channel_name, start)], tags=[TAG_HIDDEN] ) - def attach_branch(self, start: str, name: str, branch: Branch) -> None: + def attach_branch( + self, start: str, name: str, branch: Branch, *, with_reader: bool = True + ) -> None: def branch_writer( packets: Sequence[Union[str, Send]], config: RunnableConfig ) -> None: @@ -655,7 +764,8 @@ class CompiledStateGraph(CompiledGraph): else self.builder.schema ) self.nodes[start] |= branch.run( - branch_writer, _get_state_reader(self.builder, schema) + branch_writer, + _get_state_reader(self.builder, schema) if with_reader else None, ) # attach branch subscribers @@ -704,6 +814,46 @@ def _coerce_state(schema: Type[Any], input: dict[str, Any]) -> dict[str, Any]: return schema(**input) +def _control_branch(value: Any) -> Sequence[Union[str, Send]]: + if isinstance(value, Send): + return [value] + if not isinstance(value, GraphCommand): + return EMPTY_SEQ + rtn: list[Union[str, Send]] = [] + if isinstance(value.goto, str): + rtn.append(value.goto) + else: + rtn.extend(value.goto) + if isinstance(value.send, Send): + rtn.append(value.send) + else: + rtn.extend(value.send) + return rtn + + +async def _acontrol_branch(value: Any) -> Sequence[Union[str, Send]]: + if isinstance(value, Send): + return [value] + if not isinstance(value, GraphCommand): + return EMPTY_SEQ + rtn: list[Union[str, Send]] = [] + if isinstance(value.goto, str): + rtn.append(value.goto) + else: + rtn.extend(value.goto) + if isinstance(value.send, Send): + rtn.append(value.send) + else: + rtn.extend(value.send) + return rtn + + +CONTROL_BRANCH_PATH = RunnableCallable( + _control_branch, _acontrol_branch, tags=[TAG_HIDDEN], trace=False, recurse=False +) +CONTROL_BRANCH = Branch(CONTROL_BRANCH_PATH, None) + + def _get_channels( schema: Type[dict], ) -> tuple[dict[str, BaseChannel], dict[str, ManagedValueSpec]]: diff --git a/libs/langgraph/langgraph/prebuilt/chat_agent_executor.py b/libs/langgraph/langgraph/prebuilt/chat_agent_executor.py index 5f739c0a6..fc812ccbc 100644 --- a/libs/langgraph/langgraph/prebuilt/chat_agent_executor.py +++ b/libs/langgraph/langgraph/prebuilt/chat_agent_executor.py @@ -11,6 +11,7 @@ from langchain_core.tools import BaseTool from typing_extensions import Annotated, TypedDict from langgraph._api.deprecation import deprecated_parameter +from langgraph.errors import ErrorCode, create_error_message from langgraph.graph import StateGraph from langgraph.graph.graph import CompiledGraph from langgraph.graph.message import add_messages @@ -161,6 +162,37 @@ def _should_bind_tools(model: LanguageModelLike, tools: Sequence[BaseTool]) -> b return False +def _validate_chat_history( + messages: Sequence[BaseMessage], +) -> None: + """Validate that all tool calls in AIMessages have a corresponding ToolMessage.""" + all_tool_calls = [ + tool_call + for message in messages + if isinstance(message, AIMessage) + for tool_call in message.tool_calls + ] + tool_call_ids_with_results = { + message.tool_call_id for message in messages if isinstance(message, ToolMessage) + } + tool_calls_without_results = [ + tool_call + for tool_call in all_tool_calls + if tool_call["id"] not in tool_call_ids_with_results + ] + if not tool_calls_without_results: + return + + error_message = create_error_message( + message="Found AIMessages with tool_calls that do not have a corresponding ToolMessage. " + f"Here are the first few of those tool calls: {tool_calls_without_results[:3]}.\n\n" + "Every tool call (LLM requesting to call a tool) in the message history MUST have a corresponding ToolMessage " + "(result of a tool invocation to return to the LLM) - this is required by most LLM providers.", + error_code=ErrorCode.INVALID_CHAT_HISTORY, + ) + raise ValueError(error_message) + + @deprecated_parameter("messages_modifier", "0.1.9", "state_modifier", removal="0.3.0") def create_react_agent( model: LanguageModelLike, @@ -530,6 +562,7 @@ def create_react_agent( # Define the function that calls the model def call_model(state: AgentState, config: RunnableConfig) -> AgentState: + _validate_chat_history(state["messages"]) response = model_runnable.invoke(state, config) has_tool_calls = isinstance(response, AIMessage) and response.tool_calls all_tools_return_direct = ( @@ -566,6 +599,7 @@ def create_react_agent( return {"messages": [response]} async def acall_model(state: AgentState, config: RunnableConfig) -> AgentState: + _validate_chat_history(state["messages"]) response = await model_runnable.ainvoke(state, config) has_tool_calls = isinstance(response, AIMessage) and response.tool_calls all_tools_return_direct = ( diff --git a/libs/langgraph/langgraph/prebuilt/tool_executor.py b/libs/langgraph/langgraph/prebuilt/tool_executor.py index d939f23d9..692fa6b9a 100644 --- a/libs/langgraph/langgraph/prebuilt/tool_executor.py +++ b/libs/langgraph/langgraph/prebuilt/tool_executor.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Sequence, Union, cast +from typing import Any, Callable, Sequence, Union from langchain_core.load.serializable import Serializable from langchain_core.runnables import RunnableConfig @@ -101,8 +101,7 @@ class ToolExecutor(RunnableCallable): ) -> None: super().__init__(self._execute, afunc=self._aexecute, trace=False) tools_ = [ - tool if isinstance(tool, BaseTool) else cast(BaseTool, create_tool(tool)) - for tool in tools + tool if isinstance(tool, BaseTool) else create_tool(tool) for tool in tools ] self.tools = tools_ self.tool_map = {t.name: t for t in tools_} diff --git a/libs/langgraph/langgraph/prebuilt/tool_node.py b/libs/langgraph/langgraph/prebuilt/tool_node.py index 91e6483a1..cdcbdd819 100644 --- a/libs/langgraph/langgraph/prebuilt/tool_node.py +++ b/libs/langgraph/langgraph/prebuilt/tool_node.py @@ -1,6 +1,7 @@ from __future__ import annotations import asyncio +import inspect import json from copy import copy from typing import ( @@ -16,6 +17,7 @@ from typing import ( Type, Union, cast, + get_type_hints, ) from langchain_core.messages import ( @@ -32,8 +34,10 @@ from langchain_core.runnables.config import ( from langchain_core.runnables.utils import Input from langchain_core.tools import BaseTool, InjectedToolArg from langchain_core.tools import tool as create_tool +from langchain_core.tools.base import get_all_basemodel_annotations from typing_extensions import Annotated, get_args, get_origin +from langgraph.errors import GraphInterrupt from langgraph.store.base import BaseStore from langgraph.utils.runnable import RunnableCallable @@ -67,13 +71,96 @@ def msg_content_output(output: Any) -> str | List[dict]: return str(output) +def _handle_tool_error( + e: Exception, + *, + flag: Union[ + bool, + str, + Callable[..., str], + tuple[type[Exception], ...], + ], +) -> str: + if isinstance(flag, (bool, tuple)): + content = TOOL_CALL_ERROR_TEMPLATE.format(error=repr(e)) + elif isinstance(flag, str): + content = flag + elif callable(flag): + content = flag(e) + else: + raise ValueError( + f"Got unexpected type of `handle_tool_error`. Expected bool, str " + f"or callable. Received: {flag}" + ) + return content + + +def _infer_handled_types(handler: Callable[..., str]) -> tuple[type[Exception]]: + sig = inspect.signature(handler) + params = list(sig.parameters.values()) + if params: + # If it's a method, the first argument is typically 'self' or 'cls' + if params[0].name in ["self", "cls"] and len(params) == 2: + first_param = params[1] + else: + first_param = params[0] + + type_hints = get_type_hints(handler) + if first_param.name in type_hints: + origin = get_origin(first_param.annotation) + if origin is Union: + args = get_args(first_param.annotation) + if all(issubclass(arg, Exception) for arg in args): + return tuple(args) + else: + raise ValueError( + "All types in the error handler error annotation must be Exception types. " + "For example, `def custom_handler(e: Union[ValueError, TypeError])`. " + f"Got '{first_param.annotation}' instead." + ) + + exception_type = type_hints[first_param.name] + if Exception in exception_type.__mro__: + return (exception_type,) + else: + raise ValueError( + f"Arbitrary types are not supported in the error handler signature. " + "Please annotate the error with either a specific Exception type or a union of Exception types. " + "For example, `def custom_handler(e: ValueError)` or `def custom_handler(e: Union[ValueError, TypeError])`. " + f"Got '{exception_type}' instead." + ) + + # If no type information is available, return (Exception,) for backwards compatibility. + return (Exception,) + + class ToolNode(RunnableCallable): """A node that runs the tools called in the last AIMessage. - It can be used either in StateGraph with a "messages" key or in MessageGraph. If - multiple tool calls are requested, they will be run in parallel. The output will be + It can be used either in StateGraph with a "messages" state key (or a custom key passed via ToolNode's 'messages_key'). + If multiple tool calls are requested, they will be run in parallel. The output will be a list of ToolMessages, one for each tool call. + Args: + tools: A sequence of tools that can be invoked by the ToolNode. + name: The name of the ToolNode in the graph. Defaults to "tools". + tags: Optional tags to associate with the node. Defaults to None. + handle_tool_errors: How to handle tool errors raised by tools inside the node. Defaults to True. + Must be one of the following: + + - True: all errors will be caught and + a ToolMessage with a default error message (TOOL_CALL_ERROR_TEMPLATE) will be returned. + - str: all errors will be caught and + a ToolMessage with the string value of 'handle_tool_errors' will be returned. + - tuple[type[Exception], ...]: exceptions in the tuple will be caught and + a ToolMessage with a default error message (TOOL_CALL_ERROR_TEMPLATE) will be returned. + - Callable[..., str]: exceptions from the signature of the callable will be caught and + a ToolMessage with the string value of the result of the 'handle_tool_errors' callable will be returned. + - False: none of the errors raised by the tools will be caught + messages_key: The state key in the input that contains the list of messages. + The same key will be used for the output from the ToolNode. + Defaults to "messages". + The `ToolNode` is roughly analogous to: ```python @@ -101,16 +188,20 @@ class ToolNode(RunnableCallable): *, name: str = "tools", tags: Optional[list[str]] = None, - handle_tool_errors: Optional[bool] = True, + handle_tool_errors: Union[ + bool, str, Callable[..., str], tuple[type[Exception], ...] + ] = True, + messages_key: str = "messages", ) -> None: super().__init__(self._func, self._afunc, name=name, tags=tags, trace=False) self.tools_by_name: Dict[str, BaseTool] = {} self.tool_to_state_args: Dict[str, Dict[str, Optional[str]]] = {} self.tool_to_store_arg: Dict[str, Optional[str]] = {} self.handle_tool_errors = handle_tool_errors + self.messages_key = messages_key for tool_ in tools: if not isinstance(tool_, BaseTool): - tool_ = cast(BaseTool, create_tool(tool_)) + tool_ = create_tool(tool_) self.tools_by_name[tool_.name] = tool_ self.tool_to_state_args[tool_.name] = _get_state_args(tool_) self.tool_to_store_arg[tool_.name] = _get_store_arg(tool_) @@ -131,7 +222,7 @@ class ToolNode(RunnableCallable): with get_executor_for_config(config) as executor: outputs = [*executor.map(self._run_one, tool_calls, config_list)] # TypedDict, pydantic, dataclass, etc. should all be able to load from dict - return outputs if output_type == "list" else {"messages": outputs} + return outputs if output_type == "list" else {self.messages_key: outputs} def invoke( self, input: Input, config: Optional[RunnableConfig] = None, **kwargs: Any @@ -163,7 +254,7 @@ class ToolNode(RunnableCallable): *(self._arun_one(call, config) for call in tool_calls) ) # TypedDict, pydantic, dataclass, etc. should all be able to load from dict - return outputs if output_type == "list" else {"messages": outputs} + return outputs if output_type == "list" else {self.messages_key: outputs} def _run_one(self, call: ToolCall, config: RunnableConfig) -> ToolMessage: if invalid_tool_message := self._validate_tool_call(call): @@ -178,15 +269,38 @@ class ToolNode(RunnableCallable): Union[str, list], msg_content_output(tool_message.content) ) return tool_message + # GraphInterrupt is a special exception that will always be raised. + # It can be triggered in the following scenarios: + # (1) a NodeInterrupt is raised inside a tool + # (2) a NodeInterrupt is raised inside a graph node for a graph called as a tool + # (3) a GraphInterrupt is raised when a subgraph is interrupted inside a graph called as a tool + # (2 and 3 can happen in a "supervisor w/ tools" multi-agent architecture) + except GraphInterrupt as e: + raise e except Exception as e: - if not self.handle_tool_errors: + if isinstance(self.handle_tool_errors, tuple): + handled_types: tuple = self.handle_tool_errors + elif callable(self.handle_tool_errors): + handled_types = _infer_handled_types(self.handle_tool_errors) + else: + # default behavior is catching all exceptions + handled_types = (Exception,) + + # Unhandled + if not self.handle_tool_errors or not isinstance(e, handled_types): raise e - content = TOOL_CALL_ERROR_TEMPLATE.format(error=repr(e)) - return ToolMessage(content, name=call["name"], tool_call_id=call["id"]) + # Handled + else: + content = _handle_tool_error(e, flag=self.handle_tool_errors) + + return ToolMessage( + content=content, name=call["name"], tool_call_id=call["id"], status="error" + ) async def _arun_one(self, call: ToolCall, config: RunnableConfig) -> ToolMessage: if invalid_tool_message := self._validate_tool_call(call): return invalid_tool_message + try: input = {**call, **{"type": "tool_call"}} tool_message: ToolMessage = await self.tools_by_name[call["name"]].ainvoke( @@ -196,11 +310,33 @@ class ToolNode(RunnableCallable): Union[str, list], msg_content_output(tool_message.content) ) return tool_message + # GraphInterrupt is a special exception that will always be raised. + # It can be triggered in the following scenarios: + # (1) a NodeInterrupt is raised inside a tool + # (2) a NodeInterrupt is raised inside a graph node for a graph called as a tool + # (3) a GraphInterrupt is raised when a subgraph is interrupted inside a graph called as a tool + # (2 and 3 can happen in a "supervisor w/ tools" multi-agent architecture) + except GraphInterrupt as e: + raise e except Exception as e: - if not self.handle_tool_errors: + if isinstance(self.handle_tool_errors, tuple): + handled_types: tuple = self.handle_tool_errors + elif callable(self.handle_tool_errors): + handled_types = _infer_handled_types(self.handle_tool_errors) + else: + # default behavior is catching all exceptions + handled_types = (Exception,) + + # Unhandled + if not self.handle_tool_errors or not isinstance(e, handled_types): raise e - content = TOOL_CALL_ERROR_TEMPLATE.format(error=repr(e)) - return ToolMessage(content, name=call["name"], tool_call_id=call["id"]) + # Handled + else: + content = _handle_tool_error(e, flag=self.handle_tool_errors) + + return ToolMessage( + content=content, name=call["name"], tool_call_id=call["id"], status="error" + ) def _parse_input( self, @@ -214,10 +350,10 @@ class ToolNode(RunnableCallable): if isinstance(input, list): output_type = "list" message: AnyMessage = input[-1] - elif isinstance(input, dict) and (messages := input.get("messages", [])): + elif isinstance(input, dict) and (messages := input.get(self.messages_key, [])): output_type = "dict" message = messages[-1] - elif messages := getattr(input, "messages", None): + elif messages := getattr(input, self.messages_key, None): # Assume dataclass-like state that can coerce from dict output_type = "dict" message = messages[-1] @@ -238,7 +374,9 @@ class ToolNode(RunnableCallable): requested_tool=requested_tool, available_tools=", ".join(self.tools_by_name.keys()), ) - return ToolMessage(content, name=requested_tool, tool_call_id=call["id"]) + return ToolMessage( + content, name=requested_tool, tool_call_id=call["id"], status="error" + ) else: return None @@ -256,10 +394,10 @@ class ToolNode(RunnableCallable): required_fields = list(state_args.values()) if ( len(required_fields) == 1 - and required_fields[0] == "messages" + and required_fields[0] == self.messages_key or required_fields[0] is None ): - input = {"messages": input} + input = {self.messages_key: input} else: err_msg = ( f"Invalid input to ToolNode. Tool {tool_call['name']} requires " @@ -325,6 +463,7 @@ class ToolNode(RunnableCallable): def tools_condition( state: Union[list[AnyMessage], dict[str, Any], BaseModel], + messages_key: str = "messages", ) -> Literal["tools", "__end__"]: """Use in the conditional_edge to route to the ToolNode if the last message @@ -377,9 +516,9 @@ def tools_condition( """ if isinstance(state, list): ai_message = state[-1] - elif isinstance(state, dict) and (messages := state.get("messages", [])): + elif isinstance(state, dict) and (messages := state.get(messages_key, [])): ai_message = messages[-1] - elif messages := getattr(state, "messages", []): + elif messages := getattr(state, messages_key, []): ai_message = messages[-1] else: raise ValueError(f"No messages found in input state to tool_edge: {state}") @@ -521,7 +660,7 @@ def _get_state_args(tool: BaseTool) -> Dict[str, Optional[str]]: full_schema = tool.get_input_schema() tool_args_to_state_fields: Dict = {} - for name, type_ in full_schema.__annotations__.items(): + for name, type_ in get_all_basemodel_annotations(full_schema).items(): injections = [ type_arg for type_arg in get_args(type_) @@ -545,7 +684,7 @@ def _get_state_args(tool: BaseTool) -> Dict[str, Optional[str]]: def _get_store_arg(tool: BaseTool) -> Optional[str]: full_schema = tool.get_input_schema() - for name, type_ in full_schema.__annotations__.items(): + for name, type_ in get_all_basemodel_annotations(full_schema).items(): injections = [ type_arg for type_arg in get_args(type_) diff --git a/libs/langgraph/langgraph/pregel/__init__.py b/libs/langgraph/langgraph/pregel/__init__.py index 04e302232..59d2736a0 100644 --- a/libs/langgraph/langgraph/pregel/__init__.py +++ b/libs/langgraph/langgraph/pregel/__init__.py @@ -25,7 +25,6 @@ from uuid import UUID, uuid5 from langchain_core.globals import get_debug from langchain_core.runnables import ( - Runnable, RunnableSequence, ) from langchain_core.runnables.base import Input, Output @@ -34,6 +33,7 @@ from langchain_core.runnables.config import ( get_async_callback_manager_for_config, get_callback_manager_for_config, ) +from langchain_core.runnables.graph import Graph from langchain_core.runnables.utils import ( ConfigurableFieldSpec, get_unique_config_specs, @@ -54,8 +54,10 @@ from langgraph.checkpoint.base import ( ) from langgraph.constants import ( CONF, + CONFIG_KEY_CHECKPOINT_ID, CONFIG_KEY_CHECKPOINT_NS, CONFIG_KEY_CHECKPOINTER, + CONFIG_KEY_NODE_FINISHED, CONFIG_KEY_READ, CONFIG_KEY_RESUMING, CONFIG_KEY_SEND, @@ -63,11 +65,22 @@ from langgraph.constants import ( CONFIG_KEY_STREAM, CONFIG_KEY_STREAM_WRITER, CONFIG_KEY_TASK_ID, + END, + ERROR, + INPUT, INTERRUPT, NS_END, NS_SEP, + NULL_TASK_ID, + PUSH, + SCHEDULED, +) +from langgraph.errors import ( + ErrorCode, + GraphRecursionError, + InvalidUpdateError, + create_error_message, ) -from langgraph.errors import GraphRecursionError, InvalidUpdateError from langgraph.managed.base import ManagedValueSpec from langgraph.pregel.algo import ( PregelTaskWrites, @@ -81,6 +94,7 @@ from langgraph.pregel.io import read_channels from langgraph.pregel.loop import AsyncPregelLoop, StreamProtocol, SyncPregelLoop from langgraph.pregel.manager import AsyncChannelsManager, ChannelsManager from langgraph.pregel.messages import StreamMessagesHandler +from langgraph.pregel.protocol import PregelProtocol from langgraph.pregel.read import PregelNode from langgraph.pregel.retry import RetryPolicy from langgraph.pregel.runner import PregelRunner @@ -88,7 +102,13 @@ from langgraph.pregel.utils import find_subgraph_pregel, get_new_channel_version from langgraph.pregel.validate import validate_graph, validate_keys from langgraph.pregel.write import ChannelWrite, ChannelWriteEntry from langgraph.store.base import BaseStore -from langgraph.types import All, Checkpointer, LoopProtocol, StateSnapshot, StreamMode +from langgraph.types import ( + All, + Checkpointer, + LoopProtocol, + StateSnapshot, + StreamMode, +) from langgraph.utils.config import ( ensure_config, merge_configs, @@ -164,15 +184,17 @@ class Channel: return ChannelWrite( [ChannelWriteEntry(c) for c in channels] + [ - ChannelWriteEntry(k, mapper=v) - if callable(v) - else ChannelWriteEntry(k, value=v) + ( + ChannelWriteEntry(k, mapper=v) + if callable(v) + else ChannelWriteEntry(k, value=v) + ) for k, v in kwargs.items() ] ) -class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): +class Pregel(PregelProtocol): nodes: dict[str, PregelNode] channels: dict[str, Union[BaseChannel, ManagedValueSpec]] @@ -252,6 +274,16 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): if auto_validate: self.validate() + def get_graph( + self, config: RunnableConfig | None = None, *, xray: int | bool = False + ) -> Graph: + raise NotImplementedError + + async def aget_graph( + self, config: RunnableConfig | None = None, *, xray: int | bool = False + ) -> Graph: + raise NotImplementedError + def copy(self, update: dict[str, Any] | None = None) -> Self: attrs = {**self.__dict__, **(update or {})} return self.__class__(**attrs) @@ -420,6 +452,7 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): config: RunnableConfig, saved: Optional[CheckpointTuple], recurse: Optional[BaseCheckpointSaver] = None, + apply_pending_writes: bool = False, ) -> StateSnapshot: if not saved: return StateSnapshot( @@ -445,12 +478,16 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): # tasks for this checkpoint next_tasks = prepare_next_tasks( saved.checkpoint, + saved.pending_writes or [], self.nodes, channels, managed, saved.config, saved.metadata.get("step", -1) + 1, - for_execution=False, + for_execution=True, + store=self.store, + checkpointer=self.checkpointer or None, + manager=None, ) # get the subgraphs subgraphs = dict(self.get_subgraphs()) @@ -484,10 +521,29 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): task_states[task.id] = subgraphs[task.name].get_state( config, subgraphs=True ) + # apply pending writes + if null_writes := [ + w[1:] for w in saved.pending_writes or [] if w[0] == NULL_TASK_ID + ]: + apply_writes( + saved.checkpoint, + channels, + [PregelTaskWrites((), INPUT, null_writes, [])], + None, + ) + if apply_pending_writes and saved.pending_writes: + for tid, k, v in saved.pending_writes: + if k in (ERROR, INTERRUPT, SCHEDULED): + continue + if tid not in next_tasks: + continue + next_tasks[tid].writes.append((k, v)) + if tasks := [t for t in next_tasks.values() if t.writes]: + apply_writes(saved.checkpoint, channels, tasks, None) # assemble the state snapshot return StateSnapshot( read_channels(channels, self.stream_channels_asis), - tuple(t.name for t in next_tasks.values()), + tuple(t.name for t in next_tasks.values() if not t.writes), patch_checkpoint_map(saved.config, saved.metadata), saved.metadata, saved.checkpoint["ts"], @@ -505,6 +561,7 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): config: RunnableConfig, saved: Optional[CheckpointTuple], recurse: Optional[BaseCheckpointSaver] = None, + apply_pending_writes: bool = False, ) -> StateSnapshot: if not saved: return StateSnapshot( @@ -533,12 +590,16 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): # tasks for this checkpoint next_tasks = prepare_next_tasks( saved.checkpoint, + saved.pending_writes or [], self.nodes, channels, managed, saved.config, saved.metadata.get("step", -1) + 1, - for_execution=False, + for_execution=True, + store=self.store, + checkpointer=self.checkpointer or None, + manager=None, ) # get the subgraphs subgraphs = {n: g async for n, g in self.aget_subgraphs()} @@ -572,10 +633,29 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): task_states[task.id] = await subgraphs[task.name].aget_state( config, subgraphs=True ) + # apply pending writes + if null_writes := [ + w[1:] for w in saved.pending_writes or [] if w[0] == NULL_TASK_ID + ]: + apply_writes( + saved.checkpoint, + channels, + [PregelTaskWrites((), INPUT, null_writes, [])], + None, + ) + if apply_pending_writes and saved.pending_writes: + for tid, k, v in saved.pending_writes: + if k in (ERROR, INTERRUPT, SCHEDULED): + continue + if tid not in next_tasks: + continue + next_tasks[tid].writes.append((k, v)) + if tasks := [t for t in next_tasks.values() if t.writes]: + apply_writes(saved.checkpoint, channels, tasks, None) # assemble the state snapshot return StateSnapshot( read_channels(channels, self.stream_channels_asis), - tuple(t.name for t in next_tasks.values()), + tuple(t.name for t in next_tasks.values() if not t.writes), patch_checkpoint_map(saved.config, saved.metadata), saved.metadata, saved.checkpoint["ts"], @@ -619,7 +699,10 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): config = merge_configs(self.config, config) if self.config else config saved = checkpointer.get_tuple(config) return self._prepare_state_snapshot( - config, saved, recurse=checkpointer if subgraphs else None + config, + saved, + recurse=checkpointer if subgraphs else None, + apply_pending_writes=CONFIG_KEY_CHECKPOINT_ID not in config[CONF], ) async def aget_state( @@ -653,7 +736,10 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): config = merge_configs(self.config, config) if self.config else config saved = await checkpointer.aget_tuple(config) return await self._aprepare_state_snapshot( - config, saved, recurse=checkpointer if subgraphs else None + config, + saved, + recurse=checkpointer if subgraphs else None, + apply_pending_writes=CONFIG_KEY_CHECKPOINT_ID not in config[CONF], ) def get_state_history( @@ -795,7 +881,7 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): raise ValueError(f"Subgraph {recast_checkpoint_ns} not found") # get last checkpoint - config = merge_configs(self.config, config) if self.config else config + config = ensure_config(self.config, config) saved = checkpointer.get_tuple(config) checkpoint = copy_checkpoint(saved.checkpoint) if saved else empty_checkpoint() checkpoint_previous_versions = ( @@ -807,62 +893,176 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): config, {CONFIG_KEY_CHECKPOINT_NS: config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "")}, ) + checkpoint_metadata = config["metadata"] if saved: checkpoint_config = patch_configurable(config, saved.config[CONF]) - # find last node that updated the state, if not provided - if values is None and as_node is None: - next_config = checkpointer.put( - checkpoint_config, - create_checkpoint(checkpoint, None, step), - { - "source": "update", - "step": step + 1, - "writes": {}, - "parents": saved.metadata.get("parents", {}) if saved else {}, - }, - {}, - ) - return patch_checkpoint_map(next_config, saved.metadata if saved else None) - elif as_node is None and not any( - v for vv in checkpoint["versions_seen"].values() for v in vv.values() - ): - if ( - isinstance(self.input_channels, str) - and self.input_channels in self.nodes - ): - as_node = self.input_channels - elif as_node is None: - last_seen_by_node = sorted( - (v, n) - for n, seen in checkpoint["versions_seen"].items() - if n in self.nodes - for v in seen.values() - ) - # if two nodes updated the state at the same time, it's ambiguous - if last_seen_by_node: - if len(last_seen_by_node) == 1: - as_node = last_seen_by_node[0][1] - elif last_seen_by_node[-1][0] != last_seen_by_node[-2][0]: - as_node = last_seen_by_node[-1][1] - if as_node is None: - raise InvalidUpdateError("Ambiguous update, specify as_node") - if as_node not in self.nodes: - raise InvalidUpdateError(f"Node {as_node} does not exist") - # update channels + checkpoint_metadata = {**saved.metadata, **checkpoint_metadata} with ChannelsManager( self.channels, checkpoint, LoopProtocol(config=config, step=step + 1, stop=step + 2), - ) as ( - channels, - managed, - ): + ) as (channels, managed): + # no values as END, just clear all tasks + if values is None and as_node == END: + if saved is not None: + # tasks for this checkpoint + next_tasks = prepare_next_tasks( + checkpoint, + saved.pending_writes or [], + self.nodes, + channels, + managed, + saved.config, + saved.metadata.get("step", -1) + 1, + for_execution=True, + store=self.store, + checkpointer=self.checkpointer or None, + manager=None, + ) + # apply null writes + if null_writes := [ + w[1:] + for w in saved.pending_writes or [] + if w[0] == NULL_TASK_ID + ]: + apply_writes( + saved.checkpoint, + channels, + [PregelTaskWrites((), INPUT, null_writes, [])], + None, + ) + # apply writes from tasks that already ran + for tid, k, v in saved.pending_writes or []: + if k in (ERROR, INTERRUPT, SCHEDULED): + continue + if tid not in next_tasks: + continue + next_tasks[tid].writes.append((k, v)) + # clear all current tasks + apply_writes(checkpoint, channels, next_tasks.values(), None) + # save checkpoint + next_config = checkpointer.put( + checkpoint_config, + create_checkpoint(checkpoint, None, step), + { + **checkpoint_metadata, + "source": "update", + "step": step + 1, + "writes": {}, + "parents": saved.metadata.get("parents", {}) if saved else {}, + }, + {}, + ) + return patch_checkpoint_map( + next_config, saved.metadata if saved else None + ) + # no values, copy checkpoint + if values is None and as_node is None: + next_checkpoint = create_checkpoint(checkpoint, None, step) + # copy checkpoint + next_config = checkpointer.put( + checkpoint_config, + next_checkpoint, + { + **checkpoint_metadata, + "source": "update", + "step": step + 1, + "writes": {}, + "parents": saved.metadata.get("parents", {}) if saved else {}, + }, + {}, + ) + return patch_checkpoint_map( + next_config, saved.metadata if saved else None + ) + if values is None and as_node == "__copy__": + next_checkpoint = create_checkpoint(checkpoint, None, step) + # copy checkpoint + next_config = checkpointer.put( + saved.parent_config or saved.config if saved else checkpoint_config, + next_checkpoint, + { + **checkpoint_metadata, + "source": "fork", + "step": step + 1, + "parents": saved.metadata.get("parents", {}) if saved else {}, + }, + {}, + ) + return patch_checkpoint_map( + next_config, saved.metadata if saved else None + ) + # apply pending writes, if not on specific checkpoint + if ( + CONFIG_KEY_CHECKPOINT_ID not in config[CONF] + and saved is not None + and saved.pending_writes + ): + # tasks for this checkpoint + next_tasks = prepare_next_tasks( + checkpoint, + saved.pending_writes, + self.nodes, + channels, + managed, + saved.config, + saved.metadata.get("step", -1) + 1, + for_execution=True, + store=self.store, + checkpointer=self.checkpointer or None, + manager=None, + ) + # apply null writes + if null_writes := [ + w[1:] for w in saved.pending_writes or [] if w[0] == NULL_TASK_ID + ]: + apply_writes( + saved.checkpoint, + channels, + [PregelTaskWrites((), INPUT, null_writes, [])], + None, + ) + # apply writes + for tid, k, v in saved.pending_writes: + if k in (ERROR, INTERRUPT, SCHEDULED): + continue + if tid not in next_tasks: + continue + next_tasks[tid].writes.append((k, v)) + if tasks := [t for t in next_tasks.values() if t.writes]: + apply_writes(checkpoint, channels, tasks, None) + # find last node that updated the state, if not provided + if as_node is None and not any( + v for vv in checkpoint["versions_seen"].values() for v in vv.values() + ): + if ( + isinstance(self.input_channels, str) + and self.input_channels in self.nodes + ): + as_node = self.input_channels + elif as_node is None: + last_seen_by_node = sorted( + (v, n) + for n, seen in checkpoint["versions_seen"].items() + if n in self.nodes + for v in seen.values() + ) + # if two nodes updated the state at the same time, it's ambiguous + if last_seen_by_node: + if len(last_seen_by_node) == 1: + as_node = last_seen_by_node[0][1] + elif last_seen_by_node[-1][0] != last_seen_by_node[-2][0]: + as_node = last_seen_by_node[-1][1] + if as_node is None: + raise InvalidUpdateError("Ambiguous update, specify as_node") + if as_node not in self.nodes: + raise InvalidUpdateError(f"Node {as_node} does not exist") # create task to run all writers of the chosen node writers = self.nodes[as_node].flat_writers if not writers: raise InvalidUpdateError(f"Node {as_node} has no writers") writes: deque[tuple[str, Any]] = deque() - task = PregelTaskWrites(as_node, writes, [INTERRUPT]) + task = PregelTaskWrites((), as_node, writes, [INTERRUPT]) task_id = str(uuid5(UUID(checkpoint["id"]), INTERRUPT)) run = RunnableSequence(*writers) if len(writers) > 1 else writers[0] # execute task @@ -891,8 +1091,14 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): ), ) # save task writes - if saved: - checkpointer.put_writes(checkpoint_config, task.writes, task_id) + # channel writes are saved to current checkpoint + # push writes are saved to next checkpoint + channel_writes, push_writes = ( + [w for w in task.writes if w[0] != PUSH], + [w for w in task.writes if w[0] == PUSH], + ) + if saved and channel_writes: + checkpointer.put_writes(checkpoint_config, channel_writes, task_id) # apply to checkpoint and save mv_writes = apply_writes( checkpoint, channels, [task], checkpointer.get_next_version @@ -903,6 +1109,7 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): checkpoint_config, checkpoint, { + **checkpoint_metadata, "source": "update", "step": step + 1, "writes": {as_node: values}, @@ -912,6 +1119,8 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): checkpoint_previous_versions, checkpoint["channel_versions"] ), ) + if push_writes: + checkpointer.put_writes(next_config, push_writes, task_id) return patch_checkpoint_map(next_config, saved.metadata if saved else None) async def aupdate_state( @@ -947,7 +1156,7 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): raise ValueError(f"Subgraph {recast_checkpoint_ns} not found") # get last checkpoint - config = merge_configs(self.config, config) if self.config else config + config = ensure_config(self.config, config) saved = await checkpointer.aget_tuple(config) checkpoint = copy_checkpoint(saved.checkpoint) if saved else empty_checkpoint() checkpoint_previous_versions = ( @@ -959,46 +1168,10 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): config, {CONFIG_KEY_CHECKPOINT_NS: config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "")}, ) + checkpoint_metadata = config["metadata"] if saved: checkpoint_config = patch_configurable(config, saved.config[CONF]) - # find last node that updated the state, if not provided - if values is None and as_node is None: - next_config = await checkpointer.aput( - checkpoint_config, - create_checkpoint(checkpoint, None, step), - { - "source": "update", - "step": step + 1, - "writes": {}, - "parents": saved.metadata.get("parents", {}) if saved else {}, - }, - {}, - ) - return patch_checkpoint_map(next_config, saved.metadata if saved else None) - elif as_node is None and not saved: - if ( - isinstance(self.input_channels, str) - and self.input_channels in self.nodes - ): - as_node = self.input_channels - elif as_node is None: - last_seen_by_node = sorted( - (v, n) - for n, seen in checkpoint["versions_seen"].items() - if n in self.nodes - for v in seen.values() - ) - # if two nodes updated the state at the same time, it's ambiguous - if last_seen_by_node: - if len(last_seen_by_node) == 1: - as_node = last_seen_by_node[0][1] - elif last_seen_by_node[-1][0] != last_seen_by_node[-2][0]: - as_node = last_seen_by_node[-1][1] - if as_node is None: - raise InvalidUpdateError("Ambiguous update, specify as_node") - if as_node not in self.nodes: - raise InvalidUpdateError(f"Node {as_node} does not exist") - # update channels, acting as the chosen node + checkpoint_metadata = {**saved.metadata, **checkpoint_metadata} async with AsyncChannelsManager( self.channels, checkpoint, @@ -1007,12 +1180,164 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): channels, managed, ): + # no values, just clear all tasks + if values is None and as_node == END: + if saved is not None: + # tasks for this checkpoint + next_tasks = prepare_next_tasks( + checkpoint, + saved.pending_writes or [], + self.nodes, + channels, + managed, + saved.config, + saved.metadata.get("step", -1) + 1, + for_execution=True, + store=self.store, + checkpointer=self.checkpointer or None, + manager=None, + ) + # apply null writes + if null_writes := [ + w[1:] + for w in saved.pending_writes or [] + if w[0] == NULL_TASK_ID + ]: + apply_writes( + saved.checkpoint, + channels, + [PregelTaskWrites((), INPUT, null_writes, [])], + None, + ) + # apply writes from tasks that already ran + for tid, k, v in saved.pending_writes or []: + if k in (ERROR, INTERRUPT, SCHEDULED): + continue + if tid not in next_tasks: + continue + next_tasks[tid].writes.append((k, v)) + # clear all current tasks + apply_writes(checkpoint, channels, next_tasks.values(), None) + # save checkpoint + next_config = await checkpointer.aput( + checkpoint_config, + create_checkpoint(checkpoint, None, step), + { + **checkpoint_metadata, + "source": "update", + "step": step + 1, + "writes": {}, + "parents": saved.metadata.get("parents", {}) if saved else {}, + }, + {}, + ) + return patch_checkpoint_map( + next_config, saved.metadata if saved else None + ) + # no values, copy checkpoint + if values is None and as_node is None: + next_checkpoint = create_checkpoint(checkpoint, None, step) + # copy checkpoint + next_config = await checkpointer.aput( + checkpoint_config, + next_checkpoint, + { + **checkpoint_metadata, + "source": "update", + "step": step + 1, + "writes": {}, + "parents": saved.metadata.get("parents", {}) if saved else {}, + }, + {}, + ) + return patch_checkpoint_map( + next_config, saved.metadata if saved else None + ) + if values is None and as_node == "__copy__": + next_checkpoint = create_checkpoint(checkpoint, None, step) + # copy checkpoint + next_config = await checkpointer.aput( + saved.parent_config or saved.config if saved else checkpoint_config, + next_checkpoint, + { + **checkpoint_metadata, + "source": "fork", + "step": step + 1, + "parents": saved.metadata.get("parents", {}) if saved else {}, + }, + {}, + ) + return patch_checkpoint_map( + next_config, saved.metadata if saved else None + ) + # apply pending writes, if not on specific checkpoint + if ( + CONFIG_KEY_CHECKPOINT_ID not in config[CONF] + and saved is not None + and saved.pending_writes + ): + # tasks for this checkpoint + next_tasks = prepare_next_tasks( + checkpoint, + saved.pending_writes, + self.nodes, + channels, + managed, + saved.config, + saved.metadata.get("step", -1) + 1, + for_execution=True, + store=self.store, + checkpointer=self.checkpointer or None, + manager=None, + ) + # apply null writes + if null_writes := [ + w[1:] for w in saved.pending_writes or [] if w[0] == NULL_TASK_ID + ]: + apply_writes( + saved.checkpoint, + channels, + [PregelTaskWrites((), INPUT, null_writes, [])], + None, + ) + for tid, k, v in saved.pending_writes: + if k in (ERROR, INTERRUPT, SCHEDULED): + continue + if tid not in next_tasks: + continue + next_tasks[tid].writes.append((k, v)) + if tasks := [t for t in next_tasks.values() if t.writes]: + apply_writes(checkpoint, channels, tasks, None) + # find last node that updated the state, if not provided + if as_node is None and not saved: + if ( + isinstance(self.input_channels, str) + and self.input_channels in self.nodes + ): + as_node = self.input_channels + elif as_node is None: + last_seen_by_node = sorted( + (v, n) + for n, seen in checkpoint["versions_seen"].items() + if n in self.nodes + for v in seen.values() + ) + # if two nodes updated the state at the same time, it's ambiguous + if last_seen_by_node: + if len(last_seen_by_node) == 1: + as_node = last_seen_by_node[0][1] + elif last_seen_by_node[-1][0] != last_seen_by_node[-2][0]: + as_node = last_seen_by_node[-1][1] + if as_node is None: + raise InvalidUpdateError("Ambiguous update, specify as_node") + if as_node not in self.nodes: + raise InvalidUpdateError(f"Node {as_node} does not exist") # create task to run all writers of the chosen node writers = self.nodes[as_node].flat_writers if not writers: raise InvalidUpdateError(f"Node {as_node} has no writers") writes: deque[tuple[str, Any]] = deque() - task = PregelTaskWrites(as_node, writes, [INTERRUPT]) + task = PregelTaskWrites((), as_node, writes, [INTERRUPT]) task_id = str(uuid5(UUID(checkpoint["id"]), INTERRUPT)) run = RunnableSequence(*writers) if len(writers) > 1 else writers[0] # execute task @@ -1041,18 +1366,28 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): ), ) # save task writes - if saved: - await checkpointer.aput_writes(checkpoint_config, writes, task_id) + # channel writes are saved to current checkpoint + # push writes are saved to next checkpoint + channel_writes, push_writes = ( + [w for w in task.writes if w[0] != PUSH], + [w for w in task.writes if w[0] == PUSH], + ) + if saved and channel_writes: + await checkpointer.aput_writes( + checkpoint_config, channel_writes, task_id + ) # apply to checkpoint and save mv_writes = apply_writes( checkpoint, channels, [task], checkpointer.get_next_version ) assert not mv_writes, "Can't write to SharedValues from update_state" checkpoint = create_checkpoint(checkpoint, channels, step + 1) + # save checkpoint, after applying writes next_config = await checkpointer.aput( checkpoint_config, checkpoint, { + **checkpoint_metadata, "source": "update", "step": step + 1, "writes": {as_node: values}, @@ -1062,6 +1397,9 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): checkpoint_previous_versions, checkpoint["channel_versions"] ), ) + # save push writes + if push_writes: + await checkpointer.aput_writes(next_config, push_writes, task_id) return patch_checkpoint_map(next_config, saved.metadata if saved else None) def _defaults( @@ -1264,12 +1602,17 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): specs=self.channels, output_keys=output_keys, stream_keys=self.stream_channels_asis, + interrupt_before=interrupt_before_, + interrupt_after=interrupt_after_, + manager=run_manager, debug=debug, ) as loop: # create runner runner = PregelRunner( submit=loop.submit, put_writes=loop.put_writes, + schedule_task=loop.accept_push, + node_finished=config[CONF].get(CONFIG_KEY_NODE_FINISHED), ) # enable subgraph streaming if subgraphs: @@ -1291,6 +1634,7 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): return waiter else: return waiter + else: get_waiter = None # type: ignore[assignment] # Similarly to Bulk Synchronous Parallel / Pregel model @@ -1298,12 +1642,7 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): # channel updates from step N are only visible in step N+1 # channels are guaranteed to be immutable for the duration of the step, # with channel updates applied only at the transition between steps - while loop.tick( - input_keys=self.input_channels, - interrupt_before=interrupt_before_, - interrupt_after=interrupt_after_, - manager=run_manager, - ): + while loop.tick(input_keys=self.input_channels): for _ in runner.tick( loop.tasks.values(), timeout=self.step_timeout, @@ -1316,11 +1655,15 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): yield from output() # handle exit if loop.status == "out_of_steps": - raise GraphRecursionError( - f"Recursion limit of {config['recursion_limit']} reached " - "without hitting a stop condition. You can increase the " - "limit by setting the `recursion_limit` config key." + msg = create_error_message( + message=( + f"Recursion limit of {config['recursion_limit']} reached " + "without hitting a stop condition. You can increase the " + "limit by setting the `recursion_limit` config key." + ), + error_code=ErrorCode.GRAPH_RECURSION_LIMIT, ) + raise GraphRecursionError(msg) # set final channel values as run output run_manager.on_chain_end(loop.output) except BaseException as e: @@ -1480,12 +1823,18 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): specs=self.channels, output_keys=output_keys, stream_keys=self.stream_channels_asis, + interrupt_before=interrupt_before_, + interrupt_after=interrupt_after_, + manager=run_manager, + debug=debug, ) as loop: # create runner runner = PregelRunner( submit=loop.submit, put_writes=loop.put_writes, + schedule_task=loop.accept_push, use_astream=do_stream is not None, + node_finished=config[CONF].get(CONFIG_KEY_NODE_FINISHED), ) # enable subgraph streaming if subgraphs: @@ -1495,6 +1844,7 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): def get_waiter() -> asyncio.Task[None]: return aioloop.create_task(stream.wait()) + else: get_waiter = None # type: ignore[assignment] # Similarly to Bulk Synchronous Parallel / Pregel model @@ -1502,12 +1852,7 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): # channel updates from step N are only visible in step N+1 # channels are guaranteed to be immutable for the duration of the step, # with channel updates applied only at the transition between steps - while loop.tick( - input_keys=self.input_channels, - interrupt_before=interrupt_before_, - interrupt_after=interrupt_after_, - manager=run_manager, - ): + while loop.tick(input_keys=self.input_channels): async for _ in runner.atick( loop.tasks.values(), timeout=self.step_timeout, @@ -1522,11 +1867,15 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): yield o # handle exit if loop.status == "out_of_steps": - raise GraphRecursionError( - f"Recursion limit of {config['recursion_limit']} reached " - "without hitting a stop condition. You can increase the " - "limit by setting the `recursion_limit` config key." + msg = create_error_message( + message=( + f"Recursion limit of {config['recursion_limit']} reached " + "without hitting a stop condition. You can increase the " + "limit by setting the `recursion_limit` config key." + ), + error_code=ErrorCode.GRAPH_RECURSION_LIMIT, ) + raise GraphRecursionError(msg) # set final channel values as run output await run_manager.on_chain_end(loop.output) except BaseException as e: diff --git a/libs/langgraph/langgraph/pregel/algo.py b/libs/langgraph/langgraph/pregel/algo.py index 4a2455aa1..564c53022 100644 --- a/libs/langgraph/langgraph/pregel/algo.py +++ b/libs/langgraph/langgraph/pregel/algo.py @@ -25,6 +25,7 @@ from langgraph.channels.base import BaseChannel from langgraph.checkpoint.base import ( BaseCheckpointSaver, Checkpoint, + PendingWrite, V, copy_checkpoint, ) @@ -35,17 +36,21 @@ from langgraph.constants import ( CONFIG_KEY_CHECKPOINT_NS, CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_READ, + CONFIG_KEY_RESUME_VALUE, CONFIG_KEY_SEND, CONFIG_KEY_STORE, CONFIG_KEY_TASK_ID, EMPTY_SEQ, INTERRUPT, + MISSING, NO_WRITES, NS_END, NS_SEP, + NULL_TASK_ID, PULL, PUSH, RESERVED, + RESUME, TAG_HIDDEN, TASKS, Send, @@ -67,6 +72,9 @@ class WritesProtocol(Protocol): """Protocol for objects containing writes to be applied to checkpoint. Implemented by PregelTaskWrites and PregelExecutableTask.""" + @property + def path(self) -> tuple[Union[str, int, tuple], ...]: ... + @property def name(self) -> str: ... @@ -81,6 +89,7 @@ class PregelTaskWrites(NamedTuple): """Simplest implementation of WritesProtocol, for usage with writes that don't originate from a runnable task, eg. graph input, update_state, etc.""" + path: tuple[Union[str, int, tuple], ...] name: str writes: Sequence[tuple[str, Any]] triggers: Sequence[str] @@ -168,7 +177,7 @@ def local_write( """Function injected under CONFIG_KEY_SEND in task config, to write to channels. Validates writes and forwards them to `commit` function.""" for chan, value in writes: - if chan == TASKS: + if chan in (PUSH, TASKS): if not isinstance(value, Send): raise InvalidUpdateError(f"Expected Send, got {value}") if value.node not in process_keys: @@ -190,6 +199,14 @@ def apply_writes( """Apply writes from a set of tasks (usually the tasks from a Pregel step) to the checkpoint and channels, and return managed values writes to be applied externally.""" + # sort tasks on path, to ensure deterministic order for update application + # any path parts after the 3rd are ignored for sorting + # (we use them for eg. task ids which aren't good for sorting) + tasks = sorted(tasks, key=lambda t: t.path[:3]) + # if no task has triggers this is applying writes from the null task only + # so we don't do anything other than update the channels written to + bump_step = any(t.triggers for t in tasks) + # update seen versions for task in tasks: checkpoint["versions_seen"].setdefault(task.name, {}).update( @@ -220,7 +237,7 @@ def apply_writes( ) # clear pending sends - if checkpoint["pending_sends"]: + if checkpoint["pending_sends"] and bump_step: checkpoint["pending_sends"].clear() # Group writes by channel @@ -228,9 +245,9 @@ def apply_writes( pending_writes_by_managed: dict[str, list[Any]] = defaultdict(list) for task in tasks: for chan, val in task.writes: - if chan == NO_WRITES: + if chan in (NO_WRITES, PUSH, RESUME, INTERRUPT): pass - elif chan == TASKS: + elif chan == TASKS: # TODO: remove branch in 1.0 checkpoint["pending_sends"].append(val) elif chan in channels: pending_writes_by_channel[chan].append(val) @@ -255,13 +272,14 @@ def apply_writes( updated_channels.add(chan) # Channels that weren't updated in this step are notified of a new step - for chan in channels: - if chan not in updated_channels: - if channels[chan].update([]) and get_next_version is not None: - checkpoint["channel_versions"][chan] = get_next_version( - max_version, - channels[chan], - ) + if bump_step: + for chan in channels: + if chan not in updated_channels: + if channels[chan].update([]) and get_next_version is not None: + checkpoint["channel_versions"][chan] = get_next_version( + max_version, + channels[chan], + ) # Return managed values writes to be applied externally return pending_writes_by_managed @@ -270,6 +288,7 @@ def apply_writes( @overload def prepare_next_tasks( checkpoint: Checkpoint, + pending_writes: Sequence[PendingWrite], processes: Mapping[str, PregelNode], channels: Mapping[str, BaseChannel], managed: ManagedValueMapping, @@ -286,6 +305,7 @@ def prepare_next_tasks( @overload def prepare_next_tasks( checkpoint: Checkpoint, + pending_writes: Sequence[PendingWrite], processes: Mapping[str, PregelNode], channels: Mapping[str, BaseChannel], managed: ManagedValueMapping, @@ -301,6 +321,7 @@ def prepare_next_tasks( def prepare_next_tasks( checkpoint: Checkpoint, + pending_writes: Sequence[PendingWrite], processes: Mapping[str, PregelNode], channels: Mapping[str, BaseChannel], managed: ManagedValueMapping, @@ -315,13 +336,14 @@ def prepare_next_tasks( """Prepare the set of tasks that will make up the next Pregel step. This is the union of all PUSH tasks (Sends) and PULL tasks (nodes triggered by edges).""" - tasks: dict[str, Union[PregelTask, PregelExecutableTask]] = {} - # Consume pending packets - for idx, _ in enumerate(checkpoint["pending_sends"]): + tasks: list[Union[PregelTask, PregelExecutableTask]] = [] + # Consume pending_sends from previous step (legacy version of Send) + for idx, _ in enumerate(checkpoint["pending_sends"]): # TODO: remove branch in 1.0 if task := prepare_single_task( (PUSH, idx), None, checkpoint=checkpoint, + pending_writes=pending_writes, processes=processes, channels=channels, managed=managed, @@ -332,7 +354,7 @@ def prepare_next_tasks( checkpointer=checkpointer, manager=manager, ): - tasks[task.id] = task + tasks.append(task) # Check if any processes should be run in next step # If so, prepare the values to be passed to them for name in processes: @@ -340,6 +362,7 @@ def prepare_next_tasks( (PULL, name), None, checkpoint=checkpoint, + pending_writes=pending_writes, processes=processes, channels=channels, managed=managed, @@ -350,15 +373,74 @@ def prepare_next_tasks( checkpointer=checkpointer, manager=manager, ): - tasks[task.id] = task - return tasks + tasks.append(task) + # Consume pending Sends from this step (new version of Send) + if any(c == PUSH for _, c, _ in pending_writes): + # group writes by task id + grouped_by_task = defaultdict(list) + for tid, c, _ in pending_writes: + grouped_by_task[tid].append(c) + # prepare send tasks from grouped writes + # 1. start from sends originating from existing tasks + tidx = 0 + while tidx < len(tasks): + task = tasks[tidx] + if twrites := grouped_by_task.pop(task.id, None): + for idx, c in enumerate(twrites): + if c != PUSH: + continue + if next_task := prepare_single_task( + (PUSH, task.path, idx, task.id), + None, + checkpoint=checkpoint, + pending_writes=pending_writes, + processes=processes, + channels=channels, + managed=managed, + config=config, + step=step, + for_execution=for_execution, + store=store, + checkpointer=checkpointer, + manager=manager, + ): + tasks.append(next_task) + tidx += 1 + # key tasks by id + task_map = {t.id: t for t in tasks} + # 2. create new tasks for remaining sends (eg. from update_state) + for tid, writes in grouped_by_task.items(): + task = task_map.get(tid) + for idx, c in enumerate(writes): + if c != PUSH: + continue + if next_task := prepare_single_task( + (PUSH, task.path if task else (), idx, tid), + None, + checkpoint=checkpoint, + pending_writes=pending_writes, + processes=processes, + channels=channels, + managed=managed, + config=config, + step=step, + for_execution=for_execution, + store=store, + checkpointer=checkpointer, + manager=manager, + ): + task_map[next_task.id] = next_task + else: + task_map = {t.id: t for t in tasks} + return task_map def prepare_single_task( - task_path: tuple[str, Union[int, str]], + task_path: tuple[Union[str, int, tuple], ...], task_id_checksum: Optional[str], *, checkpoint: Checkpoint, + pending_writes: Sequence[PendingWrite], processes: Mapping[str, PregelNode], channels: Mapping[str, BaseChannel], managed: ManagedValueMapping, @@ -376,31 +458,74 @@ def prepare_single_task( parent_ns = configurable.get(CONFIG_KEY_CHECKPOINT_NS, "") if task_path[0] == PUSH: - idx = int(task_path[1]) - if idx >= len(checkpoint["pending_sends"]): - return - packet = checkpoint["pending_sends"][idx] - if not isinstance(packet, Send): - logger.warning( - f"Ignoring invalid packet type {type(packet)} in pending sends" + if len(task_path) == 2: # TODO: remove branch in 1.0 + # legacy SEND tasks, executed in superstep n+1 + # (PUSH, idx of pending send) + idx = cast(int, task_path[1]) + if idx >= len(checkpoint["pending_sends"]): + return + packet = checkpoint["pending_sends"][idx] + if not isinstance(packet, Send): + logger.warning( + f"Ignoring invalid packet type {type(packet)} in pending sends" + ) + return + if packet.node not in processes: + logger.warning( + f"Ignoring unknown node name {packet.node} in pending sends" + ) + return + # create task id + triggers = [PUSH] + checkpoint_ns = ( + f"{parent_ns}{NS_SEP}{packet.node}" if parent_ns else packet.node ) + task_id = _uuid5_str( + checkpoint_id, + checkpoint_ns, + str(step), + packet.node, + PUSH, + str(idx), + ) + elif len(task_path) == 4: + # new PUSH tasks, executed in superstep n + # (PUSH, parent task path, idx of PUSH write, id of parent task) + task_path_t = cast(tuple[str, tuple, int, str], task_path) + writes_for_path = [w for w in pending_writes if w[0] == task_path_t[3]] + if task_path_t[2] >= len(writes_for_path): + logger.warning( + f"Ignoring invalid write index {task_path[2]} in pending writes" + ) + return + packet = writes_for_path[task_path_t[2]][2] + if not isinstance(packet, Send): + logger.warning( + f"Ignoring invalid packet type {type(packet)} in pending writes" + ) + return + if packet.node not in processes: + logger.warning( + f"Ignoring unknown node name {packet.node} in pending writes" + ) + return + # create task id + triggers = [PUSH] + checkpoint_ns = ( + f"{parent_ns}{NS_SEP}{packet.node}" if parent_ns else packet.node + ) + task_id = _uuid5_str( + checkpoint_id, + checkpoint_ns, + str(step), + packet.node, + PUSH, + _tuple_str(task_path[1]), + str(task_path[2]), + ) + else: + logger.warning(f"Ignoring invalid PUSH task path {task_path}") return - if packet.node not in processes: - logger.warning(f"Ignoring unknown node name {packet.node} in pending sends") - return - # create task id - triggers = [PUSH] - checkpoint_ns = ( - f"{parent_ns}{NS_SEP}{packet.node}" if parent_ns else packet.node - ) - task_id = _uuid5_str( - checkpoint_id, - checkpoint_ns, - str(step), - packet.node, - PUSH, - str(idx), - ) task_checkpoint_ns = f"{checkpoint_ns}:{task_id}" metadata = { "langgraph_step": step, @@ -410,7 +535,7 @@ def prepare_single_task( "langgraph_checkpoint_ns": task_checkpoint_ns, } if task_id_checksum is not None: - assert task_id == task_id_checksum + assert task_id == task_id_checksum, f"{task_id} != {task_id_checksum}" if for_execution: proc = processes[packet.node] if node := proc.node: @@ -444,7 +569,9 @@ def prepare_single_task( checkpoint, channels, managed, - PregelTaskWrites(packet.node, writes, triggers), + PregelTaskWrites( + task_path, packet.node, writes, triggers + ), config, ), CONFIG_KEY_STORE: ( @@ -460,6 +587,14 @@ def prepare_single_task( }, CONFIG_KEY_CHECKPOINT_ID: None, CONFIG_KEY_CHECKPOINT_NS: task_checkpoint_ns, + CONFIG_KEY_RESUME_VALUE: next( + ( + v + for tid, c, v in pending_writes + if tid in (NULL_TASK_ID, task_id) and c == RESUME + ), + MISSING, + ), }, ), triggers, @@ -472,6 +607,7 @@ def prepare_single_task( else: return PregelTask(task_id, packet.node, task_path) elif task_path[0] == PULL: + # (PULL, node name) name = cast(str, task_path[1]) if name not in processes: return @@ -552,7 +688,7 @@ def prepare_single_task( checkpoint, channels, managed, - PregelTaskWrites(name, writes, triggers), + PregelTaskWrites(task_path, name, writes, triggers), config, ), CONFIG_KEY_STORE: ( @@ -568,6 +704,15 @@ def prepare_single_task( }, CONFIG_KEY_CHECKPOINT_ID: None, CONFIG_KEY_CHECKPOINT_NS: task_checkpoint_ns, + CONFIG_KEY_RESUME_VALUE: next( + ( + v + for tid, c, v in pending_writes + if tid in (NULL_TASK_ID, task_id) + and c == RESUME + ), + MISSING, + ), }, ), triggers, @@ -633,3 +778,12 @@ def _uuid5_str(namespace: bytes, *parts: str) -> str: sha.update(b"".join(p.encode() for p in parts)) hex = sha.hexdigest() return f"{hex[:8]}-{hex[8:12]}-{hex[12:16]}-{hex[16:20]}-{hex[20:32]}" + + +def _tuple_str(tup: Union[str, int, tuple]) -> str: + """Generate a string representation of a tuple.""" + return ( + f"({', '.join(_tuple_str(x) for x in tup)})" + if isinstance(tup, (tuple, list)) + else str(tup) + ) diff --git a/libs/langgraph/langgraph/pregel/debug.py b/libs/langgraph/langgraph/pregel/debug.py index d772e7cba..95b30f118 100644 --- a/libs/langgraph/langgraph/pregel/debug.py +++ b/libs/langgraph/langgraph/pregel/debug.py @@ -191,6 +191,14 @@ def map_debug_checkpoint( "state": t.state, } if t.error + else { + "id": t.id, + "name": t.name, + "result": t.result, + "interrupts": tuple(asdict(i) for i in t.interrupts), + "state": t.state, + } + if t.result else { "id": t.id, "name": t.name, @@ -208,7 +216,7 @@ def print_step_tasks(step: int, next_tasks: list[PregelExecutableTask]) -> None: print( f"{get_colored_text(f'[{step}:tasks]', color='blue')} " + get_bolded_text( - f"Starting step {step} with {n_tasks} task{'s' if n_tasks != 1 else ''}:\n" + f"Starting {n_tasks} task{'s' if n_tasks != 1 else ''} for step {step}:\n" ) + "\n".join( f"- {get_colored_text(task.name, 'green')} -> {pformat(task.input)}" diff --git a/libs/langgraph/langgraph/pregel/executor.py b/libs/langgraph/langgraph/pregel/executor.py index 691098b7a..246510fb4 100644 --- a/libs/langgraph/langgraph/pregel/executor.py +++ b/libs/langgraph/langgraph/pregel/executor.py @@ -86,19 +86,21 @@ class BackgroundExecutor(ContextManager): exc_value: Optional[BaseException], traceback: Optional[TracebackType], ) -> Optional[bool]: + # copy the tasks as done() callback may modify the dict + tasks = self.tasks.copy() # cancel all tasks that should be cancelled - for task, (cancel, _) in self.tasks.items(): + for task, (cancel, _) in tasks.items(): if cancel: task.cancel() # wait for all tasks to finish - if tasks := {t for t in self.tasks if not t.done()}: - concurrent.futures.wait(tasks) + if pending := {t for t in tasks if not t.done()}: + concurrent.futures.wait(pending) # shutdown the executor self.stack.__exit__(exc_type, exc_value, traceback) # re-raise the first exception that occurred in a task if exc_type is None: # if there's already an exception being raised, don't raise another one - for task, (_, reraise) in self.tasks.items(): + for task, (_, reraise) in tasks.items(): if not reraise: continue try: @@ -116,11 +118,17 @@ class AsyncBackgroundExecutor(AsyncContextManager): - re-raises the first exception from tasks with `__reraise_on_exit__=True` ignoring CancelledError""" - def __init__(self) -> None: + def __init__(self, config: RunnableConfig) -> None: self.context_not_supported = sys.version_info < (3, 11) self.tasks: dict[asyncio.Task, tuple[bool, bool]] = {} self.sentinel = object() self.loop = asyncio.get_running_loop() + if max_concurrency := config.get("max_concurrency"): + self.semaphore: Optional[asyncio.Semaphore] = asyncio.Semaphore( + max_concurrency + ) + else: + self.semaphore = None def submit( # type: ignore[valid-type] self, @@ -132,6 +140,8 @@ class AsyncBackgroundExecutor(AsyncContextManager): **kwargs: P.kwargs, ) -> asyncio.Task[T]: coro = cast(Coroutine[None, None, T], fn(*args, **kwargs)) + if self.semaphore: + coro = gated(self.semaphore, coro) if self.context_not_supported: task = self.loop.create_task(coro, name=__name__) else: @@ -161,17 +171,19 @@ class AsyncBackgroundExecutor(AsyncContextManager): exc_value: Optional[BaseException], traceback: Optional[TracebackType], ) -> None: + # copy the tasks as done() callback may modify the dict + tasks = self.tasks.copy() # cancel all tasks that should be cancelled - for task, (cancel, _) in self.tasks.items(): + for task, (cancel, _) in tasks.items(): if cancel: task.cancel(self.sentinel) # wait for all tasks to finish - if self.tasks: - await asyncio.wait(self.tasks) + if tasks: + await asyncio.wait(tasks) # if there's already an exception being raised, don't raise another one if exc_type is None: # re-raise the first exception that occurred in a task - for task, (_, reraise) in self.tasks.items(): + for task, (_, reraise) in tasks.items(): if not reraise: continue try: @@ -179,3 +191,9 @@ class AsyncBackgroundExecutor(AsyncContextManager): raise exc except asyncio.CancelledError: pass + + +async def gated(semaphore: asyncio.Semaphore, coro: Coroutine[None, None, T]) -> T: + """A coroutine that waits for a semaphore before running another coroutine.""" + async with semaphore: + return await coro diff --git a/libs/langgraph/langgraph/pregel/io.py b/libs/langgraph/langgraph/pregel/io.py index 2a1f629cb..6695e1ce0 100644 --- a/libs/langgraph/langgraph/pregel/io.py +++ b/libs/langgraph/langgraph/pregel/io.py @@ -1,11 +1,31 @@ from typing import Any, Iterator, Literal, Mapping, Optional, Sequence, TypeVar, Union +from uuid import UUID from langchain_core.runnables.utils import AddableDict from langgraph.channels.base import BaseChannel, EmptyChannelError -from langgraph.constants import EMPTY_SEQ, ERROR, INTERRUPT, TAG_HIDDEN +from langgraph.constants import ( + EMPTY_SEQ, + ERROR, + FF_SEND_V2, + INTERRUPT, + NULL_TASK_ID, + PUSH, + RESUME, + TAG_HIDDEN, + TASKS, +) from langgraph.pregel.log import logger -from langgraph.types import PregelExecutableTask +from langgraph.types import Command, PregelExecutableTask, Send + + +def is_task_id(task_id: str) -> bool: + """Check if a string is a valid task id.""" + try: + UUID(task_id) + except ValueError: + return False + return True def read_channel( @@ -44,6 +64,36 @@ def read_channels( return values +def map_command( + cmd: Command, +) -> Iterator[tuple[str, str, Any]]: + """Map input chunk to a sequence of pending writes in the form (channel, value).""" + if cmd.send: + if isinstance(cmd.send, (tuple, list)): + sends = cmd.send + else: + sends = [cmd.send] + for send in sends: + if not isinstance(send, Send): + raise TypeError( + f"In Command.send, expected Send, got {type(send).__name__}" + ) + yield (NULL_TASK_ID, PUSH if FF_SEND_V2 else TASKS, send) + if cmd.resume: + if isinstance(cmd.resume, dict) and all(is_task_id(k) for k in cmd.resume): + for tid, resume in cmd.resume.items(): + yield (tid, RESUME, resume) + else: + yield (NULL_TASK_ID, RESUME, cmd.resume) + if cmd.update: + if not isinstance(cmd.update, dict): + raise TypeError( + f"Expected cmd.update to be a dict mapping channel names to update values, got {type(cmd.update).__name__}" + ) + for k, v in cmd.update.items(): + yield (NULL_TASK_ID, k, v) + + def map_input( input_channels: Union[str, Sequence[str]], chunk: Optional[Union[dict[str, Any], Any]], diff --git a/libs/langgraph/langgraph/pregel/loop.py b/libs/langgraph/langgraph/pregel/loop.py index f25fa2300..6a9b6a95e 100644 --- a/libs/langgraph/langgraph/pregel/loop.py +++ b/libs/langgraph/langgraph/pregel/loop.py @@ -1,6 +1,6 @@ import asyncio import concurrent.futures -from collections import deque +from collections import defaultdict, deque from contextlib import AsyncExitStack, ExitStack from types import TracebackType from typing import ( @@ -52,6 +52,9 @@ from langgraph.constants import ( INPUT, INTERRUPT, NS_SEP, + NULL_TASK_ID, + PUSH, + RESUME, SCHEDULED, TAG_HIDDEN, ) @@ -74,6 +77,7 @@ from langgraph.pregel.algo import ( apply_writes, increment, prepare_next_tasks, + prepare_single_task, should_interrupt, ) from langgraph.pregel.debug import ( @@ -90,6 +94,7 @@ from langgraph.pregel.executor import ( Submit, ) from langgraph.pregel.io import ( + map_command, map_input, map_output_updates, map_output_values, @@ -100,7 +105,13 @@ from langgraph.pregel.manager import AsyncChannelsManager, ChannelsManager from langgraph.pregel.read import PregelNode from langgraph.pregel.utils import get_new_channel_versions from langgraph.store.base import BaseStore -from langgraph.types import All, LoopProtocol, PregelExecutableTask, StreamProtocol +from langgraph.types import ( + All, + Command, + LoopProtocol, + PregelExecutableTask, + StreamProtocol, +) from langgraph.utils.config import patch_configurable V = TypeVar("V") @@ -116,7 +127,7 @@ def DuplexStream(*streams: StreamProtocol) -> StreamProtocol: def __call__(value: StreamChunk) -> None: for stream in streams: if value[1] in stream.modes: - stream(value) # type: ignore + stream(value) return StreamProtocol(__call__, {mode for s in streams for mode in s.modes}) @@ -130,6 +141,9 @@ class PregelLoop(LoopProtocol): stream_keys: Union[str, Sequence[str]] skip_done_tasks: bool is_nested: bool + manager: Union[None, AsyncParentRunManager, ParentRunManager] + interrupt_after: Union[All, Sequence[str]] + interrupt_before: Union[All, Sequence[str]] checkpointer_get_next_version: GetNextVersion checkpointer_put_writes: Optional[ @@ -162,6 +176,7 @@ class PregelLoop(LoopProtocol): "pending", "done", "interrupt_before", "interrupt_after", "out_of_steps" ] tasks: dict[str, PregelExecutableTask] + to_interrupt: list[PregelExecutableTask] output: Union[None, dict[str, Any], Any] = None # public @@ -178,6 +193,9 @@ class PregelLoop(LoopProtocol): specs: Mapping[str, Union[BaseChannel, ManagedValueSpec]], output_keys: Union[str, Sequence[str]], stream_keys: Union[str, Sequence[str]], + interrupt_after: Union[All, Sequence[str]] = EMPTY_SEQ, + interrupt_before: Union[All, Sequence[str]] = EMPTY_SEQ, + manager: Union[None, AsyncParentRunManager, ParentRunManager] = None, check_subgraphs: bool = True, debug: bool = False, ) -> None: @@ -194,6 +212,9 @@ class PregelLoop(LoopProtocol): self.specs = specs self.output_keys = output_keys self.stream_keys = stream_keys + self.interrupt_after = interrupt_after + self.interrupt_before = interrupt_before + self.manager = manager self.is_nested = CONFIG_KEY_TASK_ID in self.config.get(CONF, {}) self.skip_done_tasks = ( CONFIG_KEY_CHECKPOINT_ID not in config[CONF] @@ -209,7 +230,11 @@ class PregelLoop(LoopProtocol): ) if check_subgraphs and self.is_nested and self.checkpointer is not None: if self.config[CONF][CONFIG_KEY_CHECKPOINT_NS] in _SEEN_CHECKPOINT_NS: - raise MultipleSubgraphsError + raise MultipleSubgraphsError( + "Multiple subgraphs called inside the same node\n\n" + "Troubleshooting URL: https://python.langchain.com/docs" + "/troubleshooting/errors/MULTIPLE_SUBGRAPHS/" + ) else: _SEEN_CHECKPOINT_NS.add(self.config[CONF][CONFIG_KEY_CHECKPOINT_NS]) if ( @@ -257,15 +282,60 @@ class PregelLoop(LoopProtocol): task_id, ) # output writes - self._output_writes(task_id, writes) + if hasattr(self, "tasks"): + self._output_writes(task_id, writes) + + def accept_push( + self, task: PregelExecutableTask, write_idx: int + ) -> Optional[PregelExecutableTask]: + """Accept a PUSH from a task, potentially returning a new task to start.""" + # don't start if an earlier PUSH has already triggered an interrupt + if self.to_interrupt: + return + # don't start if we should interrupt *after* the original task + if should_interrupt(self.checkpoint, self.interrupt_after, [task]): + self.to_interrupt.append(task) + return + if pushed := cast( + Optional[PregelExecutableTask], + prepare_single_task( + (PUSH, task.path, write_idx, task.id), + None, + checkpoint=self.checkpoint, + pending_writes=[(task.id, *w) for w in task.writes], + processes=self.nodes, + channels=self.channels, + managed=self.managed, + config=self.config, + step=self.step, + for_execution=True, + store=self.store, + checkpointer=self.checkpointer, + manager=self.manager, + ), + ): + # don't start if we should interrupt *before* the new task + if should_interrupt(self.checkpoint, self.interrupt_before, [pushed]): + self.to_interrupt.append(pushed) + return + # produce debug output + self._emit("debug", map_debug_tasks, self.step, [pushed]) + # debug flag + if self.debug: + print_step_tasks(self.step, [pushed]) + # save the new task + self.tasks[pushed.id] = pushed + # match any pending writes to the new task + if self.skip_done_tasks: + self._match_writes({pushed.id: pushed}) + # return the new task, to be started, if not run before + if not pushed.writes: + return pushed def tick( self, *, input_keys: Union[str, Sequence[str]], - interrupt_after: Union[All, Sequence[str]] = EMPTY_SEQ, - interrupt_before: Union[All, Sequence[str]] = EMPTY_SEQ, - manager: Union[None, AsyncParentRunManager, ParentRunManager] = None, ) -> bool: """Execute a single iteration of the Pregel loop. Returns True if more iterations are needed.""" @@ -274,6 +344,10 @@ class PregelLoop(LoopProtocol): if self.input not in (INPUT_DONE, INPUT_RESUMING): self._first(input_keys=input_keys) + elif self.to_interrupt: + # if we need to interrupt, do so + self.status = "interrupt_before" + raise GraphInterrupt() elif all(task.writes for task in self.tasks.values()): writes = [w for t in self.tasks.values() for w in t.writes] # debug flag @@ -281,9 +355,11 @@ class PregelLoop(LoopProtocol): print_step_writes( self.step, writes, - [self.stream_keys] - if isinstance(self.stream_keys, str) - else self.stream_keys, + ( + [self.stream_keys] + if isinstance(self.stream_keys, str) + else self.stream_keys + ), ) # all tasks have finished mv_writes = apply_writes( @@ -301,6 +377,8 @@ class PregelLoop(LoopProtocol): ) # clear pending writes self.checkpoint_pending_writes.clear() + # "not skip_done_tasks" only applies to first tick after resuming + self.skip_done_tasks = True # save checkpoint self._put_checkpoint( { @@ -314,7 +392,9 @@ class PregelLoop(LoopProtocol): } ) # after execution, check if we should interrupt - if should_interrupt(self.checkpoint, interrupt_after, self.tasks.values()): + if should_interrupt( + self.checkpoint, self.interrupt_after, self.tasks.values() + ): self.status = "interrupt_after" raise GraphInterrupt() else: @@ -325,19 +405,33 @@ class PregelLoop(LoopProtocol): self.status = "out_of_steps" return False + # apply NULL writes + if null_writes := [ + w[1:] for w in self.checkpoint_pending_writes if w[0] == NULL_TASK_ID + ]: + mv_writes = apply_writes( + self.checkpoint, + self.channels, + [PregelTaskWrites((), INPUT, null_writes, [])], + self.checkpointer_get_next_version, + ) + for key, values in mv_writes.items(): + self._update_mv(key, values) # prepare next tasks self.tasks = prepare_next_tasks( self.checkpoint, + self.checkpoint_pending_writes, self.nodes, self.channels, self.managed, self.config, self.step, for_execution=True, - manager=manager, + manager=self.manager, store=self.store, checkpointer=self.checkpointer, ) + self.to_interrupt = [] # produce debug output if self._checkpointer_put_after_previous is not None: @@ -375,36 +469,16 @@ class PregelLoop(LoopProtocol): # if there are pending writes from a previous loop, apply them if self.skip_done_tasks and self.checkpoint_pending_writes: - for tid, k, v in self.checkpoint_pending_writes: - if k in (ERROR, INTERRUPT): - continue - if task := self.tasks.get(tid): - if k == SCHEDULED: - if v == max( - self.checkpoint["versions_seen"] - .get(INTERRUPT, {}) - .values(), - default=None, - ): - self.tasks[tid] = task._replace(scheduled=True) - else: - task.writes.append((k, v)) - # print output for any tasks we applied previous writes to - for task in self.tasks.values(): - if task.writes: - self._output_writes(task.id, task.writes, cached=True) + self._match_writes(self.tasks) # if all tasks have finished, re-tick if all(task.writes for task in self.tasks.values()): - return self.tick( - input_keys=input_keys, - interrupt_after=interrupt_after, - interrupt_before=interrupt_before, - manager=manager, - ) + return self.tick(input_keys=input_keys) # before execution, check if we should interrupt - if should_interrupt(self.checkpoint, interrupt_before, self.tasks.values()): + if should_interrupt( + self.checkpoint, self.interrupt_before, self.tasks.values() + ): self.status = "interrupt_before" raise GraphInterrupt() @@ -415,10 +489,29 @@ class PregelLoop(LoopProtocol): if self.debug: print_step_tasks(self.step, list(self.tasks.values())) + # print output for any tasks we applied previous writes to + for task in self.tasks.values(): + if task.writes: + self._output_writes(task.id, task.writes, cached=True) + return True # private + def _match_writes(self, tasks: Mapping[str, PregelExecutableTask]) -> None: + for tid, k, v in self.checkpoint_pending_writes: + if k in (ERROR, INTERRUPT, RESUME): + continue + if task := tasks.get(tid): + if k == SCHEDULED: + if v == max( + self.checkpoint["versions_seen"].get(INTERRUPT, {}).values(), + default=None, + ): + self.tasks[tid] = task._replace(scheduled=True) + else: + task.writes.append((k, v)) + def _first(self, *, input_keys: Union[str, Sequence[str]]) -> None: # resuming from previous checkpoint requires # - finding a previous checkpoint @@ -439,8 +532,20 @@ class PregelLoop(LoopProtocol): self._emit( "values", map_output_values, self.output_keys, True, self.channels ) + # map command to writes + elif isinstance(self.input, Command): + writes: defaultdict[str, list[tuple[str, Any]]] = defaultdict(list) + # group writes by task ID + for tid, c, v in map_command(self.input): + writes[tid].append((c, v)) + if not writes: + raise EmptyInputError("Received empty Command input") + # save writes + for tid, ws in writes.items(): + self.put_writes(tid, ws) # map inputs to channel updates elif input_writes := deque(map_input(input_keys, self.input)): + # TODO shouldn't these writes be passed to put_writes too? # check if we should delegate (used by subgraphs in distributed mode) if self.config[CONF].get(CONFIG_KEY_DELEGATE): raise GraphDelegate( @@ -454,6 +559,7 @@ class PregelLoop(LoopProtocol): # discard any unfinished tasks from previous checkpoint discard_tasks = prepare_next_tasks( self.checkpoint, + self.checkpoint_pending_writes, self.nodes, self.channels, self.managed, @@ -468,7 +574,10 @@ class PregelLoop(LoopProtocol): mv_writes = apply_writes( self.checkpoint, self.channels, - [*discard_tasks.values(), PregelTaskWrites(INPUT, input_writes, [])], + [ + *discard_tasks.values(), + PregelTaskWrites((), INPUT, input_writes, []), + ], self.checkpointer_get_next_version, ) assert not mv_writes, "Can't write to SharedValues in graph input" @@ -485,6 +594,8 @@ class PregelLoop(LoopProtocol): ) def _put_checkpoint(self, metadata: CheckpointMetadata) -> None: + for k, v in self.config["metadata"].items(): + metadata.setdefault(k, v) # type: ignore # assign step and parents metadata["step"] = self.step metadata["parents"] = self.config[CONF].get(CONFIG_KEY_CHECKPOINT_MAP, {}) @@ -493,9 +604,11 @@ class PregelLoop(LoopProtocol): print_step_checkpoint( metadata, self.channels, - [self.stream_keys] - if isinstance(self.stream_keys, str) - else self.stream_keys, + ( + [self.stream_keys] + if isinstance(self.stream_keys, str) + else self.stream_keys + ), ) # create new checkpoint self.checkpoint = create_checkpoint(self.checkpoint, self.channels, self.step) @@ -560,11 +673,33 @@ class PregelLoop(LoopProtocol): # save final output self.output = read_channels(self.channels, self.output_keys) if suppress: - # suppress interrupt + # emit one last "values" event, with pending writes applied + if ( + hasattr(self, "tasks") + and self.checkpoint_pending_writes + and any(task.writes for task in self.tasks.values()) + ): + mv_writes = apply_writes( + self.checkpoint, + self.channels, + self.tasks.values(), + self.checkpointer_get_next_version, + ) + for key, values in mv_writes.items(): + self._update_mv(key, values) + self._emit( + "values", + map_output_values, + self.output_keys, + [w for t in self.tasks.values() for w in t.writes], + self.channels, + ) + # emit INTERRUPT event self._emit( "updates", lambda: iter([{INTERRUPT: cast(GraphInterrupt, exc_value).args[0]}]), ) + # suppress interrupt return True def _emit( @@ -579,7 +714,7 @@ class PregelLoop(LoopProtocol): if mode not in self.stream.modes: return for v in values(*args, **kwargs): - self.stream((self.checkpoint_ns, mode, v)) # type: ignore + self.stream((self.checkpoint_ns, mode, v)) def _output_writes( self, task_id: str, writes: Sequence[tuple[str, Any]], *, cached: bool = False @@ -618,6 +753,9 @@ class SyncPregelLoop(PregelLoop, ContextManager): checkpointer: Optional[BaseCheckpointSaver], nodes: Mapping[str, PregelNode], specs: Mapping[str, Union[BaseChannel, ManagedValueSpec]], + manager: Union[None, AsyncParentRunManager, ParentRunManager] = None, + interrupt_after: Union[All, Sequence[str]] = EMPTY_SEQ, + interrupt_before: Union[All, Sequence[str]] = EMPTY_SEQ, output_keys: Union[str, Sequence[str]] = EMPTY_SEQ, stream_keys: Union[str, Sequence[str]] = EMPTY_SEQ, check_subgraphs: bool = True, @@ -633,7 +771,10 @@ class SyncPregelLoop(PregelLoop, ContextManager): specs=specs, output_keys=output_keys, stream_keys=stream_keys, + interrupt_after=interrupt_after, + interrupt_before=interrupt_before, check_subgraphs=check_subgraphs, + manager=manager, debug=debug, ) self.stack = ExitStack() @@ -744,6 +885,9 @@ class AsyncPregelLoop(PregelLoop, AsyncContextManager): checkpointer: Optional[BaseCheckpointSaver], nodes: Mapping[str, PregelNode], specs: Mapping[str, Union[BaseChannel, ManagedValueSpec]], + interrupt_after: Union[All, Sequence[str]] = EMPTY_SEQ, + interrupt_before: Union[All, Sequence[str]] = EMPTY_SEQ, + manager: Union[None, AsyncParentRunManager, ParentRunManager] = None, output_keys: Union[str, Sequence[str]] = EMPTY_SEQ, stream_keys: Union[str, Sequence[str]] = EMPTY_SEQ, check_subgraphs: bool = True, @@ -759,7 +903,10 @@ class AsyncPregelLoop(PregelLoop, AsyncContextManager): specs=specs, output_keys=output_keys, stream_keys=stream_keys, + interrupt_after=interrupt_after, + interrupt_before=interrupt_before, check_subgraphs=check_subgraphs, + manager=manager, debug=debug, ) self.stack = AsyncExitStack() @@ -839,7 +986,9 @@ class AsyncPregelLoop(PregelLoop, AsyncContextManager): else [] ) - self.submit = await self.stack.enter_async_context(AsyncBackgroundExecutor()) + self.submit = await self.stack.enter_async_context( + AsyncBackgroundExecutor(self.config) + ) self.channels, self.managed = await self.stack.enter_async_context( AsyncChannelsManager(self.specs, self.checkpoint, self) ) diff --git a/libs/langgraph/langgraph/pregel/messages.py b/libs/langgraph/langgraph/pregel/messages.py index 08327805f..2c31de279 100644 --- a/libs/langgraph/langgraph/pregel/messages.py +++ b/libs/langgraph/langgraph/pregel/messages.py @@ -17,7 +17,7 @@ from langchain_core.messages import BaseMessage from langchain_core.outputs import ChatGenerationChunk, LLMResult from langchain_core.tracers._streaming import T, _StreamingCallbackHandler -from langgraph.constants import NS_SEP +from langgraph.constants import NS_SEP, TAG_HIDDEN, TAG_NOSTREAM from langgraph.pregel.loop import StreamChunk Meta = tuple[tuple[str, ...], dict[str, Any]] @@ -63,7 +63,7 @@ class StreamMessagesHandler(BaseCallbackHandler, _StreamingCallbackHandler): metadata: Optional[dict[str, Any]] = None, **kwargs: Any, ) -> Any: - if metadata: + if metadata and (not tags or TAG_NOSTREAM not in tags): self.metadata[run_id] = ( tuple(cast(str, metadata["langgraph_checkpoint_ns"]).split(NS_SEP)), metadata, @@ -114,7 +114,11 @@ class StreamMessagesHandler(BaseCallbackHandler, _StreamingCallbackHandler): metadata: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Any: - if metadata and kwargs.get("name") == metadata.get("langgraph_node"): + if ( + metadata + and kwargs.get("name") == metadata.get("langgraph_node") + and (not tags or TAG_HIDDEN not in tags) + ): self.metadata[run_id] = ( tuple(cast(str, metadata["langgraph_checkpoint_ns"]).split(NS_SEP)), metadata, diff --git a/libs/langgraph/langgraph/pregel/protocol.py b/libs/langgraph/langgraph/pregel/protocol.py index 34789284b..ac046e949 100644 --- a/libs/langgraph/langgraph/pregel/protocol.py +++ b/libs/langgraph/langgraph/pregel/protocol.py @@ -1,27 +1,29 @@ +from abc import ABC, abstractmethod from typing import ( Any, AsyncIterator, Iterator, Optional, - Protocol, Sequence, Union, - runtime_checkable, ) -from langchain_core.runnables import RunnableConfig +from langchain_core.runnables import Runnable, RunnableConfig from langchain_core.runnables.graph import Graph as DrawableGraph from typing_extensions import Self from langgraph.pregel.types import All, StateSnapshot, StreamMode -@runtime_checkable -class PregelProtocol(Protocol): +class PregelProtocol( + Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]], ABC +): + @abstractmethod def with_config( self, config: Optional[RunnableConfig] = None, **kwargs: Any ) -> Self: ... + @abstractmethod def get_graph( self, config: Optional[RunnableConfig] = None, @@ -29,6 +31,7 @@ class PregelProtocol(Protocol): xray: Union[int, bool] = False, ) -> DrawableGraph: ... + @abstractmethod async def aget_graph( self, config: Optional[RunnableConfig] = None, @@ -36,22 +39,17 @@ class PregelProtocol(Protocol): xray: Union[int, bool] = False, ) -> DrawableGraph: ... - def get_subgraphs( - self, namespace: Optional[str] = None, recurse: bool = False - ) -> Iterator[tuple[str, "PregelProtocol"]]: ... - - def aget_subgraphs( - self, namespace: Optional[str] = None, recurse: bool = False - ) -> AsyncIterator[tuple[str, "PregelProtocol"]]: ... - + @abstractmethod def get_state( self, config: RunnableConfig, *, subgraphs: bool = False ) -> StateSnapshot: ... + @abstractmethod async def aget_state( self, config: RunnableConfig, *, subgraphs: bool = False ) -> StateSnapshot: ... + @abstractmethod def get_state_history( self, config: RunnableConfig, @@ -61,6 +59,7 @@ class PregelProtocol(Protocol): limit: Optional[int] = None, ) -> Iterator[StateSnapshot]: ... + @abstractmethod def aget_state_history( self, config: RunnableConfig, @@ -70,6 +69,7 @@ class PregelProtocol(Protocol): limit: Optional[int] = None, ) -> AsyncIterator[StateSnapshot]: ... + @abstractmethod def update_state( self, config: RunnableConfig, @@ -77,6 +77,7 @@ class PregelProtocol(Protocol): as_node: Optional[str] = None, ) -> RunnableConfig: ... + @abstractmethod async def aupdate_state( self, config: RunnableConfig, @@ -84,6 +85,7 @@ class PregelProtocol(Protocol): as_node: Optional[str] = None, ) -> RunnableConfig: ... + @abstractmethod def stream( self, input: Union[dict[str, Any], Any], @@ -95,6 +97,7 @@ class PregelProtocol(Protocol): subgraphs: bool = False, ) -> Iterator[Union[dict[str, Any], Any]]: ... + @abstractmethod def astream( self, input: Union[dict[str, Any], Any], @@ -106,6 +109,7 @@ class PregelProtocol(Protocol): subgraphs: bool = False, ) -> AsyncIterator[Union[dict[str, Any], Any]]: ... + @abstractmethod def invoke( self, input: Union[dict[str, Any], Any], @@ -115,6 +119,7 @@ class PregelProtocol(Protocol): interrupt_after: Optional[Union[All, Sequence[str]]] = None, ) -> Union[dict[str, Any], Any]: ... + @abstractmethod async def ainvoke( self, input: Union[dict[str, Any], Any], diff --git a/libs/langgraph/langgraph/pregel/remote.py b/libs/langgraph/langgraph/pregel/remote.py index 1233cd63f..abe27eb28 100644 --- a/libs/langgraph/langgraph/pregel/remote.py +++ b/libs/langgraph/langgraph/pregel/remote.py @@ -2,6 +2,7 @@ from typing import ( Any, AsyncIterator, Iterator, + Literal, Optional, Sequence, Union, @@ -9,7 +10,7 @@ from typing import ( ) import orjson -from langchain_core.runnables import Runnable, RunnableConfig +from langchain_core.runnables import RunnableConfig from langchain_core.runnables.graph import ( Edge as DrawableEdge, ) @@ -19,7 +20,6 @@ from langchain_core.runnables.graph import ( from langchain_core.runnables.graph import ( Node as DrawableNode, ) -from langchain_core.runnables.schema import StandardStreamEvent, StreamEvent from langgraph_sdk.client import ( LangGraphClient, SyncLangGraphClient, @@ -27,41 +27,98 @@ from langgraph_sdk.client import ( get_sync_client, ) from langgraph_sdk.schema import Checkpoint, ThreadState +from langgraph_sdk.schema import StreamMode as StreamModeSDK from typing_extensions import Self from langgraph.checkpoint.base import CheckpointMetadata +from langgraph.constants import ( + CONF, + CONFIG_KEY_CHECKPOINT_NS, + CONFIG_KEY_STREAM, + INTERRUPT, + NS_SEP, +) +from langgraph.errors import GraphInterrupt from langgraph.pregel.protocol import PregelProtocol from langgraph.pregel.types import All, PregelTask, StateSnapshot, StreamMode -from langgraph.types import Interrupt +from langgraph.types import Interrupt, StreamProtocol from langgraph.utils.config import merge_configs -class RemoteGraph(PregelProtocol, Runnable): +class RemoteException(Exception): + """Exception raised when an error occurs in the remote graph.""" + + pass + + +class RemoteGraph(PregelProtocol): + """The `RemoteGraph` class is a client implementation for calling remote + APIs that implement the LangGraph Server API specification. + + For example, the `RemoteGraph` class can be used to call APIs from deployments + on LangGraph Cloud. + + `RemoteGraph` behaves the same way as a `Graph` and can be used directly as + a node in another `Graph`. + """ + + name: str + def __init__( self, - graph_id: str, - config: Optional[RunnableConfig] = None, + name: str, # graph_id + /, + *, url: Optional[str] = None, api_key: Optional[str] = None, headers: Optional[dict[str, str]] = None, client: Optional[LangGraphClient] = None, sync_client: Optional[SyncLangGraphClient] = None, + config: Optional[RunnableConfig] = None, ): """Specify `url`, `api_key`, and/or `headers` to create default sync and async clients. If `client` or `sync_client` are provided, they will be used instead of the default clients. - See `LangGraphClient` and `SyncLangGraphClient` for details on the default clients. + See `LangGraphClient` and `SyncLangGraphClient` for details on the default clients. At least + one of `url`, `client`, or `sync_client` must be provided. + + Args: + name: The name of the graph. + url: The URL of the remote API. + api_key: The API key to use for authentication. If not provided, it will be read from the environment (`LANGGRAPH_API_KEY`, `LANGSMITH_API_KEY`, or `LANGCHAIN_API_KEY`). + headers: Additional headers to include in the requests. + client: A `LangGraphClient` instance to use instead of creating a default client. + sync_client: A `SyncLangGraphClient` instance to use instead of creating a default client. + config: An optional `RunnableConfig` instance with additional configuration. """ - self.graph_id = graph_id + self.name = name self.config = config - self.client = client or get_client(url=url, api_key=api_key, headers=headers) - self.sync_client = sync_client or get_sync_client( - url=url, api_key=api_key, headers=headers - ) + + if client is None and url is not None: + client = get_client(url=url, api_key=api_key, headers=headers) + self.client = client + + if sync_client is None and url is not None: + sync_client = get_sync_client(url=url, api_key=api_key, headers=headers) + self.sync_client = sync_client + + def _validate_client(self) -> LangGraphClient: + if self.client is None: + raise ValueError( + "Async client is not initialized: please provide `url` or `client` when initializing `RemoteGraph`." + ) + return self.client + + def _validate_sync_client(self) -> SyncLangGraphClient: + if self.sync_client is None: + raise ValueError( + "Sync client is not initialized: please provide `url` or `sync_client` when initializing `RemoteGraph`." + ) + return self.sync_client def copy(self, update: dict[str, Any]) -> Self: attrs = {**self.__dict__, **update} - return self.__class__(**attrs) + return self.__class__(attrs.pop("name"), **attrs) def with_config( self, config: Optional[RunnableConfig] = None, **kwargs: Any @@ -76,10 +133,20 @@ class RemoteGraph(PregelProtocol, Runnable): nodes = {} for node in graph["nodes"]: node_id = str(node["id"]) + node_data = node.get("data", {}) + + # Get node name from node_data if available. If not, use node_id. + node_name = node.get("name") + if node_name is None: + if isinstance(node_data, dict): + node_name = node_data.get("name", node_id) + else: + node_name = node_id + nodes[node_id] = DrawableNode( id=node_id, - name=node.get("name", ""), - data=node.get("data", {}), + name=node_name, + data=node_data, metadata=node.get("metadata"), ) return nodes @@ -90,8 +157,22 @@ class RemoteGraph(PregelProtocol, Runnable): *, xray: Union[int, bool] = False, ) -> DrawableGraph: - graph = self.sync_client.assistants.get_graph( - assistant_id=self.graph_id, + """Get graph by graph name. + + This method calls `GET /assistants/{assistant_id}/graph`. + + Args: + config: This parameter is not used. + xray: Include graph representation of subgraphs. If an integer + value is provided, only subgraphs with a depth less than or + equal to the value will be included. + + Returns: + The graph information for the assistant in JSON format. + """ + sync_client = self._validate_sync_client() + graph = sync_client.assistants.get_graph( + assistant_id=self.name, xray=xray, ) return DrawableGraph( @@ -105,8 +186,22 @@ class RemoteGraph(PregelProtocol, Runnable): *, xray: Union[int, bool] = False, ) -> DrawableGraph: - graph = await self.client.assistants.get_graph( - assistant_id=self.graph_id, + """Get graph by graph name. + + This method calls `GET /assistants/{assistant_id}/graph`. + + Args: + config: This parameter is not used. + xray: Include graph representation of subgraphs. If an integer + value is provided, only subgraphs with a depth less than or + equal to the value will be included. + + Returns: + The graph information for the assistant in JSON format. + """ + client = self._validate_client() + graph = await client.assistants.get_graph( + assistant_id=self.name, xray=xray, ) return DrawableGraph( @@ -114,30 +209,6 @@ class RemoteGraph(PregelProtocol, Runnable): edges=[DrawableEdge(**edge) for edge in graph["edges"]], ) - def get_subgraphs( - self, namespace: Optional[str] = None, recurse: bool = False - ) -> Iterator[tuple[str, "PregelProtocol"]]: - subgraphs = self.sync_client.assistants.get_subgraphs( - assistant_id=self.graph_id, - namespace=namespace, - recurse=recurse, - ) - for namespace, graph_schema in subgraphs.items(): - remote_subgraph = self.copy({"graph_id": graph_schema["graph_id"]}) - yield (namespace, remote_subgraph) - - async def aget_subgraphs( - self, namespace: Optional[str] = None, recurse: bool = False - ) -> AsyncIterator[tuple[str, "PregelProtocol"]]: - subgraphs = await self.client.assistants.get_subgraphs( - assistant_id=self.graph_id, - namespace=namespace, - recurse=recurse, - ) - for namespace, graph_schema in subgraphs.items(): - remote_subgraph = self.copy({"graph_id": graph_schema["graph_id"]}) - yield (namespace, remote_subgraph) - def _create_state_snapshot(self, state: ThreadState) -> StateSnapshot: tasks = [] for task in state["tasks"]: @@ -154,7 +225,7 @@ class RemoteGraph(PregelProtocol, Runnable): interrupts=tuple(interrupts), state=self._create_state_snapshot(task["state"]) if task["state"] - else {"configurable": task["checkpoint"]} + else cast(RunnableConfig, {"configurable": task["checkpoint"]}) if task["checkpoint"] else None, result=task.get("result"), @@ -250,14 +321,33 @@ class RemoteGraph(PregelProtocol, Runnable): if k not in reserved_configurable_keys and not k.startswith("__pregel_") } - return {"configurable": new_configurable} + return { + "tags": config.get("tags") or [], + "metadata": config.get("metadata") or {}, + "configurable": new_configurable, + } def get_state( self, config: RunnableConfig, *, subgraphs: bool = False ) -> StateSnapshot: + """Get the state of a thread. + + This method calls `POST /threads/{thread_id}/state/checkpoint` if a + checkpoint is specified in the config or `GET /threads/{thread_id}/state` + if no checkpoint is specified. + + Args: + config: A `RunnableConfig` that includes `thread_id` in the + `configurable` field. + subgraphs: Include subgraphs in the state. + + Returns: + The latest state of the thread. + """ + sync_client = self._validate_sync_client() merged_config = merge_configs(self.config, config) - state = self.sync_client.threads.get_state( + state = sync_client.threads.get_state( thread_id=merged_config["configurable"]["thread_id"], checkpoint=self._get_checkpoint(merged_config), subgraphs=subgraphs, @@ -267,9 +357,24 @@ class RemoteGraph(PregelProtocol, Runnable): async def aget_state( self, config: RunnableConfig, *, subgraphs: bool = False ) -> StateSnapshot: + """Get the state of a thread. + + This method calls `POST /threads/{thread_id}/state/checkpoint` if a + checkpoint is specified in the config or `GET /threads/{thread_id}/state` + if no checkpoint is specified. + + Args: + config: A `RunnableConfig` that includes `thread_id` in the + `configurable` field. + subgraphs: Include subgraphs in the state. + + Returns: + The latest state of the thread. + """ + client = self._validate_client() merged_config = merge_configs(self.config, config) - state = await self.client.threads.get_state( + state = await client.threads.get_state( thread_id=merged_config["configurable"]["thread_id"], checkpoint=self._get_checkpoint(merged_config), subgraphs=subgraphs, @@ -284,9 +389,24 @@ class RemoteGraph(PregelProtocol, Runnable): before: Optional[RunnableConfig] = None, limit: Optional[int] = None, ) -> Iterator[StateSnapshot]: + """Get the state history of a thread. + + This method calls `POST /threads/{thread_id}/history`. + + Args: + config: A `RunnableConfig` that includes `thread_id` in the + `configurable` field. + filter: Metadata to filter on. + before: A `RunnableConfig` that includes checkpoint metadata. + limit: Max number of states to return. + + Returns: + States of the thread. + """ + sync_client = self._validate_sync_client() merged_config = merge_configs(self.config, config) - states = self.sync_client.threads.get_history( + states = sync_client.threads.get_history( thread_id=merged_config["configurable"]["thread_id"], limit=limit if limit else 10, before=self._get_checkpoint(before), @@ -304,9 +424,24 @@ class RemoteGraph(PregelProtocol, Runnable): before: Optional[RunnableConfig] = None, limit: Optional[int] = None, ) -> AsyncIterator[StateSnapshot]: + """Get the state history of a thread. + + This method calls `POST /threads/{thread_id}/history`. + + Args: + config: A `RunnableConfig` that includes `thread_id` in the + `configurable` field. + filter: Metadata to filter on. + before: A `RunnableConfig` that includes checkpoint metadata. + limit: Max number of states to return. + + Returns: + States of the thread. + """ + client = self._validate_client() merged_config = merge_configs(self.config, config) - states = await self.client.threads.get_history( + states = await client.threads.get_history( thread_id=merged_config["configurable"]["thread_id"], limit=limit if limit else 10, before=self._get_checkpoint(before), @@ -322,9 +457,23 @@ class RemoteGraph(PregelProtocol, Runnable): values: Optional[Union[dict[str, Any], Any]], as_node: Optional[str] = None, ) -> RunnableConfig: + """Update the state of a thread. + + This method calls `POST /threads/{thread_id}/state`. + + Args: + config: A `RunnableConfig` that includes `thread_id` in the + `configurable` field. + values: Values to update to the state. + as_node: Update the state as if this node had just executed. + + Returns: + `RunnableConfig` for the updated thread. + """ + sync_client = self._validate_sync_client() merged_config = merge_configs(self.config, config) - response: dict = self.sync_client.threads.update_state( # type: ignore + response: dict = sync_client.threads.update_state( # type: ignore thread_id=merged_config["configurable"]["thread_id"], values=values, as_node=as_node, @@ -338,9 +487,23 @@ class RemoteGraph(PregelProtocol, Runnable): values: Optional[Union[dict[str, Any], Any]], as_node: Optional[str] = None, ) -> RunnableConfig: + """Update the state of a thread. + + This method calls `POST /threads/{thread_id}/state`. + + Args: + config: A `RunnableConfig` that includes `thread_id` in the + `configurable` field. + values: Values to update to the state. + as_node: Update the state as if this node had just executed. + + Returns: + `RunnableConfig` for the updated thread. + """ + client = self._validate_client() merged_config = merge_configs(self.config, config) - response: dict = await self.client.threads.update_state( # type: ignore + response: dict = await client.threads.update_state( # type: ignore thread_id=merged_config["configurable"]["thread_id"], values=values, as_node=as_node, @@ -348,6 +511,59 @@ class RemoteGraph(PregelProtocol, Runnable): ) return self._get_config(response["checkpoint"]) + def _get_stream_modes( + self, + stream_mode: Optional[Union[StreamMode, list[StreamMode]]], + config: Optional[RunnableConfig], + default: StreamMode = "updates", + ) -> tuple[ + list[StreamModeSDK], list[StreamModeSDK], bool, Optional[StreamProtocol] + ]: + """Return a tuple of the final list of stream modes sent to the + remote graph and a boolean flag indicating if stream mode 'updates' + was present in the original list of stream modes. + + 'updates' mode is added to the list of stream modes so that interrupts + can be detected in the remote graph. + """ + updated_stream_modes: list[StreamModeSDK] = [] + req_single = True + # coerce to list, or add default stream mode + if stream_mode: + if isinstance(stream_mode, str): + updated_stream_modes.append(stream_mode) + else: + req_single = False + updated_stream_modes.extend(stream_mode) + else: + updated_stream_modes.append(default) + requested_stream_modes = updated_stream_modes.copy() + # add any from parent graph + stream: Optional[StreamProtocol] = ( + (config or {}).get(CONF, {}).get(CONFIG_KEY_STREAM) + ) + if stream: + updated_stream_modes.extend(stream.modes) + # map "messages" to "messages-tuple" + if "messages" in updated_stream_modes: + updated_stream_modes.remove("messages") + updated_stream_modes.append("messages-tuple") + + # if requested "messages-tuple", + # map to "messages" in requested_stream_modes + if "messages-tuple" in requested_stream_modes: + requested_stream_modes.remove("messages-tuple") + requested_stream_modes.append("messages") + + # add 'updates' mode if not present + if "updates" not in updated_stream_modes: + updated_stream_modes.append("updates") + + # remove 'events', as it's not supported in Pregel + if "events" in updated_stream_modes: + updated_stream_modes.remove("events") + return (updated_stream_modes, requested_stream_modes, req_single, stream) + def stream( self, input: Union[dict[str, Any], Any], @@ -358,20 +574,78 @@ class RemoteGraph(PregelProtocol, Runnable): interrupt_after: Optional[Union[All, Sequence[str]]] = None, subgraphs: bool = False, ) -> Iterator[Union[dict[str, Any], Any]]: + """Create a run and stream the results. + + This method calls `POST /threads/{thread_id}/runs/stream` if a `thread_id` + is speciffed in the `configurable` field of the config or + `POST /runs/stream` otherwise. + + Args: + input: Input to the graph. + config: A `RunnableConfig` for graph invocation. + stream_mode: Stream mode(s) to use. + interrupt_before: Interrupt the graph before these nodes. + interrupt_after: Interrupt the graph after these nodes. + subgraphs: Stream from subgraphs. + + Yields: + The output of the graph. + """ + sync_client = self._validate_sync_client() merged_config = merge_configs(self.config, config) sanitized_config = self._sanitize_config(merged_config) + stream_modes, requested, req_single, stream = self._get_stream_modes( + stream_mode, config + ) - for chunk in self.sync_client.runs.stream( - thread_id=sanitized_config["configurable"]["thread_id"], - assistant_id=self.graph_id, + for chunk in sync_client.runs.stream( + thread_id=sanitized_config["configurable"].get("thread_id"), + assistant_id=self.name, input=input, config=sanitized_config, - stream_mode=stream_mode, # type: ignore - interrupt_before=interrupt_before, # type: ignore - interrupt_after=interrupt_after, # type: ignore - stream_subgraphs=subgraphs, + stream_mode=stream_modes, + interrupt_before=interrupt_before, + interrupt_after=interrupt_after, + stream_subgraphs=subgraphs or stream is not None, + if_not_exists="create", ): - yield chunk + # split mode and ns + if NS_SEP in chunk.event: + mode, ns_ = chunk.event.split(NS_SEP, 1) + ns = tuple(ns_.split(NS_SEP)) + else: + mode, ns = chunk.event, () + # prepend caller ns (as it is not passed to remote graph) + if caller_ns := (config or {}).get(CONF, {}).get(CONFIG_KEY_CHECKPOINT_NS): + caller_ns = tuple(caller_ns.split(NS_SEP)) + ns = caller_ns + ns + # stream to parent stream + if stream is not None and mode in stream.modes: + stream((ns, mode, chunk.data)) + # raise interrupt or errors + if chunk.event.startswith("updates"): + if isinstance(chunk.data, dict) and INTERRUPT in chunk.data: + raise GraphInterrupt(chunk.data[INTERRUPT]) + elif chunk.event.startswith("error"): + raise RemoteException(chunk.data) + # filter for what was actually requested + if mode not in requested: + continue + # emit chunk + if subgraphs: + if NS_SEP in chunk.event: + mode, ns_ = chunk.event.split(NS_SEP, 1) + ns = tuple(ns_.split(NS_SEP)) + else: + mode, ns = chunk.event, () + if req_single: + yield ns, chunk.data + else: + yield ns, mode, chunk.data + elif req_single: + yield chunk.data + else: + yield chunk async def astream( self, @@ -383,49 +657,94 @@ class RemoteGraph(PregelProtocol, Runnable): interrupt_after: Optional[Union[All, Sequence[str]]] = None, subgraphs: bool = False, ) -> AsyncIterator[Union[dict[str, Any], Any]]: + """Create a run and stream the results. + + This method calls `POST /threads/{thread_id}/runs/stream` if a `thread_id` + is speciffed in the `configurable` field of the config or + `POST /runs/stream` otherwise. + + Args: + input: Input to the graph. + config: A `RunnableConfig` for graph invocation. + stream_mode: Stream mode(s) to use. + interrupt_before: Interrupt the graph before these nodes. + interrupt_after: Interrupt the graph after these nodes. + subgraphs: Stream from subgraphs. + + Yields: + The output of the graph. + """ + client = self._validate_client() merged_config = merge_configs(self.config, config) sanitized_config = self._sanitize_config(merged_config) + stream_modes, requested, req_single, stream = self._get_stream_modes( + stream_mode, config + ) - async for chunk in self.client.runs.stream( - thread_id=sanitized_config["configurable"]["thread_id"], - assistant_id=self.graph_id, + async for chunk in client.runs.stream( + thread_id=sanitized_config["configurable"].get("thread_id"), + assistant_id=self.name, input=input, config=sanitized_config, - stream_mode=stream_mode if stream_mode else "values", # type: ignore - interrupt_before=interrupt_before, # type: ignore - interrupt_after=interrupt_after, # type: ignore - stream_subgraphs=subgraphs, + stream_mode=stream_modes, + interrupt_before=interrupt_before, + interrupt_after=interrupt_after, + stream_subgraphs=subgraphs or stream is not None, + if_not_exists="create", ): - yield chunk + # split mode and ns + if NS_SEP in chunk.event: + mode, ns_ = chunk.event.split(NS_SEP, 1) + ns = tuple(ns_.split(NS_SEP)) + else: + mode, ns = chunk.event, () + # prepend caller ns (as it is not passed to remote graph) + if caller_ns := (config or {}).get(CONF, {}).get(CONFIG_KEY_CHECKPOINT_NS): + caller_ns = tuple(caller_ns.split(NS_SEP)) + ns = caller_ns + ns + # stream to parent stream + if stream is not None and mode in stream.modes: + stream((ns, mode, chunk.data)) + # raise interrupt or errors + if chunk.event.startswith("updates"): + if isinstance(chunk.data, dict) and INTERRUPT in chunk.data: + raise GraphInterrupt(chunk.data[INTERRUPT]) + elif chunk.event.startswith("error"): + raise RemoteException(chunk.data) + # filter for what was actually requested + if mode not in requested: + continue + # emit chunk + if subgraphs: + if NS_SEP in chunk.event: + mode, ns_ = chunk.event.split(NS_SEP, 1) + ns = tuple(ns_.split(NS_SEP)) + else: + mode, ns = chunk.event, () + if req_single: + yield ns, chunk.data + else: + yield ns, mode, chunk.data + elif req_single: + yield chunk.data + else: + yield chunk async def astream_events( self, input: Any, config: Optional[RunnableConfig] = None, + *, + version: Literal["v1", "v2"], + include_names: Optional[Sequence[All]] = None, + include_types: Optional[Sequence[All]] = None, + include_tags: Optional[Sequence[All]] = None, + exclude_names: Optional[Sequence[All]] = None, + exclude_types: Optional[Sequence[All]] = None, + exclude_tags: Optional[Sequence[All]] = None, **kwargs: Any, - ) -> AsyncIterator[StreamEvent]: - merged_config = merge_configs(self.config, config) - sanitized_config = self._sanitize_config(merged_config) - - # manually add 'events' to stream modes list - stream_mode: list[str] = kwargs.get("stream_mode", []) - if "events" not in stream_mode: - stream_mode.append("events") - - async for chunk in self.client.runs.stream( - thread_id=sanitized_config["configurable"]["thread_id"], - assistant_id=self.graph_id, - input=input, - config=sanitized_config, - stream_mode=stream_mode, # type: ignore - interrupt_before=kwargs.get("interrupt_before"), - interrupt_after=kwargs.get("interrupt_after"), - stream_subgraphs=kwargs.get("subgraphs", False), - ): - yield StandardStreamEvent( - event=chunk.event, - data=chunk.data, - ) + ) -> AsyncIterator[dict[str, Any]]: + raise NotImplementedError def invoke( self, @@ -435,17 +754,33 @@ class RemoteGraph(PregelProtocol, Runnable): interrupt_before: Optional[Union[All, Sequence[str]]] = None, interrupt_after: Optional[Union[All, Sequence[str]]] = None, ) -> Union[dict[str, Any], Any]: - merged_config = merge_configs(self.config, config) - sanitized_config = self._sanitize_config(merged_config) + """Create a run, wait until it finishes and return the final state. - return self.sync_client.runs.wait( - thread_id=sanitized_config["configurable"]["thread_id"], - assistant_id=self.graph_id, - input=input, - config=sanitized_config, - interrupt_before=interrupt_before, # type: ignore - interrupt_after=interrupt_after, # type: ignore - ) + This method calls `POST /threads/{thread_id}/runs/wait` if a `thread_id` + is speciffed in the `configurable` field of the config or + `POST /runs/wait` otherwise. + + Args: + input: Input to the graph. + config: A `RunnableConfig` for graph invocation. + interrupt_before: Interrupt the graph before these nodes. + interrupt_after: Interrupt the graph after these nodes. + + Returns: + The output of the graph. + """ + for chunk in self.stream( + input, + config=config, + interrupt_before=interrupt_before, + interrupt_after=interrupt_after, + stream_mode="values", + ): + pass + try: + return chunk + except UnboundLocalError: + return None async def ainvoke( self, @@ -455,14 +790,30 @@ class RemoteGraph(PregelProtocol, Runnable): interrupt_before: Optional[Union[All, Sequence[str]]] = None, interrupt_after: Optional[Union[All, Sequence[str]]] = None, ) -> Union[dict[str, Any], Any]: - merged_config = merge_configs(self.config, config) - sanitized_config = self._sanitize_config(merged_config) + """Create a run, wait until it finishes and return the final state. - return await self.client.runs.wait( - thread_id=sanitized_config["configurable"]["thread_id"], - assistant_id=self.graph_id, - input=input, - config=sanitized_config, - interrupt_before=interrupt_before, # type: ignore - interrupt_after=interrupt_after, # type: ignore - ) + This method calls `POST /threads/{thread_id}/runs/wait` if a `thread_id` + is speciffed in the `configurable` field of the config or + `POST /runs/wait` otherwise. + + Args: + input: Input to the graph. + config: A `RunnableConfig` for graph invocation. + interrupt_before: Interrupt the graph before these nodes. + interrupt_after: Interrupt the graph after these nodes. + + Returns: + The output of the graph. + """ + async for chunk in self.astream( + input, + config=config, + interrupt_before=interrupt_before, + interrupt_after=interrupt_after, + stream_mode="values", + ): + pass + try: + return chunk + except UnboundLocalError: + return None diff --git a/libs/langgraph/langgraph/pregel/retry.py b/libs/langgraph/langgraph/pregel/retry.py index 60057493d..ea9162dc2 100644 --- a/libs/langgraph/langgraph/pregel/retry.py +++ b/libs/langgraph/langgraph/pregel/retry.py @@ -2,9 +2,15 @@ import asyncio import logging import random import time -from typing import Optional, Sequence +from functools import partial +from typing import Any, Callable, Optional, Sequence -from langgraph.constants import CONF, CONFIG_KEY_CHECKPOINT_NS, CONFIG_KEY_RESUMING +from langgraph.constants import ( + CONF, + CONFIG_KEY_CHECKPOINT_NS, + CONFIG_KEY_RESUMING, + CONFIG_KEY_SEND, +) from langgraph.errors import _SEEN_CHECKPOINT_NS, GraphInterrupt from langgraph.types import PregelExecutableTask, RetryPolicy from langgraph.utils.config import patch_configurable @@ -15,12 +21,17 @@ logger = logging.getLogger(__name__) def run_with_retry( task: PregelExecutableTask, retry_policy: Optional[RetryPolicy], + writer: Optional[ + Callable[[PregelExecutableTask, Sequence[tuple[str, Any]]], None] + ] = None, ) -> None: """Run a task with retries.""" retry_policy = task.retry_policy or retry_policy interval = retry_policy.initial_interval if retry_policy else 0 attempts = 0 config = task.config + if writer is not None: + config = patch_configurable(config, {CONFIG_KEY_SEND: partial(writer, task)}) while True: try: # clear any writes from previous attempts @@ -84,12 +95,17 @@ async def arun_with_retry( task: PregelExecutableTask, retry_policy: Optional[RetryPolicy], stream: bool = False, + writer: Optional[ + Callable[[PregelExecutableTask, Sequence[tuple[str, Any]]], None] + ] = None, ) -> None: """Run a task asynchronously with retries.""" retry_policy = task.retry_policy or retry_policy interval = retry_policy.initial_interval if retry_policy else 0 attempts = 0 config = task.config + if writer is not None: + config = patch_configurable(config, {CONFIG_KEY_SEND: partial(writer, task)}) while True: try: # clear any writes from previous attempts diff --git a/libs/langgraph/langgraph/pregel/runner.py b/libs/langgraph/langgraph/pregel/runner.py index b7ad68884..64e5c8d3c 100644 --- a/libs/langgraph/langgraph/pregel/runner.py +++ b/libs/langgraph/langgraph/pregel/runner.py @@ -14,7 +14,15 @@ from typing import ( cast, ) -from langgraph.constants import ERROR, INTERRUPT, NO_WRITES +from langgraph.constants import ( + CONF, + CONFIG_KEY_SEND, + ERROR, + INTERRUPT, + NO_WRITES, + PUSH, + TAG_HIDDEN, +) from langgraph.errors import GraphDelegate, GraphInterrupt from langgraph.pregel.executor import Submit from langgraph.pregel.retry import arun_with_retry, run_with_retry @@ -31,11 +39,17 @@ class PregelRunner: *, submit: Submit, put_writes: Callable[[str, Sequence[tuple[str, Any]]], None], + schedule_task: Callable[ + [PregelExecutableTask, int], Optional[PregelExecutableTask] + ], use_astream: bool = False, + node_finished: Optional[Callable[[str], None]] = None, ) -> None: self.submit = submit self.put_writes = put_writes self.use_astream = use_astream + self.node_finished = node_finished + self.schedule_task = schedule_task def tick( self, @@ -46,27 +60,58 @@ class PregelRunner: retry_policy: Optional[RetryPolicy] = None, get_waiter: Optional[Callable[[], concurrent.futures.Future[None]]] = None, ) -> Iterator[None]: + def writer( + task: PregelExecutableTask, writes: Sequence[tuple[str, Any]] + ) -> None: + prev_length = len(task.writes) + # delegate to the underlying writer + task.config[CONF][CONFIG_KEY_SEND](writes) + for idx, w in enumerate(task.writes): + # find the index for the newly inserted writes + if idx < prev_length: + continue + assert writes[idx - prev_length] is w + # bail if not a PUSH write + if w[0] != PUSH: + continue + # schedule the next task, if the callback returns one + if next_task := self.schedule_task(task, idx): + # if the parent task was retried, + # the next task might already be running + if any( + t == next_task.id for t in futures.values() if t is not None + ): + continue + # schedule the next task + futures[ + self.submit( + run_with_retry, + next_task, + retry_policy, + writer=writer, + __reraise_on_exit__=reraise, + ) + ] = next_task + tasks = tuple(tasks) + futures: dict[concurrent.futures.Future, Optional[PregelExecutableTask]] = {} # give control back to the caller yield # fast path if single task with no timeout and no waiter if len(tasks) == 1 and timeout is None and get_waiter is None: t = tasks[0] try: - run_with_retry(t, retry_policy) + run_with_retry(t, retry_policy, writer=writer) self.commit(t, None) except Exception as exc: self.commit(t, exc) if reraise: raise - return + if not futures: # maybe `t` schuduled another task + return # add waiter task if requested if get_waiter is not None: - futures: dict[concurrent.futures.Future, Optional[PregelExecutableTask]] = { - get_waiter(): None - } - else: - futures = {} + futures[get_waiter()] = None # execute tasks, and wait for one to fail or all to finish. # each task is independent from all other concurrent tasks # yield updates/debug output as each task finishes @@ -77,10 +122,11 @@ class PregelRunner: run_with_retry, t, retry_policy, + writer=writer, __reraise_on_exit__=reraise, ) ] = t - all_futures = futures.copy() + done_futures: set[concurrent.futures.Future] = set() end_time = timeout + time.monotonic() if timeout else None while len(futures) > (1 if get_waiter is not None else 0): done, inflight = concurrent.futures.wait( @@ -97,6 +143,8 @@ class PregelRunner: if inflight and get_waiter is not None: futures[get_waiter()] = None else: + # store for panic check + done_futures.add(fut) # task finished, commit writes self.commit(task, _exception(fut)) else: @@ -108,7 +156,10 @@ class PregelRunner: # give control back to the caller yield # panic on failure or timeout - _panic_or_proceed(all_futures, panic=reraise) + _panic_or_proceed( + done_futures.union(f for f, t in futures.items() if t is not None), + panic=reraise, + ) async def atick( self, @@ -119,28 +170,67 @@ class PregelRunner: retry_policy: Optional[RetryPolicy] = None, get_waiter: Optional[Callable[[], asyncio.Future[None]]] = None, ) -> AsyncIterator[None]: + def writer( + task: PregelExecutableTask, writes: Sequence[tuple[str, Any]] + ) -> None: + prev_length = len(task.writes) + # delegate to the underlying writer + task.config[CONF][CONFIG_KEY_SEND](writes) + for idx, w in enumerate(task.writes): + # find the index for the newly inserted writes + if idx < prev_length: + continue + assert writes[idx - prev_length] is w + # bail if not a PUSH write + if w[0] != PUSH: + continue + # schedule the next task, if the callback returns one + if next_task := self.schedule_task(task, idx): + # if the parent task was retried, + # the next task might already be running + if any( + t == next_task.id for t in futures.values() if t is not None + ): + continue + # schedule the next task + futures[ + cast( + asyncio.Future, + self.submit( + arun_with_retry, + next_task, + retry_policy, + stream=self.use_astream, + writer=writer, + __name__=t.name, + __cancel_on_exit__=True, + __reraise_on_exit__=reraise, + ), + ) + ] = next_task + loop = asyncio.get_event_loop() tasks = tuple(tasks) + futures: dict[asyncio.Future, Optional[PregelExecutableTask]] = {} # give control back to the caller yield # fast path if single task with no waiter and no timeout if len(tasks) == 1 and get_waiter is None and timeout is None: t = tasks[0] try: - await arun_with_retry(t, retry_policy, stream=self.use_astream) + await arun_with_retry( + t, retry_policy, stream=self.use_astream, writer=writer + ) self.commit(t, None) except Exception as exc: self.commit(t, exc) if reraise: raise - return + if not futures: # maybe `t` schuduled another task + return # add waiter task if requested if get_waiter is not None: - futures: dict[asyncio.Future, Optional[PregelExecutableTask]] = { - get_waiter(): None - } - else: - futures = {} + futures[get_waiter()] = None # execute tasks, and wait for one to fail or all to finish. # each task is independent from all other concurrent tasks # yield updates/debug output as each task finishes @@ -154,13 +244,14 @@ class PregelRunner: t, retry_policy, stream=self.use_astream, + writer=writer, __name__=t.name, __cancel_on_exit__=True, __reraise_on_exit__=reraise, ), ) ] = t - all_futures = futures.copy() + done_futures: set[asyncio.Future] = set() end_time = timeout + loop.time() if timeout else None while len(futures) > (1 if get_waiter is not None else 0): done, inflight = await asyncio.wait( @@ -177,6 +268,8 @@ class PregelRunner: if inflight and get_waiter is not None: futures[get_waiter()] = None else: + # store for panic check + done_futures.add(fut) # task finished, commit writes self.commit(task, _exception(fut)) else: @@ -192,7 +285,9 @@ class PregelRunner: fut.cancel() # panic on failure or timeout _panic_or_proceed( - all_futures, timeout_exc_cls=asyncio.TimeoutError, panic=reraise + done_futures.union(f for f, t in futures.items() if t is not None), + timeout_exc_cls=asyncio.TimeoutError, + panic=reraise, ) def commit( @@ -209,6 +304,10 @@ class PregelRunner: # save error to checkpointer self.put_writes(task.id, [(ERROR, exception)]) else: + if self.node_finished and ( + task.config is None or TAG_HIDDEN not in task.config.get("tags", []) + ): + self.node_finished(task.name) if not task.writes: # add no writes marker task.writes.append((NO_WRITES, None)) @@ -244,10 +343,7 @@ def _exception( def _panic_or_proceed( - futs: Union[ - dict[concurrent.futures.Future, Optional[PregelExecutableTask]], - dict[asyncio.Future, Optional[PregelExecutableTask]], - ], + futs: Union[set[concurrent.futures.Future], set[asyncio.Future]], *, timeout_exc_cls: Type[Exception] = TimeoutError, panic: bool = True, @@ -255,10 +351,8 @@ def _panic_or_proceed( """Cancel remaining tasks if any failed, re-raise exception if panic is True.""" done: set[Union[concurrent.futures.Future[Any], asyncio.Future[Any]]] = set() inflight: set[Union[concurrent.futures.Future[Any], asyncio.Future[Any]]] = set() - for fut, val in futs.items(): - if val is None: - continue - elif fut.done(): + for fut in futs: + if fut.done(): done.add(fut) else: inflight.add(fut) diff --git a/libs/langgraph/langgraph/pregel/utils.py b/libs/langgraph/langgraph/pregel/utils.py index 2b09f8f75..66464ef9a 100644 --- a/libs/langgraph/langgraph/pregel/utils.py +++ b/libs/langgraph/langgraph/pregel/utils.py @@ -4,6 +4,7 @@ from langchain_core.runnables import RunnableLambda, RunnableSequence from langchain_core.runnables.utils import get_function_nonlocals from langgraph.checkpoint.base import ChannelVersions +from langgraph.pregel.protocol import PregelProtocol from langgraph.utils.runnable import Runnable, RunnableCallable, RunnableSeq @@ -32,9 +33,9 @@ def find_subgraph_pregel(candidate: Runnable) -> Optional[Runnable]: for c in candidates: if ( - isinstance(c, Pregel) + isinstance(c, PregelProtocol) # subgraphs that disabled checkpointing are not considered - and c.checkpointer is not False + and (not isinstance(c, Pregel) or c.checkpointer is not False) ): return c elif isinstance(c, RunnableSequence) or isinstance(c, RunnableSeq): @@ -47,7 +48,7 @@ def find_subgraph_pregel(candidate: Runnable) -> Optional[Runnable]: nl.__self__ if hasattr(nl, "__self__") else nl for nl in get_function_nonlocals(c.func) ) - if c.afunc is not None: + elif c.afunc is not None: candidates.extend( nl.__self__ if hasattr(nl, "__self__") else nl for nl in get_function_nonlocals(c.afunc) diff --git a/libs/langgraph/langgraph/pregel/write.py b/libs/langgraph/langgraph/pregel/write.py index 9975c7e5b..3af0fe5e9 100644 --- a/libs/langgraph/langgraph/pregel/write.py +++ b/libs/langgraph/langgraph/pregel/write.py @@ -14,7 +14,7 @@ from typing import ( from langchain_core.runnables import Runnable, RunnableConfig from langchain_core.runnables.utils import ConfigurableFieldSpec -from langgraph.constants import CONF, CONFIG_KEY_SEND, TASKS, Send +from langgraph.constants import CONF, CONFIG_KEY_SEND, FF_SEND_V2, PUSH, TASKS, Send from langgraph.errors import InvalidUpdateError from langgraph.utils.runnable import RunnableCallable @@ -112,14 +112,18 @@ class ChannelWrite(RunnableCallable): # validate for w in writes: if isinstance(w, ChannelWriteEntry): - if w.channel == TASKS: + if w.channel in (TASKS, PUSH): raise InvalidUpdateError( "Cannot write to the reserved channel TASKS" ) if w.value is PASSTHROUGH: raise InvalidUpdateError("PASSTHROUGH value must be replaced") # split packets and entries - sends = [(TASKS, packet) for packet in writes if isinstance(packet, Send)] + sends = [ + (PUSH if FF_SEND_V2 else TASKS, packet) + for packet in writes + if isinstance(packet, Send) + ] entries = [write for write in writes if isinstance(write, ChannelWriteEntry)] # process entries into values values = [ diff --git a/libs/langgraph/langgraph/types.py b/libs/langgraph/langgraph/types.py index c29cc1480..104412d8e 100644 --- a/libs/langgraph/langgraph/types.py +++ b/libs/langgraph/langgraph/types.py @@ -1,18 +1,24 @@ +import dataclasses +import sys from collections import deque -from dataclasses import dataclass from typing import ( TYPE_CHECKING, Any, Callable, + Generic, + Hashable, Literal, NamedTuple, Optional, Sequence, Type, + TypeVar, Union, + cast, ) from langchain_core.runnables import Runnable, RunnableConfig +from typing_extensions import Self from langgraph.checkpoint.base import BaseCheckpointSaver, CheckpointMetadata @@ -42,6 +48,11 @@ StreamWriter = Callable[[Any], None] Always injected into nodes if requested as a keyword argument, but it's a no-op when not using stream_mode="custom".""" +if sys.version_info >= (3, 10): + _DC_KWARGS = {"kw_only": True, "slots": True, "frozen": True} +else: + _DC_KWARGS = {"frozen": True} + def default_retry_on(exc: Exception) -> bool: import httpx @@ -99,16 +110,18 @@ class CachePolicy(NamedTuple): pass -@dataclass +@dataclasses.dataclass(**_DC_KWARGS) class Interrupt: value: Any + resumable: bool = False + ns: Optional[Sequence[str]] = None when: Literal["during"] = "during" class PregelTask(NamedTuple): id: str name: str - path: tuple[Union[str, int], ...] + path: tuple[Union[str, int, tuple], ...] error: Optional[Exception] = None interrupts: tuple[Interrupt, ...] = () state: Union[None, RunnableConfig, "StateSnapshot"] = None @@ -125,7 +138,7 @@ class PregelExecutableTask(NamedTuple): retry_policy: Optional[RetryPolicy] cache_policy: Optional[CachePolicy] id: str - path: tuple[Union[str, int], ...] + path: tuple[Union[str, int, tuple], ...] scheduled: bool = False @@ -219,6 +232,27 @@ class Send: ) +N = TypeVar("N", bound=Hashable) + + +@dataclasses.dataclass(**_DC_KWARGS) +class Command(Generic[N]): + """One or more commands to update the graph's state and send messages to nodes.""" + + update: Optional[dict[str, Any]] = None + send: Union[Send, Sequence[Send]] = () + resume: Optional[Union[Any, dict[str, Any]]] = None + + def __repr__(self) -> str: + # get all non-None values + contents = ", ".join( + f"{key}={value!r}" + for key, value in dataclasses.asdict(self).items() + if value + ) + return f"Command({contents})" + + StreamChunk = tuple[tuple[str, ...], str, Any] @@ -227,14 +261,14 @@ class StreamProtocol: modes: set[StreamMode] - __call__: Callable[[StreamChunk], None] + __call__: Callable[[Self, StreamChunk], None] def __init__( self, __call__: Callable[[StreamChunk], None], modes: set[StreamMode], ) -> None: - self.__call__ = __call__ + self.__call__ = cast(Callable[[Self, StreamChunk], None], __call__) self.modes = modes @@ -259,3 +293,28 @@ class LoopProtocol: self.store = store self.step = step self.stop = stop + + +def interrupt(value: Any) -> Any: + from langgraph.constants import ( + CONFIG_KEY_CHECKPOINT_NS, + CONFIG_KEY_RESUME_VALUE, + MISSING, + NS_SEP, + ) + from langgraph.errors import GraphInterrupt + from langgraph.utils.config import get_configurable + + conf = get_configurable() + if (resume := conf.get(CONFIG_KEY_RESUME_VALUE, MISSING)) and resume is not MISSING: + return resume + else: + raise GraphInterrupt( + ( + Interrupt( + value=value, + resumable=True, + ns=cast(str, conf[CONFIG_KEY_CHECKPOINT_NS]).split(NS_SEP), + ), + ) + ) diff --git a/libs/langgraph/langgraph/utils/config.py b/libs/langgraph/langgraph/utils/config.py index fe25b6d9a..adb26dd89 100644 --- a/libs/langgraph/langgraph/utils/config.py +++ b/libs/langgraph/langgraph/utils/config.py @@ -1,3 +1,5 @@ +import asyncio +import sys from collections import ChainMap from typing import Any, Optional, Sequence @@ -278,7 +280,10 @@ def ensure_config(*configs: Optional[RunnableConfig]) -> RunnableConfig: continue for k, v in config.items(): if v is not None and k in CONFIG_KEYS: - empty[k] = v # type: ignore[literal-required] + if k == CONF: + empty[k] = v.copy() # type: ignore[literal-required] + else: + empty[k] = v # type: ignore[literal-required] for k, v in config.items(): if v is not None and k not in CONFIG_KEYS: empty[CONF][k] = v @@ -290,3 +295,18 @@ def ensure_config(*configs: Optional[RunnableConfig]) -> RunnableConfig: ): empty["metadata"][key] = value return empty + + +def get_configurable() -> dict[str, Any]: + if sys.version_info < (3, 11): + try: + if asyncio.current_task(): + raise RuntimeError( + "Python 3.11 or later required to use this in an async context" + ) + except RuntimeError: + pass + if var_config := var_child_runnable_config.get(): + return var_config[CONF] + else: + raise RuntimeError("Called get_configurable outside of a runnable context") diff --git a/libs/langgraph/poetry.lock b/libs/langgraph/poetry.lock index 9d99aa7aa..d337af2bc 100644 --- a/libs/langgraph/poetry.lock +++ b/libs/langgraph/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "aiosqlite" @@ -31,13 +31,13 @@ files = [ [[package]] name = "anyio" -version = "4.4.0" +version = "4.6.2.post1" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, - {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, + {file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"}, + {file = "anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c"}, ] [package.dependencies] @@ -47,9 +47,9 @@ sniffio = ">=1.1" typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] +doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] +trio = ["trio (>=0.26.1)"] [[package]] name = "appnope" @@ -172,32 +172,32 @@ typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} [[package]] name = "attrs" -version = "23.2.0" +version = "24.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, ] [package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "babel" -version = "2.15.0" +version = "2.16.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" files = [ - {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, - {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, + {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, + {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, ] [package.extras] @@ -226,92 +226,106 @@ lxml = ["lxml"] [[package]] name = "bleach" -version = "6.1.0" +version = "6.2.0" description = "An easy safelist-based HTML-sanitizing tool." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "bleach-6.1.0-py3-none-any.whl", hash = "sha256:3225f354cfc436b9789c66c4ee030194bee0568fbf9cbdad3bc8b5c26c5f12b6"}, - {file = "bleach-6.1.0.tar.gz", hash = "sha256:0a31f1837963c41d46bbf1331b8778e1308ea0791db03cc4e7357b97cf42a8fe"}, + {file = "bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e"}, + {file = "bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f"}, ] [package.dependencies] -six = ">=1.9.0" webencodings = "*" [package.extras] -css = ["tinycss2 (>=1.1.0,<1.3)"] +css = ["tinycss2 (>=1.1.0,<1.5)"] [[package]] name = "certifi" -version = "2024.7.4" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] name = "cffi" -version = "1.16.0" +version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" files = [ - {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, - {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, - {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, - {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, - {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, - {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, - {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, - {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, - {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, - {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, - {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, - {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, - {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, - {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, - {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] [package.dependencies] @@ -319,101 +333,116 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "3.3.2" +version = "3.4.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, ] [[package]] @@ -446,63 +475,73 @@ test = ["pytest"] [[package]] name = "coverage" -version = "7.5.3" +version = "7.6.4" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "coverage-7.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45"}, - {file = "coverage-7.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c"}, - {file = "coverage-7.5.3-cp310-cp310-win32.whl", hash = "sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84"}, - {file = "coverage-7.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac"}, - {file = "coverage-7.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974"}, - {file = "coverage-7.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614"}, - {file = "coverage-7.5.3-cp311-cp311-win32.whl", hash = "sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9"}, - {file = "coverage-7.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a"}, - {file = "coverage-7.5.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8"}, - {file = "coverage-7.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84"}, - {file = "coverage-7.5.3-cp312-cp312-win32.whl", hash = "sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08"}, - {file = "coverage-7.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb"}, - {file = "coverage-7.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb"}, - {file = "coverage-7.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0"}, - {file = "coverage-7.5.3-cp38-cp38-win32.whl", hash = "sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485"}, - {file = "coverage-7.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56"}, - {file = "coverage-7.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85"}, - {file = "coverage-7.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd"}, - {file = "coverage-7.5.3-cp39-cp39-win32.whl", hash = "sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d"}, - {file = "coverage-7.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0"}, - {file = "coverage-7.5.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884"}, - {file = "coverage-7.5.3.tar.gz", hash = "sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f"}, + {file = "coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07"}, + {file = "coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a"}, + {file = "coverage-7.6.4-cp310-cp310-win32.whl", hash = "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa"}, + {file = "coverage-7.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172"}, + {file = "coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b"}, + {file = "coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522"}, + {file = "coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf"}, + {file = "coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19"}, + {file = "coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2"}, + {file = "coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5"}, + {file = "coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17"}, + {file = "coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08"}, + {file = "coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9"}, + {file = "coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a"}, + {file = "coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e"}, + {file = "coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963"}, + {file = "coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f"}, + {file = "coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef"}, + {file = "coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e"}, + {file = "coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1"}, + {file = "coverage-7.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3"}, + {file = "coverage-7.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901"}, + {file = "coverage-7.6.4-cp39-cp39-win32.whl", hash = "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09"}, + {file = "coverage-7.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f"}, + {file = "coverage-7.6.4-pp39.pp310-none-any.whl", hash = "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e"}, + {file = "coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73"}, ] [package.dependencies] @@ -513,33 +552,37 @@ toml = ["tomli"] [[package]] name = "debugpy" -version = "1.8.1" +version = "1.8.7" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" files = [ - {file = "debugpy-1.8.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:3bda0f1e943d386cc7a0e71bfa59f4137909e2ed947fb3946c506e113000f741"}, - {file = "debugpy-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dda73bf69ea479c8577a0448f8c707691152e6c4de7f0c4dec5a4bc11dee516e"}, - {file = "debugpy-1.8.1-cp310-cp310-win32.whl", hash = "sha256:3a79c6f62adef994b2dbe9fc2cc9cc3864a23575b6e387339ab739873bea53d0"}, - {file = "debugpy-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:7eb7bd2b56ea3bedb009616d9e2f64aab8fc7000d481faec3cd26c98a964bcdd"}, - {file = "debugpy-1.8.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:016a9fcfc2c6b57f939673c874310d8581d51a0fe0858e7fac4e240c5eb743cb"}, - {file = "debugpy-1.8.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd97ed11a4c7f6d042d320ce03d83b20c3fb40da892f994bc041bbc415d7a099"}, - {file = "debugpy-1.8.1-cp311-cp311-win32.whl", hash = "sha256:0de56aba8249c28a300bdb0672a9b94785074eb82eb672db66c8144fff673146"}, - {file = "debugpy-1.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:1a9fe0829c2b854757b4fd0a338d93bc17249a3bf69ecf765c61d4c522bb92a8"}, - {file = "debugpy-1.8.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3ebb70ba1a6524d19fa7bb122f44b74170c447d5746a503e36adc244a20ac539"}, - {file = "debugpy-1.8.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2e658a9630f27534e63922ebf655a6ab60c370f4d2fc5c02a5b19baf4410ace"}, - {file = "debugpy-1.8.1-cp312-cp312-win32.whl", hash = "sha256:caad2846e21188797a1f17fc09c31b84c7c3c23baf2516fed5b40b378515bbf0"}, - {file = "debugpy-1.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:edcc9f58ec0fd121a25bc950d4578df47428d72e1a0d66c07403b04eb93bcf98"}, - {file = "debugpy-1.8.1-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:7a3afa222f6fd3d9dfecd52729bc2e12c93e22a7491405a0ecbf9e1d32d45b39"}, - {file = "debugpy-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d915a18f0597ef685e88bb35e5d7ab968964b7befefe1aaea1eb5b2640b586c7"}, - {file = "debugpy-1.8.1-cp38-cp38-win32.whl", hash = "sha256:92116039b5500633cc8d44ecc187abe2dfa9b90f7a82bbf81d079fcdd506bae9"}, - {file = "debugpy-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:e38beb7992b5afd9d5244e96ad5fa9135e94993b0c551ceebf3fe1a5d9beb234"}, - {file = "debugpy-1.8.1-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:bfb20cb57486c8e4793d41996652e5a6a885b4d9175dd369045dad59eaacea42"}, - {file = "debugpy-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efd3fdd3f67a7e576dd869c184c5dd71d9aaa36ded271939da352880c012e703"}, - {file = "debugpy-1.8.1-cp39-cp39-win32.whl", hash = "sha256:58911e8521ca0c785ac7a0539f1e77e0ce2df753f786188f382229278b4cdf23"}, - {file = "debugpy-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:6df9aa9599eb05ca179fb0b810282255202a66835c6efb1d112d21ecb830ddd3"}, - {file = "debugpy-1.8.1-py2.py3-none-any.whl", hash = "sha256:28acbe2241222b87e255260c76741e1fbf04fdc3b6d094fcf57b6c6f75ce1242"}, - {file = "debugpy-1.8.1.zip", hash = "sha256:f696d6be15be87aef621917585f9bb94b1dc9e8aced570db1b8a6fc14e8f9b42"}, + {file = "debugpy-1.8.7-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:95fe04a573b8b22896c404365e03f4eda0ce0ba135b7667a1e57bd079793b96b"}, + {file = "debugpy-1.8.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:628a11f4b295ffb4141d8242a9bb52b77ad4a63a2ad19217a93be0f77f2c28c9"}, + {file = "debugpy-1.8.7-cp310-cp310-win32.whl", hash = "sha256:85ce9c1d0eebf622f86cc68618ad64bf66c4fc3197d88f74bb695a416837dd55"}, + {file = "debugpy-1.8.7-cp310-cp310-win_amd64.whl", hash = "sha256:29e1571c276d643757ea126d014abda081eb5ea4c851628b33de0c2b6245b037"}, + {file = "debugpy-1.8.7-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:caf528ff9e7308b74a1749c183d6808ffbedbb9fb6af78b033c28974d9b8831f"}, + {file = "debugpy-1.8.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cba1d078cf2e1e0b8402e6bda528bf8fda7ccd158c3dba6c012b7897747c41a0"}, + {file = "debugpy-1.8.7-cp311-cp311-win32.whl", hash = "sha256:171899588bcd412151e593bd40d9907133a7622cd6ecdbdb75f89d1551df13c2"}, + {file = "debugpy-1.8.7-cp311-cp311-win_amd64.whl", hash = "sha256:6e1c4ffb0c79f66e89dfd97944f335880f0d50ad29525dc792785384923e2211"}, + {file = "debugpy-1.8.7-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:4d27d842311353ede0ad572600c62e4bcd74f458ee01ab0dd3a1a4457e7e3706"}, + {file = "debugpy-1.8.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c1fd62ae0356e194f3e7b7a92acd931f71fe81c4b3be2c17a7b8a4b546ec2"}, + {file = "debugpy-1.8.7-cp312-cp312-win32.whl", hash = "sha256:2f729228430ef191c1e4df72a75ac94e9bf77413ce5f3f900018712c9da0aaca"}, + {file = "debugpy-1.8.7-cp312-cp312-win_amd64.whl", hash = "sha256:45c30aaefb3e1975e8a0258f5bbd26cd40cde9bfe71e9e5a7ac82e79bad64e39"}, + {file = "debugpy-1.8.7-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:d050a1ec7e925f514f0f6594a1e522580317da31fbda1af71d1530d6ea1f2b40"}, + {file = "debugpy-1.8.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f4349a28e3228a42958f8ddaa6333d6f8282d5edaea456070e48609c5983b7"}, + {file = "debugpy-1.8.7-cp313-cp313-win32.whl", hash = "sha256:11ad72eb9ddb436afb8337891a986302e14944f0f755fd94e90d0d71e9100bba"}, + {file = "debugpy-1.8.7-cp313-cp313-win_amd64.whl", hash = "sha256:2efb84d6789352d7950b03d7f866e6d180284bc02c7e12cb37b489b7083d81aa"}, + {file = "debugpy-1.8.7-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:4b908291a1d051ef3331484de8e959ef3e66f12b5e610c203b5b75d2725613a7"}, + {file = "debugpy-1.8.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da8df5b89a41f1fd31503b179d0a84a5fdb752dddd5b5388dbd1ae23cda31ce9"}, + {file = "debugpy-1.8.7-cp38-cp38-win32.whl", hash = "sha256:b12515e04720e9e5c2216cc7086d0edadf25d7ab7e3564ec8b4521cf111b4f8c"}, + {file = "debugpy-1.8.7-cp38-cp38-win_amd64.whl", hash = "sha256:93176e7672551cb5281577cdb62c63aadc87ec036f0c6a486f0ded337c504596"}, + {file = "debugpy-1.8.7-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:90d93e4f2db442f8222dec5ec55ccfc8005821028982f1968ebf551d32b28907"}, + {file = "debugpy-1.8.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6db2a370e2700557a976eaadb16243ec9c91bd46f1b3bb15376d7aaa7632c81"}, + {file = "debugpy-1.8.7-cp39-cp39-win32.whl", hash = "sha256:a6cf2510740e0c0b4a40330640e4b454f928c7b99b0c9dbf48b11efba08a8cda"}, + {file = "debugpy-1.8.7-cp39-cp39-win_amd64.whl", hash = "sha256:6a9d9d6d31846d8e34f52987ee0f1a904c7baa4912bf4843ab39dadf9b8f3e0d"}, + {file = "debugpy-1.8.7-py2.py3-none-any.whl", hash = "sha256:57b00de1c8d2c84a61b90880f7e5b6deaf4c312ecbde3a0e8912f2a56c4ac9ae"}, + {file = "debugpy-1.8.7.zip", hash = "sha256:18b8f731ed3e2e1df8e9cdaa23fb1fc9c24e570cd0081625308ec51c82efe42e"}, ] [[package]] @@ -564,15 +607,76 @@ files = [ {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, ] +[[package]] +name = "duckdb" +version = "1.1.2" +description = "DuckDB in-process database" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "duckdb-1.1.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:91e7f99cf5cab1d26f92cb014429153497d805e79689baa44f4c4585a8cb243f"}, + {file = "duckdb-1.1.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:0107de622fe208142a1108263a03c43956048dcc99be3702d8e5d2aeaf99554c"}, + {file = "duckdb-1.1.2-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:8a09610f780857677725897856f8cdf3cafd8a991f871e6cb8ba88b2dbc8d737"}, + {file = "duckdb-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0f0ddac0482f0f3fece54d720d13819e82ae26c01a939ffa66a87be53f7f665"}, + {file = "duckdb-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84103373e818758dfa361d27781d0f096553843c5ffb9193260a0786c5248270"}, + {file = "duckdb-1.1.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bfdfd23e2bf58014ad0673973bd0ed88cd048dfe8e82420814a71d7d52ef2288"}, + {file = "duckdb-1.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25889e6e29b87047b1dd56385ac08156e4713c59326cc6fff89657d01b2c417b"}, + {file = "duckdb-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:312570fa5277c3079de18388b86c2d87cbe1044838bb152b235c0227581d5d42"}, + {file = "duckdb-1.1.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:568439ea4fce8cb72ec1f767cd510686a9e7e29a011fc7c56d990059a6e94e48"}, + {file = "duckdb-1.1.2-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:74974f2d7210623a5d61b1fb0cb589c6e5ffcbf7dbb757a04c5ba24adcfc8cac"}, + {file = "duckdb-1.1.2-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:e26422a3358c816d764639070945b73eef55d1b4df990989e3492c85ef725c21"}, + {file = "duckdb-1.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87e972bd452eeeab197fe39dcaeecdb7c264b1f75a0ee67e532e235fe45b84df"}, + {file = "duckdb-1.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a6b73e70b73c8df85da383f6e557c03cad5c877868b9a7e41715761e8166c1e"}, + {file = "duckdb-1.1.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:623cb1952466aae5907af84107bcdec25a5ca021a8b6441e961f41edc724f6f2"}, + {file = "duckdb-1.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9fc0b550f96901fa7e76dc70a13f6477ad3e18ef1cb21d414c3a5569de3f27e"}, + {file = "duckdb-1.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:181edb1973bd8f493bcb6ecfa035f1a592dff4667758592f300619012ba251c0"}, + {file = "duckdb-1.1.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:83372b1b411086cac01ab2071122772fa66170b1b41ddbc37527464066083668"}, + {file = "duckdb-1.1.2-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:db37441deddfee6ac35a0c742d2f9e90e4e50b9e76d586a060d122b8fc56dada"}, + {file = "duckdb-1.1.2-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:19142a77e72874aeaa6fda30aeb13612c6de5e8c60fbcc3392cea6ef0694eeaf"}, + {file = "duckdb-1.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:099d99dd48d6e4682a3dd6233ceab73d977ebe1a87afaac54cf77c844e24514a"}, + {file = "duckdb-1.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be86e586ca7af7e807f72479a2b8d0983565360b19dbda4ef8a9d7b3909b8e2c"}, + {file = "duckdb-1.1.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:578e0953e4d8ba8da0cd69fb2930c45f51ce47d213b77d8a4cd461f9c0960b87"}, + {file = "duckdb-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:72b5eb5762c1a5e68849c7143f3b3747a9f15c040e34e41559f233a1569ad16f"}, + {file = "duckdb-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:9b4c6b6a08180261d98330d97355503961a25ca31cd9ef296e0681f7895b4a2c"}, + {file = "duckdb-1.1.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:695dcbc561374b126e86659709feadf883c9969ed718e94713edd4ba15d16619"}, + {file = "duckdb-1.1.2-cp313-cp313-macosx_12_0_universal2.whl", hash = "sha256:ada29be1e889f486c6cf1f6dffd15463e748faf361f33996f2e862779edc24a9"}, + {file = "duckdb-1.1.2-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:6ca722738fa9eb6218619740631de29acfdd132de6f6a6350fee5e291c2f6117"}, + {file = "duckdb-1.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c796d33f1e5a0c8c570d22da0c0b1db8578687e427029e1ce2c8ce3f9fffa6a3"}, + {file = "duckdb-1.1.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5c0996988a70dd3bc8111d9b9aeab7e38ed1999a52607c5f1b528e362b4dd1c"}, + {file = "duckdb-1.1.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c37b039f6d6fed14d89450f5ccf54922b3304192d7412e12d6cc8d9e757f7a2"}, + {file = "duckdb-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8c766b87f675c76d6d17103bf6fb9fb1a9e2fcb3d9b25c28bbc634bde31223e"}, + {file = "duckdb-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:e3e6300b7ccaf64b609f4f0780a6e1d25ab8cf34cceed46e62c35b6c4c5cb63b"}, + {file = "duckdb-1.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a01fae9604a54ecbc26e7503c522311f15afbd2870e6d8f6fbef4545dfae550"}, + {file = "duckdb-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:492b1d86a696428bd3f14dc1c7c3230e2dbca8978f288be64b04a26e0e00fad5"}, + {file = "duckdb-1.1.2-cp37-cp37m-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bba58459ad897a78c4e478a097626fc266459a40338cecc68a49a8d5dc72fb7"}, + {file = "duckdb-1.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:d395a3bf510bf24686821eec15802624797dcb33e8f14f8a7cc8e17d909474af"}, + {file = "duckdb-1.1.2-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:fd800f75728727fe699ed1eb22b636867cf48c9dd105ee88b977e20c89df4509"}, + {file = "duckdb-1.1.2-cp38-cp38-macosx_12_0_universal2.whl", hash = "sha256:d8caaf43909e49537e26df51d80d075ae2b25a610d28ed8bd31d6ccebeaf3c65"}, + {file = "duckdb-1.1.2-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:564166811c68d9c7f9911eb707ad32ec9c2507b98336d894fbe658b85bf1c697"}, + {file = "duckdb-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19386aa09f0d6f97634ba2972096d1c80d880176dfb0e949eadc91c98262a663"}, + {file = "duckdb-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9e8387bcc9a591ad14011ddfec0d408d1d9b1889c6c9b495a04c7016a24b9b3"}, + {file = "duckdb-1.1.2-cp38-cp38-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8c5ff4970403ed3ff0ac71fe0ce1e6be3199df9d542afc84c424b444ba4ffe8"}, + {file = "duckdb-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:9283dcca87c3260eb631a99d738fa72b8545ed45b475bc72ad254f7310e14284"}, + {file = "duckdb-1.1.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:f87edaf20001530e63a4f7bda13b55dc3152d7171226915f2bf34e0813c8759e"}, + {file = "duckdb-1.1.2-cp39-cp39-macosx_12_0_universal2.whl", hash = "sha256:efec169b3fe0b821e3207ba3e445f227d42dd62b4440ff79c37fa168a4fc5a71"}, + {file = "duckdb-1.1.2-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:89164a2d29d56605a95ee5032aa415dd487028c4fd3e06d971497840e74c56e7"}, + {file = "duckdb-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6858e10c60ff7e70e61d3dd53d2545c8b2609942e45fd6de38cd0dee52932de3"}, + {file = "duckdb-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca967c5a57b1d0cb0fd5e539ab24110e5a59dcbedd365bb2dc80533d6e44a8d"}, + {file = "duckdb-1.1.2-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ce949f1d7999aa6a046eb64067eee41d4c5c2872ba4fa408c9947742d0c7231"}, + {file = "duckdb-1.1.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ba6d1f918e6ca47a368a0c32806016405cb9beb2c245806b0ca998f569d2bdf"}, + {file = "duckdb-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:7111fd3e7b334a7be383313ce29918b7c643e4f6ef44d6d63c3ab3fa6716c114"}, + {file = "duckdb-1.1.2.tar.gz", hash = "sha256:c8232861dc8ec6daa29067056d5a0e5789919f2ab22ab792787616d7cd52f02a"}, +] + [[package]] name = "exceptiongroup" -version = "1.2.1" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, - {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] @@ -594,13 +698,13 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "executing" -version = "2.0.1" +version = "2.1.0" description = "Get the currently executing AST node of a frame, and other information" optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" files = [ - {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, - {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, + {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, + {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, ] [package.extras] @@ -644,13 +748,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.5" +version = "1.0.6" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, - {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, + {file = "httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f"}, + {file = "httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f"}, ] [package.dependencies] @@ -661,7 +765,7 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.26.0)"] +trio = ["trio (>=0.22.0,<1.0)"] [[package]] name = "httpx" @@ -700,33 +804,40 @@ files = [ [[package]] name = "idna" -version = "3.7" +version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + [[package]] name = "importlib-metadata" -version = "7.1.0" +version = "8.5.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, - {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, ] [package.dependencies] -zipp = ">=0.5" +zipp = ">=3.20" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] [[package]] name = "iniconfig" @@ -741,13 +852,13 @@ files = [ [[package]] name = "ipykernel" -version = "6.29.4" +version = "6.29.5" description = "IPython Kernel for Jupyter" optional = false python-versions = ">=3.8" files = [ - {file = "ipykernel-6.29.4-py3-none-any.whl", hash = "sha256:1181e653d95c6808039c509ef8e67c4126b3b3af7781496c7cbfb5ed938a27da"}, - {file = "ipykernel-6.29.4.tar.gz", hash = "sha256:3d44070060f9475ac2092b760123fadf105d2e2493c24848b6691a7c4f42af5c"}, + {file = "ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5"}, + {file = "ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215"}, ] [package.dependencies] @@ -811,21 +922,21 @@ test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pa [[package]] name = "ipywidgets" -version = "8.1.3" +version = "8.1.5" description = "Jupyter interactive widgets" optional = false python-versions = ">=3.7" files = [ - {file = "ipywidgets-8.1.3-py3-none-any.whl", hash = "sha256:efafd18f7a142248f7cb0ba890a68b96abd4d6e88ddbda483c9130d12667eaf2"}, - {file = "ipywidgets-8.1.3.tar.gz", hash = "sha256:f5f9eeaae082b1823ce9eac2575272952f40d748893972956dc09700a6392d9c"}, + {file = "ipywidgets-8.1.5-py3-none-any.whl", hash = "sha256:3290f526f87ae6e77655555baba4f36681c555b8bdbbff430b70e52c34c86245"}, + {file = "ipywidgets-8.1.5.tar.gz", hash = "sha256:870e43b1a35656a80c18c9503bbf2d16802db1cb487eec6fab27d683381dde17"}, ] [package.dependencies] comm = ">=0.1.3" ipython = ">=6.1.0" -jupyterlab-widgets = ">=3.0.11,<3.1.0" +jupyterlab-widgets = ">=3.0.12,<3.1.0" traitlets = ">=4.3.1" -widgetsnbextension = ">=4.0.11,<4.1.0" +widgetsnbextension = ">=4.0.12,<4.1.0" [package.extras] test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] @@ -918,13 +1029,13 @@ files = [ [[package]] name = "jsonschema" -version = "4.22.0" +version = "4.23.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema-4.22.0-py3-none-any.whl", hash = "sha256:ff4cfd6b1367a40e7bc6411caec72effadd3db0bbe5017de188f2d6108335802"}, - {file = "jsonschema-4.22.0.tar.gz", hash = "sha256:5b22d434a45935119af990552c862e5d6d564e8f6601206b305a61fdf661a2b7"}, + {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, + {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, ] [package.dependencies] @@ -939,21 +1050,21 @@ rfc3339-validator = {version = "*", optional = true, markers = "extra == \"forma rfc3986-validator = {version = ">0.1.0", optional = true, markers = "extra == \"format-nongpl\""} rpds-py = ">=0.7.1" uri-template = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} -webcolors = {version = ">=1.11", optional = true, markers = "extra == \"format-nongpl\""} +webcolors = {version = ">=24.6.0", optional = true, markers = "extra == \"format-nongpl\""} [package.extras] format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] -format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"] [[package]] name = "jsonschema-specifications" -version = "2023.12.1" +version = "2024.10.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, - {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, + {file = "jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf"}, + {file = "jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272"}, ] [package.dependencies] @@ -961,33 +1072,32 @@ referencing = ">=0.31.0" [[package]] name = "jupyter" -version = "1.0.0" +version = "1.1.1" description = "Jupyter metapackage. Install all the Jupyter components in one go." optional = false python-versions = "*" files = [ - {file = "jupyter-1.0.0-py2.py3-none-any.whl", hash = "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78"}, - {file = "jupyter-1.0.0.tar.gz", hash = "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"}, - {file = "jupyter-1.0.0.zip", hash = "sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7"}, + {file = "jupyter-1.1.1-py2.py3-none-any.whl", hash = "sha256:7a59533c22af65439b24bbe60373a4e95af8f16ac65a6c00820ad378e3f7cc83"}, + {file = "jupyter-1.1.1.tar.gz", hash = "sha256:d55467bceabdea49d7e3624af7e33d59c37fff53ed3a350e1ac957bed731de7a"}, ] [package.dependencies] ipykernel = "*" ipywidgets = "*" jupyter-console = "*" +jupyterlab = "*" nbconvert = "*" notebook = "*" -qtconsole = "*" [[package]] name = "jupyter-client" -version = "8.6.2" +version = "8.6.3" description = "Jupyter protocol implementation and client libraries" optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_client-8.6.2-py3-none-any.whl", hash = "sha256:50cbc5c66fd1b8f65ecb66bc490ab73217993632809b6e505687de18e9dea39f"}, - {file = "jupyter_client-8.6.2.tar.gz", hash = "sha256:2bda14d55ee5ba58552a8c53ae43d215ad9868853489213f37da060ced54d8df"}, + {file = "jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f"}, + {file = "jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419"}, ] [package.dependencies] @@ -1088,13 +1198,13 @@ jupyter-server = ">=1.1.2" [[package]] name = "jupyter-server" -version = "2.14.1" +version = "2.14.2" description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_server-2.14.1-py3-none-any.whl", hash = "sha256:16f7177c3a4ea8fe37784e2d31271981a812f0b2874af17339031dc3510cc2a5"}, - {file = "jupyter_server-2.14.1.tar.gz", hash = "sha256:12558d158ec7a0653bf96cc272bc7ad79e0127d503b982ed144399346694f726"}, + {file = "jupyter_server-2.14.2-py3-none-any.whl", hash = "sha256:47ff506127c2f7851a17bf4713434208fc490955d0e8632e95014a9a9afbeefd"}, + {file = "jupyter_server-2.14.2.tar.gz", hash = "sha256:66095021aa9638ced276c248b1d81862e4c50f292d575920bbe960de1c56b12b"}, ] [package.dependencies] @@ -1143,13 +1253,13 @@ test = ["jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-jupyter[server] (> [[package]] name = "jupyterlab" -version = "4.2.5" +version = "4.3.0" description = "JupyterLab computational environment" optional = false python-versions = ">=3.8" files = [ - {file = "jupyterlab-4.2.5-py3-none-any.whl", hash = "sha256:73b6e0775d41a9fee7ee756c80f58a6bed4040869ccc21411dc559818874d321"}, - {file = "jupyterlab-4.2.5.tar.gz", hash = "sha256:ae7f3a1b8cb88b4f55009ce79fa7c06f99d70cd63601ee4aa91815d054f46f75"}, + {file = "jupyterlab-4.3.0-py3-none-any.whl", hash = "sha256:f67e1095ad61ae04349024f0b40345062ab108a0c6998d9810fec6a3c1a70cd5"}, + {file = "jupyterlab-4.3.0.tar.gz", hash = "sha256:7c6835cbf8df0af0ec8a39332e85ff11693fb9a468205343b4fc0bfbc74817e5"}, ] [package.dependencies] @@ -1170,9 +1280,9 @@ tornado = ">=6.2.0" traitlets = "*" [package.extras] -dev = ["build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.3.5)"] -docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-jupyter", "sphinx (>=1.8,<7.3.0)", "sphinx-copybutton"] -docs-screenshots = ["altair (==5.3.0)", "ipython (==8.16.1)", "ipywidgets (==8.1.2)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.1.post2)", "matplotlib (==3.8.3)", "nbconvert (>=7.0.0)", "pandas (==2.2.1)", "scipy (==1.12.0)", "vega-datasets (==0.9.0)"] +dev = ["build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.6.9)"] +docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-jupyter", "sphinx (>=1.8,<8.1.0)", "sphinx-copybutton"] +docs-screenshots = ["altair (==5.4.1)", "ipython (==8.16.1)", "ipywidgets (==8.1.5)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.2.post3)", "matplotlib (==3.9.2)", "nbconvert (>=7.0.0)", "pandas (==2.2.3)", "scipy (==1.14.1)", "vega-datasets (==0.9.0)"] test = ["coverage", "pytest (>=7.0)", "pytest-check-links (>=0.7)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter (>=0.5.3)", "pytest-timeout", "pytest-tornasync", "requests", "requests-cache", "virtualenv"] upgrade-extension = ["copier (>=9,<10)", "jinja2-time (<0.3)", "pydantic (<3.0)", "pyyaml-include (<3.0)", "tomli-w (<2.0)"] @@ -1189,13 +1299,13 @@ files = [ [[package]] name = "jupyterlab-server" -version = "2.27.2" +version = "2.27.3" description = "A set of server components for JupyterLab and JupyterLab like applications." optional = false python-versions = ">=3.8" files = [ - {file = "jupyterlab_server-2.27.2-py3-none-any.whl", hash = "sha256:54aa2d64fd86383b5438d9f0c032f043c4d8c0264b8af9f60bd061157466ea43"}, - {file = "jupyterlab_server-2.27.2.tar.gz", hash = "sha256:15cbb349dc45e954e09bacf81b9f9bcb10815ff660fb2034ecd7417db3a7ea27"}, + {file = "jupyterlab_server-2.27.3-py3-none-any.whl", hash = "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4"}, + {file = "jupyterlab_server-2.27.3.tar.gz", hash = "sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4"}, ] [package.dependencies] @@ -1215,24 +1325,24 @@ test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-v [[package]] name = "jupyterlab-widgets" -version = "3.0.11" +version = "3.0.13" description = "Jupyter interactive widgets for JupyterLab" optional = false python-versions = ">=3.7" files = [ - {file = "jupyterlab_widgets-3.0.11-py3-none-any.whl", hash = "sha256:78287fd86d20744ace330a61625024cf5521e1c012a352ddc0a3cdc2348becd0"}, - {file = "jupyterlab_widgets-3.0.11.tar.gz", hash = "sha256:dd5ac679593c969af29c9bed054c24f26842baa51352114736756bc035deee27"}, + {file = "jupyterlab_widgets-3.0.13-py3-none-any.whl", hash = "sha256:e3cda2c233ce144192f1e29914ad522b2f4c40e77214b0cc97377ca3d323db54"}, + {file = "jupyterlab_widgets-3.0.13.tar.gz", hash = "sha256:a2966d385328c1942b683a8cd96b89b8dd82c8b8f81dda902bb2bc06d46f5bed"}, ] [[package]] name = "langchain-core" -version = "0.3.8" +version = "0.3.15" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "langchain_core-0.3.8-py3-none-any.whl", hash = "sha256:07015f7b1d9f52eefe05130e8cafe4dcbdbbf72a8411c9edafe38422e4d11b5c"}, - {file = "langchain_core-0.3.8.tar.gz", hash = "sha256:7485904f7082f1df880d5ae470a488161616132f30d99f556a1877901fffd1cb"}, + {file = "langchain_core-0.3.15-py3-none-any.whl", hash = "sha256:3d4ca6dbb8ed396a6ee061063832a2451b0ce8c345570f7b086ffa7288e4fa29"}, + {file = "langchain_core-0.3.15.tar.gz", hash = "sha256:b1a29787a4ffb7ec2103b4e97d435287201da7809b369740dd1e32f176325aba"}, ] [package.dependencies] @@ -1244,12 +1354,12 @@ pydantic = [ {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, ] PyYAML = ">=5.3" -tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0" +tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" typing-extensions = ">=4.7" [[package]] name = "langgraph-checkpoint" -version = "2.0.1" +version = "2.0.4" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = "^3.9.0,<4.0" @@ -1265,8 +1375,25 @@ type = "directory" url = "../checkpoint" [[package]] -name = "langgraph-checkpoint-postgres" +name = "langgraph-checkpoint-duckdb" version = "2.0.1" +description = "Library with a DuckDB implementation of LangGraph checkpoint saver." +optional = false +python-versions = "^3.9.0,<4.0" +files = [] +develop = true + +[package.dependencies] +duckdb = ">=1.1.2" +langgraph-checkpoint = "^2.0.2" + +[package.source] +type = "directory" +url = "../checkpoint-duckdb" + +[[package]] +name = "langgraph-checkpoint-postgres" +version = "2.0.2" description = "Library with a Postgres implementation of LangGraph checkpoint saver." optional = false python-versions = "^3.9.0,<4.0" @@ -1274,7 +1401,7 @@ files = [] develop = true [package.dependencies] -langgraph-checkpoint = "^2.0.0" +langgraph-checkpoint = "^2.0.2" orjson = ">=3.10.1" psycopg = "^3.0.0" psycopg-pool = "^3.0.0" @@ -1285,7 +1412,7 @@ url = "../checkpoint-postgres" [[package]] name = "langgraph-checkpoint-sqlite" -version = "2.0.0" +version = "2.0.1" description = "Library with a SQLite implementation of LangGraph checkpoint saver." optional = false python-versions = "^3.9.0" @@ -1294,7 +1421,7 @@ develop = true [package.dependencies] aiosqlite = "^0.20.0" -langgraph-checkpoint = "^2.0.0" +langgraph-checkpoint = "^2.0.2" [package.source] type = "directory" @@ -1302,7 +1429,7 @@ url = "../checkpoint-sqlite" [[package]] name = "langgraph-sdk" -version = "0.1.32" +version = "0.1.36" description = "SDK for interacting with LangGraph API" optional = false python-versions = "^3.9.0,<4.0" @@ -1320,13 +1447,13 @@ url = "../sdk-py" [[package]] name = "langsmith" -version = "0.1.129" +version = "0.1.138" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = "<4.0,>=3.8.1" files = [ - {file = "langsmith-0.1.129-py3-none-any.whl", hash = "sha256:31393fbbb17d6be5b99b9b22d530450094fab23c6c37281a6a6efb2143d05347"}, - {file = "langsmith-0.1.129.tar.gz", hash = "sha256:6c3ba66471bef41b9f87da247cc0b493268b3f54656f73648a256a205261b6a0"}, + {file = "langsmith-0.1.138-py3-none-any.whl", hash = "sha256:5c2bd5c11c75f7b3d06a0f06b115186e7326ca969fd26d66ffc65a0669012aee"}, + {file = "langsmith-0.1.138.tar.gz", hash = "sha256:1ecf613bb52f6bf17f1510e24ad8b70d4b0259bc9d3dbfd69b648c66d4644f0b"}, ] [package.dependencies] @@ -1337,74 +1464,76 @@ pydantic = [ {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, ] requests = ">=2,<3" +requests-toolbelt = ">=1.0.0,<2.0.0" [[package]] name = "markupsafe" -version = "2.1.5" +version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] [[package]] @@ -1507,38 +1636,43 @@ files = [ [[package]] name = "mypy" -version = "1.11.2" +version = "1.13.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, - {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, - {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, - {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, - {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, - {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, - {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, - {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, - {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, - {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, - {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, - {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, - {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, - {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, - {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, - {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, - {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, - {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, - {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, - {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, - {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, - {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, - {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, - {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, - {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, - {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, - {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, + {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, + {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, + {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, + {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, + {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, + {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, + {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, + {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, + {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, + {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, + {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, + {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, + {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, + {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, + {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, + {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, + {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, + {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, + {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, + {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, ] [package.dependencies] @@ -1548,6 +1682,7 @@ typing-extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] reports = ["lxml"] @@ -1657,26 +1792,26 @@ files = [ [[package]] name = "notebook" -version = "7.2.2" +version = "7.0.7" description = "Jupyter Notebook - A web-based notebook environment for interactive computing" optional = false python-versions = ">=3.8" files = [ - {file = "notebook-7.2.2-py3-none-any.whl", hash = "sha256:c89264081f671bc02eec0ed470a627ed791b9156cad9285226b31611d3e9fe1c"}, - {file = "notebook-7.2.2.tar.gz", hash = "sha256:2ef07d4220421623ad3fe88118d687bc0450055570cdd160814a59cf3a1c516e"}, + {file = "notebook-7.0.7-py3-none-any.whl", hash = "sha256:289b606d7e173f75a18beb1406ef411b43f97f7a9c55ba03efa3622905a62346"}, + {file = "notebook-7.0.7.tar.gz", hash = "sha256:3bcff00c17b3ac142ef5f436d50637d936b274cfa0b41f6ac0175363de9b4e09"}, ] [package.dependencies] jupyter-server = ">=2.4.0,<3" -jupyterlab = ">=4.2.0,<4.3" -jupyterlab-server = ">=2.27.1,<3" +jupyterlab = ">=4.0.2,<5" +jupyterlab-server = ">=2.22.1,<3" notebook-shim = ">=0.2,<0.3" tornado = ">=6.2.0" [package.extras] dev = ["hatch", "pre-commit"] docs = ["myst-parser", "nbsphinx", "pydata-sphinx-theme", "sphinx (>=1.3.6)", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] -test = ["importlib-resources (>=5.0)", "ipykernel", "jupyter-server[test] (>=2.4.0,<3)", "jupyterlab-server[test] (>=2.27.1,<3)", "nbval", "pytest (>=7.0)", "pytest-console-scripts", "pytest-timeout", "pytest-tornasync", "requests"] +test = ["importlib-resources (>=5.0)", "ipykernel", "jupyter-server[test] (>=2.4.0,<3)", "jupyterlab-server[test] (>=2.22.1,<3)", "nbval", "pytest (>=7.0)", "pytest-console-scripts", "pytest-timeout", "pytest-tornasync", "requests"] [[package]] name = "notebook-shim" @@ -1697,68 +1832,69 @@ test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync" [[package]] name = "orjson" -version = "3.10.7" +version = "3.10.10" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.10.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:74f4544f5a6405b90da8ea724d15ac9c36da4d72a738c64685003337401f5c12"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34a566f22c28222b08875b18b0dfbf8a947e69df21a9ed5c51a6bf91cfb944ac"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf6ba8ebc8ef5792e2337fb0419f8009729335bb400ece005606336b7fd7bab7"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac7cf6222b29fbda9e3a472b41e6a5538b48f2c8f99261eecd60aafbdb60690c"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de817e2f5fc75a9e7dd350c4b0f54617b280e26d1631811a43e7e968fa71e3e9"}, - {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:348bdd16b32556cf8d7257b17cf2bdb7ab7976af4af41ebe79f9796c218f7e91"}, - {file = "orjson-3.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:479fd0844ddc3ca77e0fd99644c7fe2de8e8be1efcd57705b5c92e5186e8a250"}, - {file = "orjson-3.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fdf5197a21dd660cf19dfd2a3ce79574588f8f5e2dbf21bda9ee2d2b46924d84"}, - {file = "orjson-3.10.7-cp310-none-win32.whl", hash = "sha256:d374d36726746c81a49f3ff8daa2898dccab6596864ebe43d50733275c629175"}, - {file = "orjson-3.10.7-cp310-none-win_amd64.whl", hash = "sha256:cb61938aec8b0ffb6eef484d480188a1777e67b05d58e41b435c74b9d84e0b9c"}, - {file = "orjson-3.10.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7db8539039698ddfb9a524b4dd19508256107568cdad24f3682d5773e60504a2"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:480f455222cb7a1dea35c57a67578848537d2602b46c464472c995297117fa09"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8a9c9b168b3a19e37fe2778c0003359f07822c90fdff8f98d9d2a91b3144d8e0"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8de062de550f63185e4c1c54151bdddfc5625e37daf0aa1e75d2a1293e3b7d9a"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b0dd04483499d1de9c8f6203f8975caf17a6000b9c0c54630cef02e44ee624e"}, - {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b58d3795dafa334fc8fd46f7c5dc013e6ad06fd5b9a4cc98cb1456e7d3558bd6"}, - {file = "orjson-3.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33cfb96c24034a878d83d1a9415799a73dc77480e6c40417e5dda0710d559ee6"}, - {file = "orjson-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e724cebe1fadc2b23c6f7415bad5ee6239e00a69f30ee423f319c6af70e2a5c0"}, - {file = "orjson-3.10.7-cp311-none-win32.whl", hash = "sha256:82763b46053727a7168d29c772ed5c870fdae2f61aa8a25994c7984a19b1021f"}, - {file = "orjson-3.10.7-cp311-none-win_amd64.whl", hash = "sha256:eb8d384a24778abf29afb8e41d68fdd9a156cf6e5390c04cc07bbc24b89e98b5"}, - {file = "orjson-3.10.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44a96f2d4c3af51bfac6bc4ef7b182aa33f2f054fd7f34cc0ee9a320d051d41f"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ac14cd57df0572453543f8f2575e2d01ae9e790c21f57627803f5e79b0d3c3"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bdbb61dcc365dd9be94e8f7df91975edc9364d6a78c8f7adb69c1cdff318ec93"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b48b3db6bb6e0a08fa8c83b47bc169623f801e5cc4f24442ab2b6617da3b5313"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23820a1563a1d386414fef15c249040042b8e5d07b40ab3fe3efbfbbcbcb8864"}, - {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0c6a008e91d10a2564edbb6ee5069a9e66df3fbe11c9a005cb411f441fd2c09"}, - {file = "orjson-3.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d352ee8ac1926d6193f602cbe36b1643bbd1bbcb25e3c1a657a4390f3000c9a5"}, - {file = "orjson-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d2d9f990623f15c0ae7ac608103c33dfe1486d2ed974ac3f40b693bad1a22a7b"}, - {file = "orjson-3.10.7-cp312-none-win32.whl", hash = "sha256:7c4c17f8157bd520cdb7195f75ddbd31671997cbe10aee559c2d613592e7d7eb"}, - {file = "orjson-3.10.7-cp312-none-win_amd64.whl", hash = "sha256:1d9c0e733e02ada3ed6098a10a8ee0052dd55774de3d9110d29868d24b17faa1"}, - {file = "orjson-3.10.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:77d325ed866876c0fa6492598ec01fe30e803272a6e8b10e992288b009cbe149"}, - {file = "orjson-3.10.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ea2c232deedcb605e853ae1db2cc94f7390ac776743b699b50b071b02bea6fe"}, - {file = "orjson-3.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3dcfbede6737fdbef3ce9c37af3fb6142e8e1ebc10336daa05872bfb1d87839c"}, - {file = "orjson-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11748c135f281203f4ee695b7f80bb1358a82a63905f9f0b794769483ea854ad"}, - {file = "orjson-3.10.7-cp313-none-win32.whl", hash = "sha256:a7e19150d215c7a13f39eb787d84db274298d3f83d85463e61d277bbd7f401d2"}, - {file = "orjson-3.10.7-cp313-none-win_amd64.whl", hash = "sha256:eef44224729e9525d5261cc8d28d6b11cafc90e6bd0be2157bde69a52ec83024"}, - {file = "orjson-3.10.7-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6ea2b2258eff652c82652d5e0f02bd5e0463a6a52abb78e49ac288827aaa1469"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:430ee4d85841e1483d487e7b81401785a5dfd69db5de01314538f31f8fbf7ee1"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4b6146e439af4c2472c56f8540d799a67a81226e11992008cb47e1267a9b3225"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:084e537806b458911137f76097e53ce7bf5806dda33ddf6aaa66a028f8d43a23"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4829cf2195838e3f93b70fd3b4292156fc5e097aac3739859ac0dcc722b27ac0"}, - {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1193b2416cbad1a769f868b1749535d5da47626ac29445803dae7cc64b3f5c98"}, - {file = "orjson-3.10.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4e6c3da13e5a57e4b3dca2de059f243ebec705857522f188f0180ae88badd354"}, - {file = "orjson-3.10.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c31008598424dfbe52ce8c5b47e0752dca918a4fdc4a2a32004efd9fab41d866"}, - {file = "orjson-3.10.7-cp38-none-win32.whl", hash = "sha256:7122a99831f9e7fe977dc45784d3b2edc821c172d545e6420c375e5a935f5a1c"}, - {file = "orjson-3.10.7-cp38-none-win_amd64.whl", hash = "sha256:a763bc0e58504cc803739e7df040685816145a6f3c8a589787084b54ebc9f16e"}, - {file = "orjson-3.10.7-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e76be12658a6fa376fcd331b1ea4e58f5a06fd0220653450f0d415b8fd0fbe20"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed350d6978d28b92939bfeb1a0570c523f6170efc3f0a0ef1f1df287cd4f4960"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:144888c76f8520e39bfa121b31fd637e18d4cc2f115727865fdf9fa325b10412"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09b2d92fd95ad2402188cf51573acde57eb269eddabaa60f69ea0d733e789fe9"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b24a579123fa884f3a3caadaed7b75eb5715ee2b17ab5c66ac97d29b18fe57f"}, - {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591bcfe7512353bd609875ab38050efe3d55e18934e2f18950c108334b4ff"}, - {file = "orjson-3.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f4db56635b58cd1a200b0a23744ff44206ee6aa428185e2b6c4a65b3197abdcd"}, - {file = "orjson-3.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0fa5886854673222618638c6df7718ea7fe2f3f2384c452c9ccedc70b4a510a5"}, - {file = "orjson-3.10.7-cp39-none-win32.whl", hash = "sha256:8272527d08450ab16eb405f47e0f4ef0e5ff5981c3d82afe0efd25dcbef2bcd2"}, - {file = "orjson-3.10.7-cp39-none-win_amd64.whl", hash = "sha256:974683d4618c0c7dbf4f69c95a979734bf183d0658611760017f6e70a145af58"}, - {file = "orjson-3.10.7.tar.gz", hash = "sha256:75ef0640403f945f3a1f9f6400686560dbfb0fb5b16589ad62cd477043c4eee3"}, + {file = "orjson-3.10.10-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b788a579b113acf1c57e0a68e558be71d5d09aa67f62ca1f68e01117e550a998"}, + {file = "orjson-3.10.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:804b18e2b88022c8905bb79bd2cbe59c0cd014b9328f43da8d3b28441995cda4"}, + {file = "orjson-3.10.10-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9972572a1d042ec9ee421b6da69f7cc823da5962237563fa548ab17f152f0b9b"}, + {file = "orjson-3.10.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc6993ab1c2ae7dd0711161e303f1db69062955ac2668181bfdf2dd410e65258"}, + {file = "orjson-3.10.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d78e4cacced5781b01d9bc0f0cd8b70b906a0e109825cb41c1b03f9c41e4ce86"}, + {file = "orjson-3.10.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6eb2598df518281ba0cbc30d24c5b06124ccf7e19169e883c14e0831217a0bc"}, + {file = "orjson-3.10.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23776265c5215ec532de6238a52707048401a568f0fa0d938008e92a147fe2c7"}, + {file = "orjson-3.10.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8cc2a654c08755cef90b468ff17c102e2def0edd62898b2486767204a7f5cc9c"}, + {file = "orjson-3.10.10-cp310-none-win32.whl", hash = "sha256:081b3fc6a86d72efeb67c13d0ea7c030017bd95f9868b1e329a376edc456153b"}, + {file = "orjson-3.10.10-cp310-none-win_amd64.whl", hash = "sha256:ff38c5fb749347768a603be1fb8a31856458af839f31f064c5aa74aca5be9efe"}, + {file = "orjson-3.10.10-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:879e99486c0fbb256266c7c6a67ff84f46035e4f8749ac6317cc83dacd7f993a"}, + {file = "orjson-3.10.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:019481fa9ea5ff13b5d5d95e6fd5ab25ded0810c80b150c2c7b1cc8660b662a7"}, + {file = "orjson-3.10.10-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0dd57eff09894938b4c86d4b871a479260f9e156fa7f12f8cad4b39ea8028bb5"}, + {file = "orjson-3.10.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dbde6d70cd95ab4d11ea8ac5e738e30764e510fc54d777336eec09bb93b8576c"}, + {file = "orjson-3.10.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2625cb37b8fb42e2147404e5ff7ef08712099197a9cd38895006d7053e69d6"}, + {file = "orjson-3.10.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbf3c20c6a7db69df58672a0d5815647ecf78c8e62a4d9bd284e8621c1fe5ccb"}, + {file = "orjson-3.10.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:75c38f5647e02d423807d252ce4528bf6a95bd776af999cb1fb48867ed01d1f6"}, + {file = "orjson-3.10.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:23458d31fa50ec18e0ec4b0b4343730928296b11111df5f547c75913714116b2"}, + {file = "orjson-3.10.10-cp311-none-win32.whl", hash = "sha256:2787cd9dedc591c989f3facd7e3e86508eafdc9536a26ec277699c0aa63c685b"}, + {file = "orjson-3.10.10-cp311-none-win_amd64.whl", hash = "sha256:6514449d2c202a75183f807bc755167713297c69f1db57a89a1ef4a0170ee269"}, + {file = "orjson-3.10.10-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8564f48f3620861f5ef1e080ce7cd122ee89d7d6dacf25fcae675ff63b4d6e05"}, + {file = "orjson-3.10.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5bf161a32b479034098c5b81f2608f09167ad2fa1c06abd4e527ea6bf4837a9"}, + {file = "orjson-3.10.10-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68b65c93617bcafa7f04b74ae8bc2cc214bd5cb45168a953256ff83015c6747d"}, + {file = "orjson-3.10.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8e28406f97fc2ea0c6150f4c1b6e8261453318930b334abc419214c82314f85"}, + {file = "orjson-3.10.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4d0d9fe174cc7a5bdce2e6c378bcdb4c49b2bf522a8f996aa586020e1b96cee"}, + {file = "orjson-3.10.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3be81c42f1242cbed03cbb3973501fcaa2675a0af638f8be494eaf37143d999"}, + {file = "orjson-3.10.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:65f9886d3bae65be026219c0a5f32dbbe91a9e6272f56d092ab22561ad0ea33b"}, + {file = "orjson-3.10.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:730ed5350147db7beb23ddaf072f490329e90a1d059711d364b49fe352ec987b"}, + {file = "orjson-3.10.10-cp312-none-win32.whl", hash = "sha256:a8f4bf5f1c85bea2170800020d53a8877812892697f9c2de73d576c9307a8a5f"}, + {file = "orjson-3.10.10-cp312-none-win_amd64.whl", hash = "sha256:384cd13579a1b4cd689d218e329f459eb9ddc504fa48c5a83ef4889db7fd7a4f"}, + {file = "orjson-3.10.10-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44bffae68c291f94ff5a9b4149fe9d1bdd4cd0ff0fb575bcea8351d48db629a1"}, + {file = "orjson-3.10.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e27b4c6437315df3024f0835887127dac2a0a3ff643500ec27088d2588fa5ae1"}, + {file = "orjson-3.10.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca84df16d6b49325a4084fd8b2fe2229cb415e15c46c529f868c3387bb1339d"}, + {file = "orjson-3.10.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c14ce70e8f39bd71f9f80423801b5d10bf93d1dceffdecd04df0f64d2c69bc01"}, + {file = "orjson-3.10.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:24ac62336da9bda1bd93c0491eff0613003b48d3cb5d01470842e7b52a40d5b4"}, + {file = "orjson-3.10.10-cp313-none-win32.whl", hash = "sha256:eb0a42831372ec2b05acc9ee45af77bcaccbd91257345f93780a8e654efc75db"}, + {file = "orjson-3.10.10-cp313-none-win_amd64.whl", hash = "sha256:f0c4f37f8bf3f1075c6cc8dd8a9f843689a4b618628f8812d0a71e6968b95ffd"}, + {file = "orjson-3.10.10-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:829700cc18503efc0cf502d630f612884258020d98a317679cd2054af0259568"}, + {file = "orjson-3.10.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0ceb5e0e8c4f010ac787d29ae6299846935044686509e2f0f06ed441c1ca949"}, + {file = "orjson-3.10.10-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0c25908eb86968613216f3db4d3003f1c45d78eb9046b71056ca327ff92bdbd4"}, + {file = "orjson-3.10.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:218cb0bc03340144b6328a9ff78f0932e642199ac184dd74b01ad691f42f93ff"}, + {file = "orjson-3.10.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2277ec2cea3775640dc81ab5195bb5b2ada2fe0ea6eee4677474edc75ea6785"}, + {file = "orjson-3.10.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:848ea3b55ab5ccc9d7bbd420d69432628b691fba3ca8ae3148c35156cbd282aa"}, + {file = "orjson-3.10.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e3e67b537ac0c835b25b5f7d40d83816abd2d3f4c0b0866ee981a045287a54f3"}, + {file = "orjson-3.10.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:7948cfb909353fce2135dcdbe4521a5e7e1159484e0bb024c1722f272488f2b8"}, + {file = "orjson-3.10.10-cp38-none-win32.whl", hash = "sha256:78bee66a988f1a333dc0b6257503d63553b1957889c17b2c4ed72385cd1b96ae"}, + {file = "orjson-3.10.10-cp38-none-win_amd64.whl", hash = "sha256:f1d647ca8d62afeb774340a343c7fc023efacfd3a39f70c798991063f0c681dd"}, + {file = "orjson-3.10.10-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5a059afddbaa6dd733b5a2d76a90dbc8af790b993b1b5cb97a1176ca713b5df8"}, + {file = "orjson-3.10.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f9b5c59f7e2a1a410f971c5ebc68f1995822837cd10905ee255f96074537ee6"}, + {file = "orjson-3.10.10-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d5ef198bafdef4aa9d49a4165ba53ffdc0a9e1c7b6f76178572ab33118afea25"}, + {file = "orjson-3.10.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf29ce0bb5d3320824ec3d1508652421000ba466abd63bdd52c64bcce9eb1fa"}, + {file = "orjson-3.10.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dddd5516bcc93e723d029c1633ae79c4417477b4f57dad9bfeeb6bc0315e654a"}, + {file = "orjson-3.10.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12f2003695b10817f0fa8b8fca982ed7f5761dcb0d93cff4f2f9f6709903fd7"}, + {file = "orjson-3.10.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:672f9874a8a8fb9bb1b771331d31ba27f57702c8106cdbadad8bda5d10bc1019"}, + {file = "orjson-3.10.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1dcbb0ca5fafb2b378b2c74419480ab2486326974826bbf6588f4dc62137570a"}, + {file = "orjson-3.10.10-cp39-none-win32.whl", hash = "sha256:d9bbd3a4b92256875cb058c3381b782649b9a3c68a4aa9a2fff020c2f9cfc1be"}, + {file = "orjson-3.10.10-cp39-none-win_amd64.whl", hash = "sha256:766f21487a53aee8524b97ca9582d5c6541b03ab6210fbaf10142ae2f3ced2aa"}, + {file = "orjson-3.10.10.tar.gz", hash = "sha256:37949383c4df7b4337ce82ee35b6d7471e55195efa7dcb45ab8226ceadb0fe3b"}, ] [[package]] @@ -1825,19 +1961,19 @@ ptyprocess = ">=0.5" [[package]] name = "platformdirs" -version = "4.2.2" +version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] [[package]] name = "pluggy" @@ -1856,13 +1992,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "prometheus-client" -version = "0.20.0" +version = "0.21.0" description = "Python client for the Prometheus monitoring system." optional = false python-versions = ">=3.8" files = [ - {file = "prometheus_client-0.20.0-py3-none-any.whl", hash = "sha256:cde524a85bce83ca359cc837f28b8c0db5cac7aa653a588fd7e84ba061c329e7"}, - {file = "prometheus_client-0.20.0.tar.gz", hash = "sha256:287629d00b147a32dcb2be0b9df905da599b2d82f80377083ec8463309a4bb89"}, + {file = "prometheus_client-0.21.0-py3-none-any.whl", hash = "sha256:4fa6b4dd0ac16d58bb587c04b1caae65b8c5043e85f778f42f5f632f6af2e166"}, + {file = "prometheus_client-0.21.0.tar.gz", hash = "sha256:96c83c606b71ff2b0a433c98889d275f51ffec6c5e267de37c7a2b5c9aa9233e"}, ] [package.extras] @@ -1870,13 +2006,13 @@ twisted = ["twisted"] [[package]] name = "prompt-toolkit" -version = "3.0.47" +version = "3.0.48" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, - {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, + {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, + {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, ] [package.dependencies] @@ -1884,31 +2020,33 @@ wcwidth = "*" [[package]] name = "psutil" -version = "5.9.8" +version = "6.1.0" description = "Cross-platform lib for process and system monitoring in Python." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, - {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, - {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, - {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"}, - {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"}, - {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"}, - {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"}, - {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"}, - {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"}, - {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"}, - {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"}, - {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"}, - {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"}, - {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"}, - {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, - {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, + {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, + {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, + {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:000d1d1ebd634b4efb383f4034437384e44a6d455260aaee2eca1e9c1b55f047"}, + {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5cd2bcdc75b452ba2e10f0e8ecc0b57b827dd5d7aaffbc6821b2a9a242823a76"}, + {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:045f00a43c737f960d273a83973b2511430d61f283a44c96bf13a6e829ba8fdc"}, + {file = "psutil-6.1.0-cp27-none-win32.whl", hash = "sha256:9118f27452b70bb1d9ab3198c1f626c2499384935aaf55388211ad982611407e"}, + {file = "psutil-6.1.0-cp27-none-win_amd64.whl", hash = "sha256:a8506f6119cff7015678e2bce904a4da21025cc70ad283a53b099e7620061d85"}, + {file = "psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e2dcd475ce8b80522e51d923d10c7871e45f20918e027ab682f94f1c6351688"}, + {file = "psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0895b8414afafc526712c498bd9de2b063deaac4021a3b3c34566283464aff8e"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcbfce5d89f1d1f2546a2090f4fcf87c7f669d1d90aacb7d7582addece9fb38"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:498c6979f9c6637ebc3a73b3f87f9eb1ec24e1ce53a7c5173b8508981614a90b"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d905186d647b16755a800e7263d43df08b790d709d575105d419f8b6ef65423a"}, + {file = "psutil-6.1.0-cp36-cp36m-win32.whl", hash = "sha256:6d3fbbc8d23fcdcb500d2c9f94e07b1342df8ed71b948a2649b5cb060a7c94ca"}, + {file = "psutil-6.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1209036fbd0421afde505a4879dee3b2fd7b1e14fee81c0069807adcbbcca747"}, + {file = "psutil-6.1.0-cp37-abi3-win32.whl", hash = "sha256:1ad45a1f5d0b608253b11508f80940985d1d0c8f6111b5cb637533a0e6ddc13e"}, + {file = "psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"}, + {file = "psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"}, ] [package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +dev = ["black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "wheel"] +test = ["pytest", "pytest-xdist", "setuptools"] [[package]] name = "psycopg" @@ -2034,13 +2172,13 @@ files = [ [[package]] name = "pure-eval" -version = "0.2.2" +version = "0.2.3" description = "Safely evaluate AST nodes without side effects" optional = false python-versions = "*" files = [ - {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, - {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, ] [package.extras] @@ -2213,13 +2351,13 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyperf" -version = "2.7.0" +version = "2.8.0" description = "Python module to run and analyze benchmarks" optional = false python-versions = ">=3.7" files = [ - {file = "pyperf-2.7.0-py3-none-any.whl", hash = "sha256:dce63053b916b73d8736a77404309328f938851b5c2c5e8493cde910ce37e362"}, - {file = "pyperf-2.7.0.tar.gz", hash = "sha256:4201c6601032f374e9c900c6d2544a2f5891abedc1a96eec0e7b2338a6247589"}, + {file = "pyperf-2.8.0-py3-none-any.whl", hash = "sha256:1a775b5a09882f18bf876430ef78e07646f773f50774546f5f6a8b34d60e3968"}, + {file = "pyperf-2.8.0.tar.gz", hash = "sha256:b30a20465819daf102b6543b512f6799a5a879ff2a123981e6cd732d0e6a7a79"}, ] [package.dependencies] @@ -2230,13 +2368,13 @@ dev = ["importlib-metadata", "tox"] [[package]] name = "pytest" -version = "8.3.2" +version = "8.3.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, - {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, ] [package.dependencies] @@ -2316,13 +2454,13 @@ pytest = "*" [[package]] name = "pytest-watcher" -version = "0.4.2" +version = "0.4.3" description = "Automatically rerun your tests on file modifications" optional = false python-versions = "<4.0.0,>=3.7.0" files = [ - {file = "pytest_watcher-0.4.2-py3-none-any.whl", hash = "sha256:a43949ba67dd8d7e1fd0de5eea44a999081f0aec9f93b4e744264b4c6a3d9bbe"}, - {file = "pytest_watcher-0.4.2.tar.gz", hash = "sha256:7b292f025ca19617cd7567c228c6187b5087f2da9e4d2cf6e144e5764a0471b0"}, + {file = "pytest_watcher-0.4.3-py3-none-any.whl", hash = "sha256:d59b1e1396f33a65ea4949b713d6884637755d641646960056a90b267c3460f9"}, + {file = "pytest_watcher-0.4.3.tar.gz", hash = "sha256:0cb0e4661648c8c0ff2b2d25efa5a8e421784b9e4c60fcecbf9b7c30b2d731b3"}, ] [package.dependencies] @@ -2391,244 +2529,229 @@ files = [ [[package]] name = "pywin32" -version = "306" +version = "308" description = "Python for Window Extensions" optional = false python-versions = "*" files = [ - {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, - {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, - {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, - {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, - {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, - {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, - {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, - {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, - {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, - {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, - {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, - {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, - {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, - {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, + {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, + {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, + {file = "pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c"}, + {file = "pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a"}, + {file = "pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b"}, + {file = "pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6"}, + {file = "pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897"}, + {file = "pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47"}, + {file = "pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091"}, + {file = "pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed"}, + {file = "pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4"}, + {file = "pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd"}, + {file = "pywin32-308-cp37-cp37m-win32.whl", hash = "sha256:1f696ab352a2ddd63bd07430080dd598e6369152ea13a25ebcdd2f503a38f1ff"}, + {file = "pywin32-308-cp37-cp37m-win_amd64.whl", hash = "sha256:13dcb914ed4347019fbec6697a01a0aec61019c1046c2b905410d197856326a6"}, + {file = "pywin32-308-cp38-cp38-win32.whl", hash = "sha256:5794e764ebcabf4ff08c555b31bd348c9025929371763b2183172ff4708152f0"}, + {file = "pywin32-308-cp38-cp38-win_amd64.whl", hash = "sha256:3b92622e29d651c6b783e368ba7d6722b1634b8e70bd376fd7610fe1992e19de"}, + {file = "pywin32-308-cp39-cp39-win32.whl", hash = "sha256:7873ca4dc60ab3287919881a7d4f88baee4a6e639aa6962de25a98ba6b193341"}, + {file = "pywin32-308-cp39-cp39-win_amd64.whl", hash = "sha256:71b3322d949b4cc20776436a9c9ba0eeedcbc9c650daa536df63f0ff111bb920"}, ] [[package]] name = "pywinpty" -version = "2.0.13" +version = "2.0.14" description = "Pseudo terminal support for Windows from Python." optional = false python-versions = ">=3.8" files = [ - {file = "pywinpty-2.0.13-cp310-none-win_amd64.whl", hash = "sha256:697bff211fb5a6508fee2dc6ff174ce03f34a9a233df9d8b5fe9c8ce4d5eaf56"}, - {file = "pywinpty-2.0.13-cp311-none-win_amd64.whl", hash = "sha256:b96fb14698db1284db84ca38c79f15b4cfdc3172065b5137383910567591fa99"}, - {file = "pywinpty-2.0.13-cp312-none-win_amd64.whl", hash = "sha256:2fd876b82ca750bb1333236ce98488c1be96b08f4f7647cfdf4129dfad83c2d4"}, - {file = "pywinpty-2.0.13-cp38-none-win_amd64.whl", hash = "sha256:61d420c2116c0212808d31625611b51caf621fe67f8a6377e2e8b617ea1c1f7d"}, - {file = "pywinpty-2.0.13-cp39-none-win_amd64.whl", hash = "sha256:71cb613a9ee24174730ac7ae439fd179ca34ccb8c5349e8d7b72ab5dea2c6f4b"}, - {file = "pywinpty-2.0.13.tar.gz", hash = "sha256:c34e32351a3313ddd0d7da23d27f835c860d32fe4ac814d372a3ea9594f41dde"}, + {file = "pywinpty-2.0.14-cp310-none-win_amd64.whl", hash = "sha256:0b149c2918c7974f575ba79f5a4aad58bd859a52fa9eb1296cc22aa412aa411f"}, + {file = "pywinpty-2.0.14-cp311-none-win_amd64.whl", hash = "sha256:cf2a43ac7065b3e0dc8510f8c1f13a75fb8fde805efa3b8cff7599a1ef497bc7"}, + {file = "pywinpty-2.0.14-cp312-none-win_amd64.whl", hash = "sha256:55dad362ef3e9408ade68fd173e4f9032b3ce08f68cfe7eacb2c263ea1179737"}, + {file = "pywinpty-2.0.14-cp313-none-win_amd64.whl", hash = "sha256:074fb988a56ec79ca90ed03a896d40707131897cefb8f76f926e3834227f2819"}, + {file = "pywinpty-2.0.14-cp39-none-win_amd64.whl", hash = "sha256:5725fd56f73c0531ec218663bd8c8ff5acc43c78962fab28564871b5fce053fd"}, + {file = "pywinpty-2.0.14.tar.gz", hash = "sha256:18bd9529e4a5daf2d9719aa17788ba6013e594ae94c5a0c27e83df3278b0660e"}, ] [[package]] name = "pyyaml" -version = "6.0.1" +version = "6.0.2" description = "YAML parser and emitter for Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] name = "pyzmq" -version = "26.0.3" +version = "26.2.0" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.7" files = [ - {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:44dd6fc3034f1eaa72ece33588867df9e006a7303725a12d64c3dff92330f625"}, - {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:acb704195a71ac5ea5ecf2811c9ee19ecdc62b91878528302dd0be1b9451cc90"}, - {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbb9c997932473a27afa93954bb77a9f9b786b4ccf718d903f35da3232317de"}, - {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bcb34f869d431799c3ee7d516554797f7760cb2198ecaa89c3f176f72d062be"}, - {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ece17ec5f20d7d9b442e5174ae9f020365d01ba7c112205a4d59cf19dc38ee"}, - {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ba6e5e6588e49139a0979d03a7deb9c734bde647b9a8808f26acf9c547cab1bf"}, - {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3bf8b000a4e2967e6dfdd8656cd0757d18c7e5ce3d16339e550bd462f4857e59"}, - {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2136f64fbb86451dbbf70223635a468272dd20075f988a102bf8a3f194a411dc"}, - {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e8918973fbd34e7814f59143c5f600ecd38b8038161239fd1a3d33d5817a38b8"}, - {file = "pyzmq-26.0.3-cp310-cp310-win32.whl", hash = "sha256:0aaf982e68a7ac284377d051c742610220fd06d330dcd4c4dbb4cdd77c22a537"}, - {file = "pyzmq-26.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:f1a9b7d00fdf60b4039f4455afd031fe85ee8305b019334b72dcf73c567edc47"}, - {file = "pyzmq-26.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:80b12f25d805a919d53efc0a5ad7c0c0326f13b4eae981a5d7b7cc343318ebb7"}, - {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:a72a84570f84c374b4c287183debc776dc319d3e8ce6b6a0041ce2e400de3f32"}, - {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ca684ee649b55fd8f378127ac8462fb6c85f251c2fb027eb3c887e8ee347bcd"}, - {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e222562dc0f38571c8b1ffdae9d7adb866363134299264a1958d077800b193b7"}, - {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f17cde1db0754c35a91ac00b22b25c11da6eec5746431d6e5092f0cd31a3fea9"}, - {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b7c0c0b3244bb2275abe255d4a30c050d541c6cb18b870975553f1fb6f37527"}, - {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ac97a21de3712afe6a6c071abfad40a6224fd14fa6ff0ff8d0c6e6cd4e2f807a"}, - {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:88b88282e55fa39dd556d7fc04160bcf39dea015f78e0cecec8ff4f06c1fc2b5"}, - {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:72b67f966b57dbd18dcc7efbc1c7fc9f5f983e572db1877081f075004614fcdd"}, - {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4b6cecbbf3b7380f3b61de3a7b93cb721125dc125c854c14ddc91225ba52f83"}, - {file = "pyzmq-26.0.3-cp311-cp311-win32.whl", hash = "sha256:eed56b6a39216d31ff8cd2f1d048b5bf1700e4b32a01b14379c3b6dde9ce3aa3"}, - {file = "pyzmq-26.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:3191d312c73e3cfd0f0afdf51df8405aafeb0bad71e7ed8f68b24b63c4f36500"}, - {file = "pyzmq-26.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:b6907da3017ef55139cf0e417c5123a84c7332520e73a6902ff1f79046cd3b94"}, - {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:068ca17214038ae986d68f4a7021f97e187ed278ab6dccb79f837d765a54d753"}, - {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7821d44fe07335bea256b9f1f41474a642ca55fa671dfd9f00af8d68a920c2d4"}, - {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb438a26d87c123bb318e5f2b3d86a36060b01f22fbdffd8cf247d52f7c9a2b"}, - {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69ea9d6d9baa25a4dc9cef5e2b77b8537827b122214f210dd925132e34ae9b12"}, - {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7daa3e1369355766dea11f1d8ef829905c3b9da886ea3152788dc25ee6079e02"}, - {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6ca7a9a06b52d0e38ccf6bca1aeff7be178917893f3883f37b75589d42c4ac20"}, - {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1b7d0e124948daa4d9686d421ef5087c0516bc6179fdcf8828b8444f8e461a77"}, - {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e746524418b70f38550f2190eeee834db8850088c834d4c8406fbb9bc1ae10b2"}, - {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6b3146f9ae6af82c47a5282ac8803523d381b3b21caeae0327ed2f7ecb718798"}, - {file = "pyzmq-26.0.3-cp312-cp312-win32.whl", hash = "sha256:2b291d1230845871c00c8462c50565a9cd6026fe1228e77ca934470bb7d70ea0"}, - {file = "pyzmq-26.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:926838a535c2c1ea21c903f909a9a54e675c2126728c21381a94ddf37c3cbddf"}, - {file = "pyzmq-26.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:5bf6c237f8c681dfb91b17f8435b2735951f0d1fad10cc5dfd96db110243370b"}, - {file = "pyzmq-26.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c0991f5a96a8e620f7691e61178cd8f457b49e17b7d9cfa2067e2a0a89fc1d5"}, - {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dbf012d8fcb9f2cf0643b65df3b355fdd74fc0035d70bb5c845e9e30a3a4654b"}, - {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:01fbfbeb8249a68d257f601deb50c70c929dc2dfe683b754659569e502fbd3aa"}, - {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c8eb19abe87029c18f226d42b8a2c9efdd139d08f8bf6e085dd9075446db450"}, - {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5344b896e79800af86ad643408ca9aa303a017f6ebff8cee5a3163c1e9aec987"}, - {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:204e0f176fd1d067671157d049466869b3ae1fc51e354708b0dc41cf94e23a3a"}, - {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a42db008d58530efa3b881eeee4991146de0b790e095f7ae43ba5cc612decbc5"}, - {file = "pyzmq-26.0.3-cp37-cp37m-win32.whl", hash = "sha256:8d7a498671ca87e32b54cb47c82a92b40130a26c5197d392720a1bce1b3c77cf"}, - {file = "pyzmq-26.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:3b4032a96410bdc760061b14ed6a33613ffb7f702181ba999df5d16fb96ba16a"}, - {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2cc4e280098c1b192c42a849de8de2c8e0f3a84086a76ec5b07bfee29bda7d18"}, - {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5bde86a2ed3ce587fa2b207424ce15b9a83a9fa14422dcc1c5356a13aed3df9d"}, - {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:34106f68e20e6ff253c9f596ea50397dbd8699828d55e8fa18bd4323d8d966e6"}, - {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ebbbd0e728af5db9b04e56389e2299a57ea8b9dd15c9759153ee2455b32be6ad"}, - {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6b1d1c631e5940cac5a0b22c5379c86e8df6a4ec277c7a856b714021ab6cfad"}, - {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e891ce81edd463b3b4c3b885c5603c00141151dd9c6936d98a680c8c72fe5c67"}, - {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9b273ecfbc590a1b98f014ae41e5cf723932f3b53ba9367cfb676f838038b32c"}, - {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b32bff85fb02a75ea0b68f21e2412255b5731f3f389ed9aecc13a6752f58ac97"}, - {file = "pyzmq-26.0.3-cp38-cp38-win32.whl", hash = "sha256:f6c21c00478a7bea93caaaef9e7629145d4153b15a8653e8bb4609d4bc70dbfc"}, - {file = "pyzmq-26.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:3401613148d93ef0fd9aabdbddb212de3db7a4475367f49f590c837355343972"}, - {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:2ed8357f4c6e0daa4f3baf31832df8a33334e0fe5b020a61bc8b345a3db7a606"}, - {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1c8f2a2ca45292084c75bb6d3a25545cff0ed931ed228d3a1810ae3758f975f"}, - {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b63731993cdddcc8e087c64e9cf003f909262b359110070183d7f3025d1c56b5"}, - {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b3cd31f859b662ac5d7f4226ec7d8bd60384fa037fc02aee6ff0b53ba29a3ba8"}, - {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115f8359402fa527cf47708d6f8a0f8234f0e9ca0cab7c18c9c189c194dbf620"}, - {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:715bdf952b9533ba13dfcf1f431a8f49e63cecc31d91d007bc1deb914f47d0e4"}, - {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e1258c639e00bf5e8a522fec6c3eaa3e30cf1c23a2f21a586be7e04d50c9acab"}, - {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:15c59e780be8f30a60816a9adab900c12a58d79c1ac742b4a8df044ab2a6d920"}, - {file = "pyzmq-26.0.3-cp39-cp39-win32.whl", hash = "sha256:d0cdde3c78d8ab5b46595054e5def32a755fc028685add5ddc7403e9f6de9879"}, - {file = "pyzmq-26.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:ce828058d482ef860746bf532822842e0ff484e27f540ef5c813d516dd8896d2"}, - {file = "pyzmq-26.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:788f15721c64109cf720791714dc14afd0f449d63f3a5487724f024345067381"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c18645ef6294d99b256806e34653e86236eb266278c8ec8112622b61db255de"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e6bc96ebe49604df3ec2c6389cc3876cabe475e6bfc84ced1bf4e630662cb35"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:971e8990c5cc4ddcff26e149398fc7b0f6a042306e82500f5e8db3b10ce69f84"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8416c23161abd94cc7da80c734ad7c9f5dbebdadfdaa77dad78244457448223"}, - {file = "pyzmq-26.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:082a2988364b60bb5de809373098361cf1dbb239623e39e46cb18bc035ed9c0c"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d57dfbf9737763b3a60d26e6800e02e04284926329aee8fb01049635e957fe81"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:77a85dca4c2430ac04dc2a2185c2deb3858a34fe7f403d0a946fa56970cf60a1"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4c82a6d952a1d555bf4be42b6532927d2a5686dd3c3e280e5f63225ab47ac1f5"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4496b1282c70c442809fc1b151977c3d967bfb33e4e17cedbf226d97de18f709"}, - {file = "pyzmq-26.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:e4946d6bdb7ba972dfda282f9127e5756d4f299028b1566d1245fa0d438847e6"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:03c0ae165e700364b266876d712acb1ac02693acd920afa67da2ebb91a0b3c09"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3e3070e680f79887d60feeda051a58d0ac36622e1759f305a41059eff62c6da7"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6ca08b840fe95d1c2bd9ab92dac5685f949fc6f9ae820ec16193e5ddf603c3b2"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e76654e9dbfb835b3518f9938e565c7806976c07b37c33526b574cc1a1050480"}, - {file = "pyzmq-26.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:871587bdadd1075b112e697173e946a07d722459d20716ceb3d1bd6c64bd08ce"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d0a2d1bd63a4ad79483049b26514e70fa618ce6115220da9efdff63688808b17"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0270b49b6847f0d106d64b5086e9ad5dc8a902413b5dbbb15d12b60f9c1747a4"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:703c60b9910488d3d0954ca585c34f541e506a091a41930e663a098d3b794c67"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74423631b6be371edfbf7eabb02ab995c2563fee60a80a30829176842e71722a"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4adfbb5451196842a88fda3612e2c0414134874bffb1c2ce83ab4242ec9e027d"}, - {file = "pyzmq-26.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3516119f4f9b8671083a70b6afaa0a070f5683e431ab3dc26e9215620d7ca1ad"}, - {file = "pyzmq-26.0.3.tar.gz", hash = "sha256:dba7d9f2e047dfa2bca3b01f4f84aa5246725203d6284e3790f2ca15fba6b40a"}, + {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629"}, + {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89289a5ee32ef6c439086184529ae060c741334b8970a6855ec0b6ad3ff28764"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5506f06d7dc6ecf1efacb4a013b1f05071bb24b76350832c96449f4a2d95091c"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ea039387c10202ce304af74def5021e9adc6297067f3441d348d2b633e8166a"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a2224fa4a4c2ee872886ed00a571f5e967c85e078e8e8c2530a2fb01b3309b88"}, + {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:28ad5233e9c3b52d76196c696e362508959741e1a005fb8fa03b51aea156088f"}, + {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1c17211bc037c7d88e85ed8b7d8f7e52db6dc8eca5590d162717c654550f7282"}, + {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b8f86dd868d41bea9a5f873ee13bf5551c94cf6bc51baebc6f85075971fe6eea"}, + {file = "pyzmq-26.2.0-cp310-cp310-win32.whl", hash = "sha256:46a446c212e58456b23af260f3d9fb785054f3e3653dbf7279d8f2b5546b21c2"}, + {file = "pyzmq-26.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:49d34ab71db5a9c292a7644ce74190b1dd5a3475612eefb1f8be1d6961441971"}, + {file = "pyzmq-26.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:bfa832bfa540e5b5c27dcf5de5d82ebc431b82c453a43d141afb1e5d2de025fa"}, + {file = "pyzmq-26.2.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:8f7e66c7113c684c2b3f1c83cdd3376103ee0ce4c49ff80a648643e57fb22218"}, + {file = "pyzmq-26.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3a495b30fc91db2db25120df5847d9833af237546fd59170701acd816ccc01c4"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77eb0968da535cba0470a5165468b2cac7772cfb569977cff92e240f57e31bef"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ace4f71f1900a548f48407fc9be59c6ba9d9aaf658c2eea6cf2779e72f9f317"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92a78853d7280bffb93df0a4a6a2498cba10ee793cc8076ef797ef2f74d107cf"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:689c5d781014956a4a6de61d74ba97b23547e431e9e7d64f27d4922ba96e9d6e"}, + {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0aca98bc423eb7d153214b2df397c6421ba6373d3397b26c057af3c904452e37"}, + {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f3496d76b89d9429a656293744ceca4d2ac2a10ae59b84c1da9b5165f429ad3"}, + {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5c2b3bfd4b9689919db068ac6c9911f3fcb231c39f7dd30e3138be94896d18e6"}, + {file = "pyzmq-26.2.0-cp311-cp311-win32.whl", hash = "sha256:eac5174677da084abf378739dbf4ad245661635f1600edd1221f150b165343f4"}, + {file = "pyzmq-26.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:5a509df7d0a83a4b178d0f937ef14286659225ef4e8812e05580776c70e155d5"}, + {file = "pyzmq-26.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0e6091b157d48cbe37bd67233318dbb53e1e6327d6fc3bb284afd585d141003"}, + {file = "pyzmq-26.2.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:ded0fc7d90fe93ae0b18059930086c51e640cdd3baebdc783a695c77f123dcd9"}, + {file = "pyzmq-26.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:17bf5a931c7f6618023cdacc7081f3f266aecb68ca692adac015c383a134ca52"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55cf66647e49d4621a7e20c8d13511ef1fe1efbbccf670811864452487007e08"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4661c88db4a9e0f958c8abc2b97472e23061f0bc737f6f6179d7a27024e1faa5"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea7f69de383cb47522c9c208aec6dd17697db7875a4674c4af3f8cfdac0bdeae"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7f98f6dfa8b8ccaf39163ce872bddacca38f6a67289116c8937a02e30bbe9711"}, + {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e3e0210287329272539eea617830a6a28161fbbd8a3271bf4150ae3e58c5d0e6"}, + {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6b274e0762c33c7471f1a7471d1a2085b1a35eba5cdc48d2ae319f28b6fc4de3"}, + {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:29c6a4635eef69d68a00321e12a7d2559fe2dfccfa8efae3ffb8e91cd0b36a8b"}, + {file = "pyzmq-26.2.0-cp312-cp312-win32.whl", hash = "sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7"}, + {file = "pyzmq-26.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a"}, + {file = "pyzmq-26.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b"}, + {file = "pyzmq-26.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726"}, + {file = "pyzmq-26.2.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b"}, + {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18"}, + {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115"}, + {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e"}, + {file = "pyzmq-26.2.0-cp313-cp313-win32.whl", hash = "sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5"}, + {file = "pyzmq-26.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad"}, + {file = "pyzmq-26.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797"}, + {file = "pyzmq-26.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a"}, + {file = "pyzmq-26.2.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386"}, + {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306"}, + {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6"}, + {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0"}, + {file = "pyzmq-26.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b55a4229ce5da9497dd0452b914556ae58e96a4381bb6f59f1305dfd7e53fc8"}, + {file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9cb3a6460cdea8fe8194a76de8895707e61ded10ad0be97188cc8463ffa7e3a8"}, + {file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ab5cad923cc95c87bffee098a27856c859bd5d0af31bd346035aa816b081fe1"}, + {file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ed69074a610fad1c2fda66180e7b2edd4d31c53f2d1872bc2d1211563904cd9"}, + {file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cccba051221b916a4f5e538997c45d7d136a5646442b1231b916d0164067ea27"}, + {file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:0eaa83fc4c1e271c24eaf8fb083cbccef8fde77ec8cd45f3c35a9a123e6da097"}, + {file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9edda2df81daa129b25a39b86cb57dfdfe16f7ec15b42b19bfac503360d27a93"}, + {file = "pyzmq-26.2.0-cp37-cp37m-win32.whl", hash = "sha256:ea0eb6af8a17fa272f7b98d7bebfab7836a0d62738e16ba380f440fceca2d951"}, + {file = "pyzmq-26.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4ff9dc6bc1664bb9eec25cd17506ef6672d506115095411e237d571e92a58231"}, + {file = "pyzmq-26.2.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2eb7735ee73ca1b0d71e0e67c3739c689067f055c764f73aac4cc8ecf958ee3f"}, + {file = "pyzmq-26.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a534f43bc738181aa7cbbaf48e3eca62c76453a40a746ab95d4b27b1111a7d2"}, + {file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:aedd5dd8692635813368e558a05266b995d3d020b23e49581ddd5bbe197a8ab6"}, + {file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8be4700cd8bb02cc454f630dcdf7cfa99de96788b80c51b60fe2fe1dac480289"}, + {file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fcc03fa4997c447dce58264e93b5aa2d57714fbe0f06c07b7785ae131512732"}, + {file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:402b190912935d3db15b03e8f7485812db350d271b284ded2b80d2e5704be780"}, + {file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8685fa9c25ff00f550c1fec650430c4b71e4e48e8d852f7ddcf2e48308038640"}, + {file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:76589c020680778f06b7e0b193f4b6dd66d470234a16e1df90329f5e14a171cd"}, + {file = "pyzmq-26.2.0-cp38-cp38-win32.whl", hash = "sha256:8423c1877d72c041f2c263b1ec6e34360448decfb323fa8b94e85883043ef988"}, + {file = "pyzmq-26.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:76589f2cd6b77b5bdea4fca5992dc1c23389d68b18ccc26a53680ba2dc80ff2f"}, + {file = "pyzmq-26.2.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:b1d464cb8d72bfc1a3adc53305a63a8e0cac6bc8c5a07e8ca190ab8d3faa43c2"}, + {file = "pyzmq-26.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4da04c48873a6abdd71811c5e163bd656ee1b957971db7f35140a2d573f6949c"}, + {file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d049df610ac811dcffdc147153b414147428567fbbc8be43bb8885f04db39d98"}, + {file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05590cdbc6b902101d0e65d6a4780af14dc22914cc6ab995d99b85af45362cc9"}, + {file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c811cfcd6a9bf680236c40c6f617187515269ab2912f3d7e8c0174898e2519db"}, + {file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6835dd60355593de10350394242b5757fbbd88b25287314316f266e24c61d073"}, + {file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc6bee759a6bddea5db78d7dcd609397449cb2d2d6587f48f3ca613b19410cfc"}, + {file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c530e1eecd036ecc83c3407f77bb86feb79916d4a33d11394b8234f3bd35b940"}, + {file = "pyzmq-26.2.0-cp39-cp39-win32.whl", hash = "sha256:367b4f689786fca726ef7a6c5ba606958b145b9340a5e4808132cc65759abd44"}, + {file = "pyzmq-26.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:e6fa2e3e683f34aea77de8112f6483803c96a44fd726d7358b9888ae5bb394ec"}, + {file = "pyzmq-26.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:7445be39143a8aa4faec43b076e06944b8f9d0701b669df4af200531b21e40bb"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:706e794564bec25819d21a41c31d4df2d48e1cc4b061e8d345d7fb4dd3e94072"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b435f2753621cd36e7c1762156815e21c985c72b19135dac43a7f4f31d28dd1"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160c7e0a5eb178011e72892f99f918c04a131f36056d10d9c1afb223fc952c2d"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4a71d5d6e7b28a47a394c0471b7e77a0661e2d651e7ae91e0cab0a587859ca"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:90412f2db8c02a3864cbfc67db0e3dcdbda336acf1c469526d3e869394fe001c"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2ea4ad4e6a12e454de05f2949d4beddb52460f3de7c8b9d5c46fbb7d7222e02c"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fc4f7a173a5609631bb0c42c23d12c49df3966f89f496a51d3eb0ec81f4519d6"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:878206a45202247781472a2d99df12a176fef806ca175799e1c6ad263510d57c"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17c412bad2eb9468e876f556eb4ee910e62d721d2c7a53c7fa31e643d35352e6"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0d987a3ae5a71c6226b203cfd298720e0086c7fe7c74f35fa8edddfbd6597eed"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:39887ac397ff35b7b775db7201095fc6310a35fdbae85bac4523f7eb3b840e20"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fdb5b3e311d4d4b0eb8b3e8b4d1b0a512713ad7e6a68791d0923d1aec433d919"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:226af7dcb51fdb0109f0016449b357e182ea0ceb6b47dfb5999d569e5db161d5"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bed0e799e6120b9c32756203fb9dfe8ca2fb8467fed830c34c877e25638c3fc"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:29c7947c594e105cb9e6c466bace8532dc1ca02d498684128b339799f5248277"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cdeabcff45d1c219636ee2e54d852262e5c2e085d6cb476d938aee8d921356b3"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35cffef589bcdc587d06f9149f8d5e9e8859920a071df5a2671de2213bef592a"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18c8dc3b7468d8b4bdf60ce9d7141897da103c7a4690157b32b60acb45e333e6"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7133d0a1677aec369d67dd78520d3fa96dd7f3dcec99d66c1762870e5ea1a50a"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6a96179a24b14fa6428cbfc08641c779a53f8fcec43644030328f44034c7f1f4"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4f78c88905461a9203eac9faac157a2a0dbba84a0fd09fd29315db27be40af9f"}, + {file = "pyzmq-26.2.0.tar.gz", hash = "sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f"}, ] [package.dependencies] cffi = {version = "*", markers = "implementation_name == \"pypy\""} -[[package]] -name = "qtconsole" -version = "5.5.2" -description = "Jupyter Qt console" -optional = false -python-versions = ">=3.8" -files = [ - {file = "qtconsole-5.5.2-py3-none-any.whl", hash = "sha256:42d745f3d05d36240244a04e1e1ec2a86d5d9b6edb16dbdef582ccb629e87e0b"}, - {file = "qtconsole-5.5.2.tar.gz", hash = "sha256:6b5fb11274b297463706af84dcbbd5c92273b1f619e6d25d08874b0a88516989"}, -] - -[package.dependencies] -ipykernel = ">=4.1" -jupyter-client = ">=4.1" -jupyter-core = "*" -packaging = "*" -pygments = "*" -pyzmq = ">=17.1" -qtpy = ">=2.4.0" -traitlets = "<5.2.1 || >5.2.1,<5.2.2 || >5.2.2" - -[package.extras] -doc = ["Sphinx (>=1.3)"] -test = ["flaky", "pytest", "pytest-qt"] - -[[package]] -name = "qtpy" -version = "2.4.1" -description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6)." -optional = false -python-versions = ">=3.7" -files = [ - {file = "QtPy-2.4.1-py3-none-any.whl", hash = "sha256:1c1d8c4fa2c884ae742b069151b0abe15b3f70491f3972698c683b8e38de839b"}, - {file = "QtPy-2.4.1.tar.gz", hash = "sha256:a5a15ffd519550a1361bdc56ffc07fda56a6af7292f17c7b395d4083af632987"}, -] - -[package.dependencies] -packaging = "*" - -[package.extras] -test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"] - [[package]] name = "referencing" version = "0.35.1" @@ -2665,6 +2788,20 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + [[package]] name = "rfc3339-validator" version = "0.1.4" @@ -2692,141 +2829,141 @@ files = [ [[package]] name = "rpds-py" -version = "0.20.0" +version = "0.20.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2"}, - {file = "rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140"}, - {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f"}, - {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce"}, - {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94"}, - {file = "rpds_py-0.20.0-cp310-none-win32.whl", hash = "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee"}, - {file = "rpds_py-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399"}, - {file = "rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489"}, - {file = "rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3"}, - {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272"}, - {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad"}, - {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58"}, - {file = "rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0"}, - {file = "rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c"}, - {file = "rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6"}, - {file = "rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef"}, - {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821"}, - {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940"}, - {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174"}, - {file = "rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139"}, - {file = "rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585"}, - {file = "rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29"}, - {file = "rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f"}, - {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c"}, - {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2"}, - {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57"}, - {file = "rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a"}, - {file = "rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2"}, - {file = "rpds_py-0.20.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24"}, - {file = "rpds_py-0.20.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8"}, - {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e"}, - {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253"}, - {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a"}, - {file = "rpds_py-0.20.0-cp38-none-win32.whl", hash = "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5"}, - {file = "rpds_py-0.20.0-cp38-none-win_amd64.whl", hash = "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232"}, - {file = "rpds_py-0.20.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22"}, - {file = "rpds_py-0.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580"}, - {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b"}, - {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420"}, - {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b"}, - {file = "rpds_py-0.20.0-cp39-none-win32.whl", hash = "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7"}, - {file = "rpds_py-0.20.0-cp39-none-win_amd64.whl", hash = "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8"}, - {file = "rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121"}, + {file = "rpds_py-0.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a649dfd735fff086e8a9d0503a9f0c7d01b7912a333c7ae77e1515c08c146dad"}, + {file = "rpds_py-0.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f16bc1334853e91ddaaa1217045dd7be166170beec337576818461268a3de67f"}, + {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14511a539afee6f9ab492b543060c7491c99924314977a55c98bfa2ee29ce78c"}, + {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3ccb8ac2d3c71cda472b75af42818981bdacf48d2e21c36331b50b4f16930163"}, + {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c142b88039b92e7e0cb2552e8967077e3179b22359e945574f5e2764c3953dcf"}, + {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f19169781dddae7478a32301b499b2858bc52fc45a112955e798ee307e294977"}, + {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13c56de6518e14b9bf6edde23c4c39dac5b48dcf04160ea7bce8fca8397cdf86"}, + {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:925d176a549f4832c6f69fa6026071294ab5910e82a0fe6c6228fce17b0706bd"}, + {file = "rpds_py-0.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:78f0b6877bfce7a3d1ff150391354a410c55d3cdce386f862926a4958ad5ab7e"}, + {file = "rpds_py-0.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3dd645e2b0dcb0fd05bf58e2e54c13875847687d0b71941ad2e757e5d89d4356"}, + {file = "rpds_py-0.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4f676e21db2f8c72ff0936f895271e7a700aa1f8d31b40e4e43442ba94973899"}, + {file = "rpds_py-0.20.1-cp310-none-win32.whl", hash = "sha256:648386ddd1e19b4a6abab69139b002bc49ebf065b596119f8f37c38e9ecee8ff"}, + {file = "rpds_py-0.20.1-cp310-none-win_amd64.whl", hash = "sha256:d9ecb51120de61e4604650666d1f2b68444d46ae18fd492245a08f53ad2b7711"}, + {file = "rpds_py-0.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:762703bdd2b30983c1d9e62b4c88664df4a8a4d5ec0e9253b0231171f18f6d75"}, + {file = "rpds_py-0.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0b581f47257a9fce535c4567782a8976002d6b8afa2c39ff616edf87cbeff712"}, + {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:842c19a6ce894493563c3bd00d81d5100e8e57d70209e84d5491940fdb8b9e3a"}, + {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42cbde7789f5c0bcd6816cb29808e36c01b960fb5d29f11e052215aa85497c93"}, + {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c8e9340ce5a52f95fa7d3b552b35c7e8f3874d74a03a8a69279fd5fca5dc751"}, + {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ba6f89cac95c0900d932c9efb7f0fb6ca47f6687feec41abcb1bd5e2bd45535"}, + {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a916087371afd9648e1962e67403c53f9c49ca47b9680adbeef79da3a7811b0"}, + {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:200a23239781f46149e6a415f1e870c5ef1e712939fe8fa63035cd053ac2638e"}, + {file = "rpds_py-0.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:58b1d5dd591973d426cbb2da5e27ba0339209832b2f3315928c9790e13f159e8"}, + {file = "rpds_py-0.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6b73c67850ca7cae0f6c56f71e356d7e9fa25958d3e18a64927c2d930859b8e4"}, + {file = "rpds_py-0.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d8761c3c891cc51e90bc9926d6d2f59b27beaf86c74622c8979380a29cc23ac3"}, + {file = "rpds_py-0.20.1-cp311-none-win32.whl", hash = "sha256:cd945871335a639275eee904caef90041568ce3b42f402c6959b460d25ae8732"}, + {file = "rpds_py-0.20.1-cp311-none-win_amd64.whl", hash = "sha256:7e21b7031e17c6b0e445f42ccc77f79a97e2687023c5746bfb7a9e45e0921b84"}, + {file = "rpds_py-0.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:36785be22066966a27348444b40389f8444671630063edfb1a2eb04318721e17"}, + {file = "rpds_py-0.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:142c0a5124d9bd0e2976089484af5c74f47bd3298f2ed651ef54ea728d2ea42c"}, + {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbddc10776ca7ebf2a299c41a4dde8ea0d8e3547bfd731cb87af2e8f5bf8962d"}, + {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15a842bb369e00295392e7ce192de9dcbf136954614124a667f9f9f17d6a216f"}, + {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be5ef2f1fc586a7372bfc355986226484e06d1dc4f9402539872c8bb99e34b01"}, + {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbcf360c9e3399b056a238523146ea77eeb2a596ce263b8814c900263e46031a"}, + {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecd27a66740ffd621d20b9a2f2b5ee4129a56e27bfb9458a3bcc2e45794c96cb"}, + {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0b937b2a1988f184a3e9e577adaa8aede21ec0b38320d6009e02bd026db04fa"}, + {file = "rpds_py-0.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6889469bfdc1eddf489729b471303739bf04555bb151fe8875931f8564309afc"}, + {file = "rpds_py-0.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:19b73643c802f4eaf13d97f7855d0fb527fbc92ab7013c4ad0e13a6ae0ed23bd"}, + {file = "rpds_py-0.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3c6afcf2338e7f374e8edc765c79fbcb4061d02b15dd5f8f314a4af2bdc7feb5"}, + {file = "rpds_py-0.20.1-cp312-none-win32.whl", hash = "sha256:dc73505153798c6f74854aba69cc75953888cf9866465196889c7cdd351e720c"}, + {file = "rpds_py-0.20.1-cp312-none-win_amd64.whl", hash = "sha256:8bbe951244a838a51289ee53a6bae3a07f26d4e179b96fc7ddd3301caf0518eb"}, + {file = "rpds_py-0.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6ca91093a4a8da4afae7fe6a222c3b53ee4eef433ebfee4d54978a103435159e"}, + {file = "rpds_py-0.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b9c2fe36d1f758b28121bef29ed1dee9b7a2453e997528e7d1ac99b94892527c"}, + {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f009c69bc8c53db5dfab72ac760895dc1f2bc1b62ab7408b253c8d1ec52459fc"}, + {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6740a3e8d43a32629bb9b009017ea5b9e713b7210ba48ac8d4cb6d99d86c8ee8"}, + {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:32b922e13d4c0080d03e7b62991ad7f5007d9cd74e239c4b16bc85ae8b70252d"}, + {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe00a9057d100e69b4ae4a094203a708d65b0f345ed546fdef86498bf5390982"}, + {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49fe9b04b6fa685bd39237d45fad89ba19e9163a1ccaa16611a812e682913496"}, + {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa7ac11e294304e615b43f8c441fee5d40094275ed7311f3420d805fde9b07b4"}, + {file = "rpds_py-0.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aa97af1558a9bef4025f8f5d8c60d712e0a3b13a2fe875511defc6ee77a1ab7"}, + {file = "rpds_py-0.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:483b29f6f7ffa6af845107d4efe2e3fa8fb2693de8657bc1849f674296ff6a5a"}, + {file = "rpds_py-0.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37fe0f12aebb6a0e3e17bb4cd356b1286d2d18d2e93b2d39fe647138458b4bcb"}, + {file = "rpds_py-0.20.1-cp313-none-win32.whl", hash = "sha256:a624cc00ef2158e04188df5e3016385b9353638139a06fb77057b3498f794782"}, + {file = "rpds_py-0.20.1-cp313-none-win_amd64.whl", hash = "sha256:b71b8666eeea69d6363248822078c075bac6ed135faa9216aa85f295ff009b1e"}, + {file = "rpds_py-0.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5b48e790e0355865197ad0aca8cde3d8ede347831e1959e158369eb3493d2191"}, + {file = "rpds_py-0.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3e310838a5801795207c66c73ea903deda321e6146d6f282e85fa7e3e4854804"}, + {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249280b870e6a42c0d972339e9cc22ee98730a99cd7f2f727549af80dd5a963"}, + {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e79059d67bea28b53d255c1437b25391653263f0e69cd7dec170d778fdbca95e"}, + {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b431c777c9653e569986ecf69ff4a5dba281cded16043d348bf9ba505486f36"}, + {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da584ff96ec95e97925174eb8237e32f626e7a1a97888cdd27ee2f1f24dd0ad8"}, + {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a0629ec053fc013808a85178524e3cb63a61dbc35b22499870194a63578fb9"}, + {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fbf15aff64a163db29a91ed0868af181d6f68ec1a3a7d5afcfe4501252840bad"}, + {file = "rpds_py-0.20.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:07924c1b938798797d60c6308fa8ad3b3f0201802f82e4a2c41bb3fafb44cc28"}, + {file = "rpds_py-0.20.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4a5a844f68776a7715ecb30843b453f07ac89bad393431efbf7accca3ef599c1"}, + {file = "rpds_py-0.20.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:518d2ca43c358929bf08f9079b617f1c2ca6e8848f83c1225c88caeac46e6cbc"}, + {file = "rpds_py-0.20.1-cp38-none-win32.whl", hash = "sha256:3aea7eed3e55119635a74bbeb80b35e776bafccb70d97e8ff838816c124539f1"}, + {file = "rpds_py-0.20.1-cp38-none-win_amd64.whl", hash = "sha256:7dca7081e9a0c3b6490a145593f6fe3173a94197f2cb9891183ef75e9d64c425"}, + {file = "rpds_py-0.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b41b6321805c472f66990c2849e152aff7bc359eb92f781e3f606609eac877ad"}, + {file = "rpds_py-0.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a90c373ea2975519b58dece25853dbcb9779b05cc46b4819cb1917e3b3215b6"}, + {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16d4477bcb9fbbd7b5b0e4a5d9b493e42026c0bf1f06f723a9353f5153e75d30"}, + {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84b8382a90539910b53a6307f7c35697bc7e6ffb25d9c1d4e998a13e842a5e83"}, + {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4888e117dd41b9d34194d9e31631af70d3d526efc363085e3089ab1a62c32ed1"}, + {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5265505b3d61a0f56618c9b941dc54dc334dc6e660f1592d112cd103d914a6db"}, + {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e75ba609dba23f2c95b776efb9dd3f0b78a76a151e96f96cc5b6b1b0004de66f"}, + {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1791ff70bc975b098fe6ecf04356a10e9e2bd7dc21fa7351c1742fdeb9b4966f"}, + {file = "rpds_py-0.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d126b52e4a473d40232ec2052a8b232270ed1f8c9571aaf33f73a14cc298c24f"}, + {file = "rpds_py-0.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c14937af98c4cc362a1d4374806204dd51b1e12dded1ae30645c298e5a5c4cb1"}, + {file = "rpds_py-0.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3d089d0b88996df627693639d123c8158cff41c0651f646cd8fd292c7da90eaf"}, + {file = "rpds_py-0.20.1-cp39-none-win32.whl", hash = "sha256:653647b8838cf83b2e7e6a0364f49af96deec64d2a6578324db58380cff82aca"}, + {file = "rpds_py-0.20.1-cp39-none-win_amd64.whl", hash = "sha256:fa41a64ac5b08b292906e248549ab48b69c5428f3987b09689ab2441f267d04d"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a07ced2b22f0cf0b55a6a510078174c31b6d8544f3bc00c2bcee52b3d613f74"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:68cb0a499f2c4a088fd2f521453e22ed3527154136a855c62e148b7883b99f9a"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa3060d885657abc549b2a0f8e1b79699290e5d83845141717c6c90c2df38311"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95f3b65d2392e1c5cec27cff08fdc0080270d5a1a4b2ea1d51d5f4a2620ff08d"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2cc3712a4b0b76a1d45a9302dd2f53ff339614b1c29603a911318f2357b04dd2"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d4eea0761e37485c9b81400437adb11c40e13ef513375bbd6973e34100aeb06"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f5179583d7a6cdb981151dd349786cbc318bab54963a192692d945dd3f6435d"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fbb0ffc754490aff6dabbf28064be47f0f9ca0b9755976f945214965b3ace7e"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:a94e52537a0e0a85429eda9e49f272ada715506d3b2431f64b8a3e34eb5f3e75"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:92b68b79c0da2a980b1c4197e56ac3dd0c8a149b4603747c4378914a68706979"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:93da1d3db08a827eda74356f9f58884adb254e59b6664f64cc04cdff2cc19b0d"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:754bbed1a4ca48479e9d4182a561d001bbf81543876cdded6f695ec3d465846b"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ca449520e7484534a2a44faf629362cae62b660601432d04c482283c47eaebab"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:9c4cb04a16b0f199a8c9bf807269b2f63b7b5b11425e4a6bd44bd6961d28282c"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63804105143c7e24cee7db89e37cb3f3941f8e80c4379a0b355c52a52b6780"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:55cd1fa4ecfa6d9f14fbd97ac24803e6f73e897c738f771a9fe038f2f11ff07c"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f8f741b6292c86059ed175d80eefa80997125b7c478fb8769fd9ac8943a16c0"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fc212779bf8411667234b3cdd34d53de6c2b8b8b958e1e12cb473a5f367c338"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ad56edabcdb428c2e33bbf24f255fe2b43253b7d13a2cdbf05de955217313e6"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a3a1e9ee9728b2c1734f65d6a1d376c6f2f6fdcc13bb007a08cc4b1ff576dc5"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:e13de156137b7095442b288e72f33503a469aa1980ed856b43c353ac86390519"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:07f59760ef99f31422c49038964b31c4dfcfeb5d2384ebfc71058a7c9adae2d2"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:59240685e7da61fb78f65a9f07f8108e36a83317c53f7b276b4175dc44151684"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:83cba698cfb3c2c5a7c3c6bac12fe6c6a51aae69513726be6411076185a8b24a"}, + {file = "rpds_py-0.20.1.tar.gz", hash = "sha256:e1791c4aabd117653530dccd24108fa03cc6baf21f58b950d0a73c3b3b29a350"}, ] [[package]] name = "ruff" -version = "0.6.2" +version = "0.6.9" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.6.2-py3-none-linux_armv6l.whl", hash = "sha256:5c8cbc6252deb3ea840ad6a20b0f8583caab0c5ef4f9cca21adc5a92b8f79f3c"}, - {file = "ruff-0.6.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:17002fe241e76544448a8e1e6118abecbe8cd10cf68fde635dad480dba594570"}, - {file = "ruff-0.6.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3dbeac76ed13456f8158b8f4fe087bf87882e645c8e8b606dd17b0b66c2c1158"}, - {file = "ruff-0.6.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:094600ee88cda325988d3f54e3588c46de5c18dae09d683ace278b11f9d4d534"}, - {file = "ruff-0.6.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:316d418fe258c036ba05fbf7dfc1f7d3d4096db63431546163b472285668132b"}, - {file = "ruff-0.6.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d72b8b3abf8a2d51b7b9944a41307d2f442558ccb3859bbd87e6ae9be1694a5d"}, - {file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2aed7e243be68487aa8982e91c6e260982d00da3f38955873aecd5a9204b1d66"}, - {file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d371f7fc9cec83497fe7cf5eaf5b76e22a8efce463de5f775a1826197feb9df8"}, - {file = "ruff-0.6.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8f310d63af08f583363dfb844ba8f9417b558199c58a5999215082036d795a1"}, - {file = "ruff-0.6.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7db6880c53c56addb8638fe444818183385ec85eeada1d48fc5abe045301b2f1"}, - {file = "ruff-0.6.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1175d39faadd9a50718f478d23bfc1d4da5743f1ab56af81a2b6caf0a2394f23"}, - {file = "ruff-0.6.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b939f9c86d51635fe486585389f54582f0d65b8238e08c327c1534844b3bb9a"}, - {file = "ruff-0.6.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d0d62ca91219f906caf9b187dea50d17353f15ec9bb15aae4a606cd697b49b4c"}, - {file = "ruff-0.6.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7438a7288f9d67ed3c8ce4d059e67f7ed65e9fe3aa2ab6f5b4b3610e57e3cb56"}, - {file = "ruff-0.6.2-py3-none-win32.whl", hash = "sha256:279d5f7d86696df5f9549b56b9b6a7f6c72961b619022b5b7999b15db392a4da"}, - {file = "ruff-0.6.2-py3-none-win_amd64.whl", hash = "sha256:d9f3469c7dd43cd22eb1c3fc16926fb8258d50cb1b216658a07be95dd117b0f2"}, - {file = "ruff-0.6.2-py3-none-win_arm64.whl", hash = "sha256:f28fcd2cd0e02bdf739297516d5643a945cc7caf09bd9bcb4d932540a5ea4fa9"}, - {file = "ruff-0.6.2.tar.gz", hash = "sha256:239ee6beb9e91feb8e0ec384204a763f36cb53fb895a1a364618c6abb076b3be"}, + {file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"}, + {file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"}, + {file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"}, + {file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"}, + {file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"}, + {file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"}, + {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"}, + {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"}, + {file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"}, + {file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"}, + {file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"}, + {file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"}, + {file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"}, + {file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"}, + {file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"}, + {file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"}, + {file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"}, + {file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"}, ] [[package]] @@ -2847,18 +2984,23 @@ win32 = ["pywin32"] [[package]] name = "setuptools" -version = "70.0.0" +version = "75.3.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"}, - {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"}, + {file = "setuptools-75.3.0-py3-none-any.whl", hash = "sha256:f2504966861356aa38616760c0f66568e535562374995367b4e69c7143cf6bcd"}, + {file = "setuptools-75.3.0.tar.gz", hash = "sha256:fba5dd4d766e97be1b1681d98712680ae8f2f26d7881245f2ce9e40714f1a686"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.12.*)", "pytest-mypy"] [[package]] name = "six" @@ -2884,13 +3026,13 @@ files = [ [[package]] name = "soupsieve" -version = "2.5" +version = "2.6" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" files = [ - {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, - {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, ] [[package]] @@ -2914,13 +3056,13 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] [[package]] name = "syrupy" -version = "4.6.1" +version = "4.7.2" description = "Pytest Snapshot Test Utility" optional = false -python-versions = ">=3.8.1,<4" +python-versions = ">=3.8.1" files = [ - {file = "syrupy-4.6.1-py3-none-any.whl", hash = "sha256:203e52f9cb9fa749cf683f29bd68f02c16c3bc7e7e5fe8f2fc59bdfe488ce133"}, - {file = "syrupy-4.6.1.tar.gz", hash = "sha256:37a835c9ce7857eeef86d62145885e10b3cb9615bc6abeb4ce404b3f18e1bb36"}, + {file = "syrupy-4.7.2-py3-none-any.whl", hash = "sha256:eae7ba6be5aed190237caa93be288e97ca1eec5ca58760e4818972a10c4acc64"}, + {file = "syrupy-4.7.2.tar.gz", hash = "sha256:ea45e099f242de1bb53018c238f408a5bb6c82007bc687aefcbeaa0e1c2e935a"}, ] [package.dependencies] @@ -2928,13 +3070,13 @@ pytest = ">=7.0.0,<9.0.0" [[package]] name = "tenacity" -version = "8.4.1" +version = "9.0.0" description = "Retry code until it succeeds" optional = false python-versions = ">=3.8" files = [ - {file = "tenacity-8.4.1-py3-none-any.whl", hash = "sha256:28522e692eda3e1b8f5e99c51464efcc0b9fc86933da92415168bc1c4e2308fa"}, - {file = "tenacity-8.4.1.tar.gz", hash = "sha256:54b1412b878ddf7e1f1577cd49527bad8cdef32421bd599beac0c6c3f10582fd"}, + {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, + {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, ] [package.extras] @@ -2964,13 +3106,13 @@ typing = ["mypy (>=1.6,<2.0)", "traitlets (>=5.11.1)"] [[package]] name = "tinycss2" -version = "1.3.0" +version = "1.4.0" description = "A tiny CSS parser" optional = false python-versions = ">=3.8" files = [ - {file = "tinycss2-1.3.0-py3-none-any.whl", hash = "sha256:54a8dbdffb334d536851be0226030e9505965bb2f30f21a4a82c55fb2a80fae7"}, - {file = "tinycss2-1.3.0.tar.gz", hash = "sha256:152f9acabd296a8375fbca5b84c961ff95971fcfc32e79550c8df8e29118c54d"}, + {file = "tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289"}, + {file = "tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7"}, ] [package.dependencies] @@ -2982,13 +3124,13 @@ test = ["pytest", "ruff"] [[package]] name = "tomli" -version = "2.0.1" +version = "2.0.2" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, ] [[package]] @@ -3028,24 +3170,24 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0, [[package]] name = "types-python-dateutil" -version = "2.9.0.20240316" +version = "2.9.0.20241003" description = "Typing stubs for python-dateutil" optional = false python-versions = ">=3.8" files = [ - {file = "types-python-dateutil-2.9.0.20240316.tar.gz", hash = "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202"}, - {file = "types_python_dateutil-2.9.0.20240316-py3-none-any.whl", hash = "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b"}, + {file = "types-python-dateutil-2.9.0.20241003.tar.gz", hash = "sha256:58cb85449b2a56d6684e41aeefb4c4280631246a0da1a719bdbe6f3fb0317446"}, + {file = "types_python_dateutil-2.9.0.20241003-py3-none-any.whl", hash = "sha256:250e1d8e80e7bbc3a6c99b907762711d1a1cdd00e978ad39cb5940f6f0a87f3d"}, ] [[package]] name = "types-requests" -version = "2.32.0.20240914" +version = "2.32.0.20241016" description = "Typing stubs for requests" optional = false python-versions = ">=3.8" files = [ - {file = "types-requests-2.32.0.20240914.tar.gz", hash = "sha256:2850e178db3919d9bf809e434eef65ba49d0e7e33ac92d588f4a5e295fffd405"}, - {file = "types_requests-2.32.0.20240914-py3-none-any.whl", hash = "sha256:59c2f673eb55f32a99b2894faf6020e1a9f4a402ad0f192bfee0b64469054310"}, + {file = "types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95"}, + {file = "types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747"}, ] [package.dependencies] @@ -3064,13 +3206,13 @@ files = [ [[package]] name = "tzdata" -version = "2024.1" +version = "2024.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, + {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, + {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, ] [[package]] @@ -3089,13 +3231,13 @@ dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake [[package]] name = "urllib3" -version = "2.2.2" +version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, - {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] @@ -3157,43 +3299,41 @@ test = ["aiohttp (>=3.10.5)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", [[package]] name = "watchdog" -version = "4.0.1" +version = "5.0.3" description = "Filesystem events monitoring" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"}, - {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"}, - {file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"}, - {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"}, - {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"}, - {file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"}, - {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"}, - {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"}, - {file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"}, - {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35"}, - {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db"}, - {file = "watchdog-4.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709"}, - {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba"}, - {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235"}, - {file = "watchdog-4.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682"}, - {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"}, - {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"}, - {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193"}, - {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625"}, - {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd"}, - {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"}, - {file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"}, - {file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"}, - {file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"}, - {file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"}, - {file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"}, + {file = "watchdog-5.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:85527b882f3facda0579bce9d743ff7f10c3e1e0db0a0d0e28170a7d0e5ce2ea"}, + {file = "watchdog-5.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:53adf73dcdc0ef04f7735066b4a57a4cd3e49ef135daae41d77395f0b5b692cb"}, + {file = "watchdog-5.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e25adddab85f674acac303cf1f5835951345a56c5f7f582987d266679979c75b"}, + {file = "watchdog-5.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f01f4a3565a387080dc49bdd1fefe4ecc77f894991b88ef927edbfa45eb10818"}, + {file = "watchdog-5.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91b522adc25614cdeaf91f7897800b82c13b4b8ac68a42ca959f992f6990c490"}, + {file = "watchdog-5.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d52db5beb5e476e6853da2e2d24dbbbed6797b449c8bf7ea118a4ee0d2c9040e"}, + {file = "watchdog-5.0.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:94d11b07c64f63f49876e0ab8042ae034674c8653bfcdaa8c4b32e71cfff87e8"}, + {file = "watchdog-5.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:349c9488e1d85d0a58e8cb14222d2c51cbc801ce11ac3936ab4c3af986536926"}, + {file = "watchdog-5.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:53a3f10b62c2d569e260f96e8d966463dec1a50fa4f1b22aec69e3f91025060e"}, + {file = "watchdog-5.0.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:950f531ec6e03696a2414b6308f5c6ff9dab7821a768c9d5788b1314e9a46ca7"}, + {file = "watchdog-5.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae6deb336cba5d71476caa029ceb6e88047fc1dc74b62b7c4012639c0b563906"}, + {file = "watchdog-5.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1021223c08ba8d2d38d71ec1704496471ffd7be42cfb26b87cd5059323a389a1"}, + {file = "watchdog-5.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:752fb40efc7cc8d88ebc332b8f4bcbe2b5cc7e881bccfeb8e25054c00c994ee3"}, + {file = "watchdog-5.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a2e8f3f955d68471fa37b0e3add18500790d129cc7efe89971b8a4cc6fdeb0b2"}, + {file = "watchdog-5.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b8ca4d854adcf480bdfd80f46fdd6fb49f91dd020ae11c89b3a79e19454ec627"}, + {file = "watchdog-5.0.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:90a67d7857adb1d985aca232cc9905dd5bc4803ed85cfcdcfcf707e52049eda7"}, + {file = "watchdog-5.0.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:720ef9d3a4f9ca575a780af283c8fd3a0674b307651c1976714745090da5a9e8"}, + {file = "watchdog-5.0.3-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:223160bb359281bb8e31c8f1068bf71a6b16a8ad3d9524ca6f523ac666bb6a1e"}, + {file = "watchdog-5.0.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:560135542c91eaa74247a2e8430cf83c4342b29e8ad4f520ae14f0c8a19cfb5b"}, + {file = "watchdog-5.0.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dd021efa85970bd4824acacbb922066159d0f9e546389a4743d56919b6758b91"}, + {file = "watchdog-5.0.3-py3-none-manylinux2014_armv7l.whl", hash = "sha256:78864cc8f23dbee55be34cc1494632a7ba30263951b5b2e8fc8286b95845f82c"}, + {file = "watchdog-5.0.3-py3-none-manylinux2014_i686.whl", hash = "sha256:1e9679245e3ea6498494b3028b90c7b25dbb2abe65c7d07423ecfc2d6218ff7c"}, + {file = "watchdog-5.0.3-py3-none-manylinux2014_ppc64.whl", hash = "sha256:9413384f26b5d050b6978e6fcd0c1e7f0539be7a4f1a885061473c5deaa57221"}, + {file = "watchdog-5.0.3-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:294b7a598974b8e2c6123d19ef15de9abcd282b0fbbdbc4d23dfa812959a9e05"}, + {file = "watchdog-5.0.3-py3-none-manylinux2014_s390x.whl", hash = "sha256:26dd201857d702bdf9d78c273cafcab5871dd29343748524695cecffa44a8d97"}, + {file = "watchdog-5.0.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:0f9332243355643d567697c3e3fa07330a1d1abf981611654a1f2bf2175612b7"}, + {file = "watchdog-5.0.3-py3-none-win32.whl", hash = "sha256:c66f80ee5b602a9c7ab66e3c9f36026590a0902db3aea414d59a2f55188c1f49"}, + {file = "watchdog-5.0.3-py3-none-win_amd64.whl", hash = "sha256:f00b4cf737f568be9665563347a910f8bdc76f88c2970121c86243c8cfdf90e9"}, + {file = "watchdog-5.0.3-py3-none-win_ia64.whl", hash = "sha256:49f4d36cb315c25ea0d946e018c01bb028048023b9e103d3d3943f58e109dd45"}, + {file = "watchdog-5.0.3.tar.gz", hash = "sha256:108f42a7f0345042a854d4d0ad0834b741d421330d5f575b81cb27b883500176"}, ] [package.extras] @@ -3212,13 +3352,13 @@ files = [ [[package]] name = "webcolors" -version = "24.6.0" +version = "24.8.0" description = "A library for working with the color formats defined by HTML and CSS." optional = false python-versions = ">=3.8" files = [ - {file = "webcolors-24.6.0-py3-none-any.whl", hash = "sha256:8cf5bc7e28defd1d48b9e83d5fc30741328305a8195c29a8e668fa45586568a1"}, - {file = "webcolors-24.6.0.tar.gz", hash = "sha256:1d160d1de46b3e81e58d0a280d0c78b467dc80f47294b91b1ad8029d2cedb55b"}, + {file = "webcolors-24.8.0-py3-none-any.whl", hash = "sha256:fc4c3b59358ada164552084a8ebee637c221e4059267d0f8325b3b560f6c7f0a"}, + {file = "webcolors-24.8.0.tar.gz", hash = "sha256:08b07af286a01bcd30d583a7acadf629583d1f79bfef27dd2c2c5c263817277d"}, ] [package.extras] @@ -3254,31 +3394,35 @@ test = ["websockets"] [[package]] name = "widgetsnbextension" -version = "4.0.11" +version = "4.0.13" description = "Jupyter interactive widgets for Jupyter Notebook" optional = false python-versions = ">=3.7" files = [ - {file = "widgetsnbextension-4.0.11-py3-none-any.whl", hash = "sha256:55d4d6949d100e0d08b94948a42efc3ed6dfdc0e9468b2c4b128c9a2ce3a7a36"}, - {file = "widgetsnbextension-4.0.11.tar.gz", hash = "sha256:8b22a8f1910bfd188e596fe7fc05dcbd87e810c8a4ba010bdb3da86637398474"}, + {file = "widgetsnbextension-4.0.13-py3-none-any.whl", hash = "sha256:74b2692e8500525cc38c2b877236ba51d34541e6385eeed5aec15a70f88a6c71"}, + {file = "widgetsnbextension-4.0.13.tar.gz", hash = "sha256:ffcb67bc9febd10234a362795f643927f4e0c05d9342c727b65d2384f8feacb6"}, ] [[package]] name = "zipp" -version = "3.19.2" +version = "3.20.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, - {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, + {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, + {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, ] [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.9.0,<4.0" -content-hash = "fefcf32c107aa6384115fc90b5dc628ca784667a970f2390a49750e66b334f8b" +content-hash = "9bf5668d3f70f3b77457906732404a6401583a5966f70a72ef10a68f2a5b27ad" diff --git a/libs/langgraph/pyproject.toml b/libs/langgraph/pyproject.toml index 9197bb2b6..d92effd90 100644 --- a/libs/langgraph/pyproject.toml +++ b/libs/langgraph/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langgraph" -version = "0.2.38" +version = "0.2.52" description = "Building stateful, multi-actor applications with LLMs" authors = [] license = "MIT" @@ -9,8 +9,8 @@ repository = "https://www.github.com/langchain-ai/langgraph" [tool.poetry.dependencies] python = ">=3.9.0,<4.0" -langchain-core = ">=0.2.39,<0.4" -langgraph-checkpoint = "^2.0.0" +langchain-core = ">=0.2.43,<0.4.0,!=0.3.0,!=0.3.1,!=0.3.2,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14" +langgraph-checkpoint = "^2.0.4" langgraph-sdk = "^0.1.32" [tool.poetry.group.dev.dependencies] @@ -27,6 +27,7 @@ jupyter = "^1.0.0" pytest-xdist = {extras = ["psutil"], version = "^3.6.1"} pytest-repeat = "^0.9.3" langgraph-checkpoint = {path = "../checkpoint", develop = true} +langgraph-checkpoint-duckdb = {path = "../checkpoint-duckdb", 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} diff --git a/libs/langgraph/tests/__snapshots__/test_pregel.ambr b/libs/langgraph/tests/__snapshots__/test_pregel.ambr index 86ed2de4e..1cfdd9b76 100644 --- a/libs/langgraph/tests/__snapshots__/test_pregel.ambr +++ b/libs/langgraph/tests/__snapshots__/test_pregel.ambr @@ -21,6 +21,40 @@ ''' # --- +# name: test_branch_then[duckdb] + ''' + graph TD; + __start__ --> prepare; + finish --> __end__; + prepare -.-> tool_two_slow; + tool_two_slow --> finish; + prepare -.-> tool_two_fast; + tool_two_fast --> finish; + + ''' +# --- +# name: test_branch_then[duckdb].1 + ''' + %%{init: {'flowchart': {'curve': 'linear'}}}%% + graph TD; + __start__([

__start__

]):::first + prepare(prepare) + tool_two_slow(tool_two_slow) + tool_two_fast(tool_two_fast) + finish(finish) + __end__([

__end__

]):::last + __start__ --> prepare; + finish --> __end__; + prepare -.-> tool_two_slow; + tool_two_slow --> finish; + prepare -.-> tool_two_fast; + tool_two_fast --> finish; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc + + ''' +# --- # name: test_branch_then[memory] ''' graph TD; @@ -695,6 +729,281 @@ ''' # --- +# name: test_conditional_graph[duckdb] + ''' + { + "nodes": [ + { + "id": "__start__", + "type": "schema", + "data": "__start__" + }, + { + "id": "agent", + "type": "runnable", + "data": { + "id": [ + "langchain", + "schema", + "runnable", + "RunnableAssign" + ], + "name": "agent" + } + }, + { + "id": "tools", + "type": "runnable", + "data": { + "id": [ + "langgraph", + "utils", + "runnable", + "RunnableCallable" + ], + "name": "tools" + }, + "metadata": { + "parents": {}, + "version": 2, + "variant": "b" + } + }, + { + "id": "__end__", + "type": "schema", + "data": "__end__" + } + ], + "edges": [ + { + "source": "__start__", + "target": "agent" + }, + { + "source": "tools", + "target": "agent" + }, + { + "source": "agent", + "target": "tools", + "data": "continue", + "conditional": true + }, + { + "source": "agent", + "target": "__end__", + "data": "exit", + "conditional": true + } + ] + } + ''' +# --- +# name: test_conditional_graph[duckdb].1 + ''' + graph TD; + __start__ --> agent; + tools --> agent; + agent -.  continue  .-> tools; + agent -.  exit  .-> __end__; + + ''' +# --- +# name: test_conditional_graph[duckdb].2 + ''' + %%{init: {'flowchart': {'curve': 'linear'}}}%% + graph TD; + __start__([

__start__

]):::first + agent(agent) + tools(tools
parents = {} + version = 2 + variant = b) + __end__([

__end__

]):::last + __start__ --> agent; + tools --> agent; + agent -.  continue  .-> tools; + agent -.  exit  .-> __end__; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc + + ''' +# --- +# name: test_conditional_graph[duckdb].3 + ''' + { + "nodes": [ + { + "id": "__start__", + "type": "schema", + "data": "__start__" + }, + { + "id": "agent", + "type": "runnable", + "data": { + "id": [ + "langchain", + "schema", + "runnable", + "RunnableAssign" + ], + "name": "agent" + } + }, + { + "id": "tools", + "type": "runnable", + "data": { + "id": [ + "langgraph", + "utils", + "runnable", + "RunnableCallable" + ], + "name": "tools" + }, + "metadata": { + "parents": {}, + "version": 2, + "variant": "b" + } + }, + { + "id": "__end__", + "type": "schema", + "data": "__end__" + } + ], + "edges": [ + { + "source": "__start__", + "target": "agent" + }, + { + "source": "tools", + "target": "agent" + }, + { + "source": "agent", + "target": "tools", + "data": "continue", + "conditional": true + }, + { + "source": "agent", + "target": "__end__", + "data": "exit", + "conditional": true + } + ] + } + ''' +# --- +# name: test_conditional_graph[duckdb].4 + ''' + graph TD; + __start__ --> agent; + tools --> agent; + agent -.  continue  .-> tools; + agent -.  exit  .-> __end__; + + ''' +# --- +# name: test_conditional_graph[duckdb].5 + dict({ + 'edges': list([ + dict({ + 'source': '__start__', + 'target': 'agent', + }), + dict({ + 'source': 'tools', + 'target': 'agent', + }), + dict({ + 'conditional': True, + 'data': 'continue', + 'source': 'agent', + 'target': 'tools', + }), + dict({ + 'conditional': True, + 'data': 'exit', + 'source': 'agent', + 'target': '__end__', + }), + ]), + 'nodes': list([ + dict({ + 'data': '__start__', + 'id': '__start__', + 'type': 'schema', + }), + dict({ + 'data': dict({ + 'id': list([ + 'langchain', + 'schema', + 'runnable', + 'RunnableAssign', + ]), + 'name': 'agent', + }), + 'id': 'agent', + 'metadata': dict({ + '__interrupt': 'after', + }), + 'type': 'runnable', + }), + dict({ + 'data': dict({ + 'id': list([ + 'langgraph', + 'utils', + 'runnable', + 'RunnableCallable', + ]), + 'name': 'tools', + }), + 'id': 'tools', + 'metadata': dict({ + 'parents': dict({ + }), + 'variant': 'b', + 'version': 2, + }), + 'type': 'runnable', + }), + dict({ + 'data': '__end__', + 'id': '__end__', + 'type': 'schema', + }), + ]), + }) +# --- +# name: test_conditional_graph[duckdb].6 + ''' + %%{init: {'flowchart': {'curve': 'linear'}}}%% + graph TD; + __start__([

__start__

]):::first + agent(agent
__interrupt = after) + tools(tools
parents = {} + version = 2 + variant = b) + __end__([

__end__

]):::last + __start__ --> agent; + tools --> agent; + agent -.  continue  .-> tools; + agent -.  exit  .-> __end__; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc + + ''' +# --- # name: test_conditional_graph[memory] ''' { @@ -806,65 +1115,16 @@ "data": "__start__" }, { - "id": 1, - "type": "schema", - "data": "ParallelInput" - }, - { - "id": 2, - "type": "schema", - "data": "ParallelOutput" - }, - { - "id": 3, - "type": "runnable", - "data": { - "id": [ - "langchain", - "prompts", - "prompt", - "PromptTemplate" - ], - "name": "PromptTemplate" - } - }, - { - "id": 4, - "type": "runnable", - "data": { - "id": [ - "langchain_core", - "language_models", - "fake", - "FakeStreamingListLLM" - ], - "name": "FakeStreamingListLLM" - } - }, - { - "id": 5, - "type": "runnable", - "data": { - "id": [ - "langchain_core", - "runnables", - "base", - "RunnableLambda" - ], - "name": "agent_parser" - } - }, - { - "id": 6, + "id": "agent", "type": "runnable", "data": { "id": [ "langchain", "schema", "runnable", - "RunnablePassthrough" + "RunnableAssign" ], - "name": "Passthrough" + "name": "agent" } }, { @@ -892,46 +1152,22 @@ } ], "edges": [ - { - "source": 3, - "target": 4 - }, - { - "source": 4, - "target": 5 - }, - { - "source": 1, - "target": 3 - }, - { - "source": 5, - "target": 2 - }, - { - "source": 1, - "target": 6 - }, - { - "source": 6, - "target": 2 - }, { "source": "__start__", - "target": 1 + "target": "agent" }, { "source": "tools", - "target": 1 + "target": "agent" }, { - "source": 2, + "source": "agent", "target": "tools", "data": "continue", "conditional": true }, { - "source": 2, + "source": "agent", "target": "__end__", "data": "exit", "conditional": true @@ -943,16 +1179,10 @@ # name: test_conditional_graph[memory].4 ''' graph TD; - PromptTemplate --> FakeStreamingListLLM; - FakeStreamingListLLM --> agent_parser; - Parallel_agent_outcome_Input --> PromptTemplate; - agent_parser --> Parallel_agent_outcome_Output; - Parallel_agent_outcome_Input --> Passthrough; - Passthrough --> Parallel_agent_outcome_Output; - __start__ --> Parallel_agent_outcome_Input; - tools --> Parallel_agent_outcome_Input; - Parallel_agent_outcome_Output -.  continue  .-> tools; - Parallel_agent_outcome_Output -.  exit  .-> __end__; + __start__ --> agent; + tools --> agent; + agent -.  continue  .-> tools; + agent -.  exit  .-> __end__; ''' # --- @@ -1160,65 +1390,16 @@ "data": "__start__" }, { - "id": 1, - "type": "schema", - "data": "ParallelInput" - }, - { - "id": 2, - "type": "schema", - "data": "ParallelOutput" - }, - { - "id": 3, - "type": "runnable", - "data": { - "id": [ - "langchain", - "prompts", - "prompt", - "PromptTemplate" - ], - "name": "PromptTemplate" - } - }, - { - "id": 4, - "type": "runnable", - "data": { - "id": [ - "langchain_core", - "language_models", - "fake", - "FakeStreamingListLLM" - ], - "name": "FakeStreamingListLLM" - } - }, - { - "id": 5, - "type": "runnable", - "data": { - "id": [ - "langchain_core", - "runnables", - "base", - "RunnableLambda" - ], - "name": "agent_parser" - } - }, - { - "id": 6, + "id": "agent", "type": "runnable", "data": { "id": [ "langchain", "schema", "runnable", - "RunnablePassthrough" + "RunnableAssign" ], - "name": "Passthrough" + "name": "agent" } }, { @@ -1246,46 +1427,22 @@ } ], "edges": [ - { - "source": 3, - "target": 4 - }, - { - "source": 4, - "target": 5 - }, - { - "source": 1, - "target": 3 - }, - { - "source": 5, - "target": 2 - }, - { - "source": 1, - "target": 6 - }, - { - "source": 6, - "target": 2 - }, { "source": "__start__", - "target": 1 + "target": "agent" }, { "source": "tools", - "target": 1 + "target": "agent" }, { - "source": 2, + "source": "agent", "target": "tools", "data": "continue", "conditional": true }, { - "source": 2, + "source": "agent", "target": "__end__", "data": "exit", "conditional": true @@ -1297,16 +1454,10 @@ # name: test_conditional_graph[postgres].4 ''' graph TD; - PromptTemplate --> FakeStreamingListLLM; - FakeStreamingListLLM --> agent_parser; - Parallel_agent_outcome_Input --> PromptTemplate; - agent_parser --> Parallel_agent_outcome_Output; - Parallel_agent_outcome_Input --> Passthrough; - Passthrough --> Parallel_agent_outcome_Output; - __start__ --> Parallel_agent_outcome_Input; - tools --> Parallel_agent_outcome_Input; - Parallel_agent_outcome_Output -.  continue  .-> tools; - Parallel_agent_outcome_Output -.  exit  .-> __end__; + __start__ --> agent; + tools --> agent; + agent -.  continue  .-> tools; + agent -.  exit  .-> __end__; ''' # --- @@ -1514,65 +1665,16 @@ "data": "__start__" }, { - "id": 1, - "type": "schema", - "data": "ParallelInput" - }, - { - "id": 2, - "type": "schema", - "data": "ParallelOutput" - }, - { - "id": 3, - "type": "runnable", - "data": { - "id": [ - "langchain", - "prompts", - "prompt", - "PromptTemplate" - ], - "name": "PromptTemplate" - } - }, - { - "id": 4, - "type": "runnable", - "data": { - "id": [ - "langchain_core", - "language_models", - "fake", - "FakeStreamingListLLM" - ], - "name": "FakeStreamingListLLM" - } - }, - { - "id": 5, - "type": "runnable", - "data": { - "id": [ - "langchain_core", - "runnables", - "base", - "RunnableLambda" - ], - "name": "agent_parser" - } - }, - { - "id": 6, + "id": "agent", "type": "runnable", "data": { "id": [ "langchain", "schema", "runnable", - "RunnablePassthrough" + "RunnableAssign" ], - "name": "Passthrough" + "name": "agent" } }, { @@ -1600,46 +1702,22 @@ } ], "edges": [ - { - "source": 3, - "target": 4 - }, - { - "source": 4, - "target": 5 - }, - { - "source": 1, - "target": 3 - }, - { - "source": 5, - "target": 2 - }, - { - "source": 1, - "target": 6 - }, - { - "source": 6, - "target": 2 - }, { "source": "__start__", - "target": 1 + "target": "agent" }, { "source": "tools", - "target": 1 + "target": "agent" }, { - "source": 2, + "source": "agent", "target": "tools", "data": "continue", "conditional": true }, { - "source": 2, + "source": "agent", "target": "__end__", "data": "exit", "conditional": true @@ -1651,16 +1729,10 @@ # name: test_conditional_graph[postgres_pipe].4 ''' graph TD; - PromptTemplate --> FakeStreamingListLLM; - FakeStreamingListLLM --> agent_parser; - Parallel_agent_outcome_Input --> PromptTemplate; - agent_parser --> Parallel_agent_outcome_Output; - Parallel_agent_outcome_Input --> Passthrough; - Passthrough --> Parallel_agent_outcome_Output; - __start__ --> Parallel_agent_outcome_Input; - tools --> Parallel_agent_outcome_Input; - Parallel_agent_outcome_Output -.  continue  .-> tools; - Parallel_agent_outcome_Output -.  exit  .-> __end__; + __start__ --> agent; + tools --> agent; + agent -.  continue  .-> tools; + agent -.  exit  .-> __end__; ''' # --- @@ -1868,65 +1940,16 @@ "data": "__start__" }, { - "id": 1, - "type": "schema", - "data": "ParallelInput" - }, - { - "id": 2, - "type": "schema", - "data": "ParallelOutput" - }, - { - "id": 3, - "type": "runnable", - "data": { - "id": [ - "langchain", - "prompts", - "prompt", - "PromptTemplate" - ], - "name": "PromptTemplate" - } - }, - { - "id": 4, - "type": "runnable", - "data": { - "id": [ - "langchain_core", - "language_models", - "fake", - "FakeStreamingListLLM" - ], - "name": "FakeStreamingListLLM" - } - }, - { - "id": 5, - "type": "runnable", - "data": { - "id": [ - "langchain_core", - "runnables", - "base", - "RunnableLambda" - ], - "name": "agent_parser" - } - }, - { - "id": 6, + "id": "agent", "type": "runnable", "data": { "id": [ "langchain", "schema", "runnable", - "RunnablePassthrough" + "RunnableAssign" ], - "name": "Passthrough" + "name": "agent" } }, { @@ -1954,46 +1977,22 @@ } ], "edges": [ - { - "source": 3, - "target": 4 - }, - { - "source": 4, - "target": 5 - }, - { - "source": 1, - "target": 3 - }, - { - "source": 5, - "target": 2 - }, - { - "source": 1, - "target": 6 - }, - { - "source": 6, - "target": 2 - }, { "source": "__start__", - "target": 1 + "target": "agent" }, { "source": "tools", - "target": 1 + "target": "agent" }, { - "source": 2, + "source": "agent", "target": "tools", "data": "continue", "conditional": true }, { - "source": 2, + "source": "agent", "target": "__end__", "data": "exit", "conditional": true @@ -2005,16 +2004,10 @@ # name: test_conditional_graph[postgres_pool].4 ''' graph TD; - PromptTemplate --> FakeStreamingListLLM; - FakeStreamingListLLM --> agent_parser; - Parallel_agent_outcome_Input --> PromptTemplate; - agent_parser --> Parallel_agent_outcome_Output; - Parallel_agent_outcome_Input --> Passthrough; - Passthrough --> Parallel_agent_outcome_Output; - __start__ --> Parallel_agent_outcome_Input; - tools --> Parallel_agent_outcome_Input; - Parallel_agent_outcome_Output -.  continue  .-> tools; - Parallel_agent_outcome_Output -.  exit  .-> __end__; + __start__ --> agent; + tools --> agent; + agent -.  continue  .-> tools; + agent -.  exit  .-> __end__; ''' # --- @@ -2222,65 +2215,16 @@ "data": "__start__" }, { - "id": 1, - "type": "schema", - "data": "ParallelInput" - }, - { - "id": 2, - "type": "schema", - "data": "ParallelOutput" - }, - { - "id": 3, - "type": "runnable", - "data": { - "id": [ - "langchain", - "prompts", - "prompt", - "PromptTemplate" - ], - "name": "PromptTemplate" - } - }, - { - "id": 4, - "type": "runnable", - "data": { - "id": [ - "langchain_core", - "language_models", - "fake", - "FakeStreamingListLLM" - ], - "name": "FakeStreamingListLLM" - } - }, - { - "id": 5, - "type": "runnable", - "data": { - "id": [ - "langchain_core", - "runnables", - "base", - "RunnableLambda" - ], - "name": "agent_parser" - } - }, - { - "id": 6, + "id": "agent", "type": "runnable", "data": { "id": [ "langchain", "schema", "runnable", - "RunnablePassthrough" + "RunnableAssign" ], - "name": "Passthrough" + "name": "agent" } }, { @@ -2308,46 +2252,22 @@ } ], "edges": [ - { - "source": 3, - "target": 4 - }, - { - "source": 4, - "target": 5 - }, - { - "source": 1, - "target": 3 - }, - { - "source": 5, - "target": 2 - }, - { - "source": 1, - "target": 6 - }, - { - "source": 6, - "target": 2 - }, { "source": "__start__", - "target": 1 + "target": "agent" }, { "source": "tools", - "target": 1 + "target": "agent" }, { - "source": 2, + "source": "agent", "target": "tools", "data": "continue", "conditional": true }, { - "source": 2, + "source": "agent", "target": "__end__", "data": "exit", "conditional": true @@ -2359,16 +2279,10 @@ # name: test_conditional_graph[sqlite].4 ''' graph TD; - PromptTemplate --> FakeStreamingListLLM; - FakeStreamingListLLM --> agent_parser; - Parallel_agent_outcome_Input --> PromptTemplate; - agent_parser --> Parallel_agent_outcome_Output; - Parallel_agent_outcome_Input --> Passthrough; - Passthrough --> Parallel_agent_outcome_Output; - __start__ --> Parallel_agent_outcome_Input; - tools --> Parallel_agent_outcome_Input; - Parallel_agent_outcome_Output -.  continue  .-> tools; - Parallel_agent_outcome_Output -.  exit  .-> __end__; + __start__ --> agent; + tools --> agent; + agent -.  continue  .-> tools; + agent -.  exit  .-> __end__; ''' # --- @@ -2543,6 +2457,88 @@ ''' # --- +# name: test_conditional_state_graph[duckdb] + '{"$defs": {"AgentAction": {"description": "Represents a request to execute an action by an agent.\\n\\nThe action consists of the name of the tool to execute and the input to pass\\nto the tool. The log is used to pass along extra information about the action.", "properties": {"tool": {"title": "Tool", "type": "string"}, "tool_input": {"anyOf": [{"type": "string"}, {"type": "object"}], "title": "Tool Input"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentAction", "default": "AgentAction", "enum": ["AgentAction"], "title": "Type", "type": "string"}}, "required": ["tool", "tool_input", "log"], "title": "AgentAction", "type": "object"}, "AgentFinish": {"description": "Final return value of an ActionAgent.\\n\\nAgents return an AgentFinish when they have reached a stopping condition.", "properties": {"return_values": {"title": "Return Values", "type": "object"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentFinish", "default": "AgentFinish", "enum": ["AgentFinish"], "title": "Type", "type": "string"}}, "required": ["return_values", "log"], "title": "AgentFinish", "type": "object"}}, "properties": {"input": {"default": null, "title": "Input", "type": "string"}, "agent_outcome": {"anyOf": [{"$ref": "#/$defs/AgentAction"}, {"$ref": "#/$defs/AgentFinish"}, {"type": "null"}], "default": null, "title": "Agent Outcome"}, "intermediate_steps": {"default": null, "items": {"maxItems": 2, "minItems": 2, "prefixItems": [{"$ref": "#/$defs/AgentAction"}, {"type": "string"}], "type": "array"}, "title": "Intermediate Steps", "type": "array"}}, "title": "LangGraphInput", "type": "object"}' +# --- +# name: test_conditional_state_graph[duckdb].1 + '{"$defs": {"AgentAction": {"description": "Represents a request to execute an action by an agent.\\n\\nThe action consists of the name of the tool to execute and the input to pass\\nto the tool. The log is used to pass along extra information about the action.", "properties": {"tool": {"title": "Tool", "type": "string"}, "tool_input": {"anyOf": [{"type": "string"}, {"type": "object"}], "title": "Tool Input"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentAction", "default": "AgentAction", "enum": ["AgentAction"], "title": "Type", "type": "string"}}, "required": ["tool", "tool_input", "log"], "title": "AgentAction", "type": "object"}, "AgentFinish": {"description": "Final return value of an ActionAgent.\\n\\nAgents return an AgentFinish when they have reached a stopping condition.", "properties": {"return_values": {"title": "Return Values", "type": "object"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentFinish", "default": "AgentFinish", "enum": ["AgentFinish"], "title": "Type", "type": "string"}}, "required": ["return_values", "log"], "title": "AgentFinish", "type": "object"}}, "properties": {"input": {"default": null, "title": "Input", "type": "string"}, "agent_outcome": {"anyOf": [{"$ref": "#/$defs/AgentAction"}, {"$ref": "#/$defs/AgentFinish"}, {"type": "null"}], "default": null, "title": "Agent Outcome"}, "intermediate_steps": {"default": null, "items": {"maxItems": 2, "minItems": 2, "prefixItems": [{"$ref": "#/$defs/AgentAction"}, {"type": "string"}], "type": "array"}, "title": "Intermediate Steps", "type": "array"}}, "title": "LangGraphOutput", "type": "object"}' +# --- +# name: test_conditional_state_graph[duckdb].2 + ''' + { + "nodes": [ + { + "id": "__start__", + "type": "schema", + "data": "__start__" + }, + { + "id": "agent", + "type": "runnable", + "data": { + "id": [ + "langchain", + "schema", + "runnable", + "RunnableSequence" + ], + "name": "agent" + } + }, + { + "id": "tools", + "type": "runnable", + "data": { + "id": [ + "langgraph", + "utils", + "runnable", + "RunnableCallable" + ], + "name": "tools" + } + }, + { + "id": "__end__", + "type": "schema", + "data": "__end__" + } + ], + "edges": [ + { + "source": "__start__", + "target": "agent" + }, + { + "source": "tools", + "target": "agent" + }, + { + "source": "agent", + "target": "tools", + "data": "continue", + "conditional": true + }, + { + "source": "agent", + "target": "__end__", + "data": "exit", + "conditional": true + } + ] + } + ''' +# --- +# name: test_conditional_state_graph[duckdb].3 + ''' + graph TD; + __start__ --> agent; + tools --> agent; + agent -.  continue  .-> tools; + agent -.  exit  .-> __end__; + + ''' +# --- # name: test_conditional_state_graph[memory] '{"$defs": {"AgentAction": {"description": "Represents a request to execute an action by an agent.\\n\\nThe action consists of the name of the tool to execute and the input to pass\\nto the tool. The log is used to pass along extra information about the action.", "properties": {"tool": {"title": "Tool", "type": "string"}, "tool_input": {"anyOf": [{"type": "string"}, {"type": "object"}], "title": "Tool Input"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentAction", "default": "AgentAction", "enum": ["AgentAction"], "title": "Type", "type": "string"}}, "required": ["tool", "tool_input", "log"], "title": "AgentAction", "type": "object"}, "AgentFinish": {"description": "Final return value of an ActionAgent.\\n\\nAgents return an AgentFinish when they have reached a stopping condition.", "properties": {"return_values": {"title": "Return Values", "type": "object"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentFinish", "default": "AgentFinish", "enum": ["AgentFinish"], "title": "Type", "type": "string"}}, "required": ["return_values", "log"], "title": "AgentFinish", "type": "object"}}, "properties": {"input": {"default": null, "title": "Input", "type": "string"}, "agent_outcome": {"anyOf": [{"$ref": "#/$defs/AgentAction"}, {"$ref": "#/$defs/AgentFinish"}, {"type": "null"}], "default": null, "title": "Agent Outcome"}, "intermediate_steps": {"default": null, "items": {"maxItems": 2, "minItems": 2, "prefixItems": [{"$ref": "#/$defs/AgentAction"}, {"type": "string"}], "type": "array"}, "title": "Intermediate Steps", "type": "array"}}, "title": "LangGraphInput", "type": "object"}' # --- @@ -3043,7 +3039,7 @@ ''' # --- -# name: test_in_one_fan_out_state_graph_waiting_edge +# name: test_in_one_fan_out_state_graph_waiting_edge[duckdb] ''' graph TD; __start__ --> rewrite_query; @@ -3134,19 +3130,6 @@ ''' # --- -# name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic1 - ''' - graph TD; - __start__ --> rewrite_query; - analyzer_one --> retriever_one; - qa --> __end__; - retriever_one --> qa; - retriever_two --> qa; - rewrite_query --> analyzer_one; - rewrite_query -.-> retriever_two; - - ''' -# --- # name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic1.1 dict({ 'definitions': dict({ @@ -3204,6 +3187,76 @@ 'type': 'object', }) # --- +# name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic1[duckdb] + ''' + graph TD; + __start__ --> rewrite_query; + analyzer_one --> retriever_one; + qa --> __end__; + retriever_one --> qa; + retriever_two --> qa; + rewrite_query --> analyzer_one; + rewrite_query -.-> retriever_two; + + ''' +# --- +# name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic1[duckdb].1 + dict({ + 'definitions': dict({ + 'InnerObject': dict({ + 'properties': dict({ + 'yo': dict({ + 'title': 'Yo', + 'type': 'integer', + }), + }), + 'required': list([ + 'yo', + ]), + 'title': 'InnerObject', + 'type': 'object', + }), + }), + 'properties': dict({ + 'inner': dict({ + '$ref': '#/definitions/InnerObject', + }), + 'query': dict({ + 'title': 'Query', + 'type': 'string', + }), + }), + 'required': list([ + 'query', + 'inner', + ]), + 'title': 'Input', + 'type': 'object', + }) +# --- +# name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic1[duckdb].2 + dict({ + 'properties': dict({ + 'answer': dict({ + 'title': 'Answer', + 'type': 'string', + }), + 'docs': dict({ + 'items': dict({ + 'type': 'string', + }), + 'title': 'Docs', + 'type': 'array', + }), + }), + 'required': list([ + 'answer', + 'docs', + ]), + 'title': 'Output', + 'type': 'object', + }) +# --- # name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic1[memory] ''' graph TD; @@ -3554,19 +3607,6 @@ 'type': 'object', }) # --- -# name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2 - ''' - graph TD; - __start__ --> rewrite_query; - analyzer_one --> retriever_one; - qa --> __end__; - retriever_one --> qa; - retriever_two --> qa; - rewrite_query --> analyzer_one; - rewrite_query -.-> retriever_two; - - ''' -# --- # name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2.1 dict({ '$defs': dict({ @@ -3624,6 +3664,76 @@ 'type': 'object', }) # --- +# name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2[duckdb] + ''' + graph TD; + __start__ --> rewrite_query; + analyzer_one --> retriever_one; + qa --> __end__; + retriever_one --> qa; + retriever_two --> qa; + rewrite_query --> analyzer_one; + rewrite_query -.-> retriever_two; + + ''' +# --- +# name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2[duckdb].1 + dict({ + '$defs': dict({ + 'InnerObject': dict({ + 'properties': dict({ + 'yo': dict({ + 'title': 'Yo', + 'type': 'integer', + }), + }), + 'required': list([ + 'yo', + ]), + 'title': 'InnerObject', + 'type': 'object', + }), + }), + 'properties': dict({ + 'inner': dict({ + '$ref': '#/$defs/InnerObject', + }), + 'query': dict({ + 'title': 'Query', + 'type': 'string', + }), + }), + 'required': list([ + 'query', + 'inner', + ]), + 'title': 'Input', + 'type': 'object', + }) +# --- +# name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2[duckdb].2 + dict({ + 'properties': dict({ + 'answer': dict({ + 'title': 'Answer', + 'type': 'string', + }), + 'docs': dict({ + 'items': dict({ + 'type': 'string', + }), + 'title': 'Docs', + 'type': 'array', + }), + }), + 'required': list([ + 'answer', + 'docs', + ]), + 'title': 'Output', + 'type': 'object', + }) +# --- # name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2[memory] ''' graph TD; @@ -3974,7 +4084,7 @@ 'type': 'object', }) # --- -# name: test_in_one_fan_out_state_graph_waiting_edge_via_branch +# name: test_in_one_fan_out_state_graph_waiting_edge_via_branch[duckdb] ''' graph TD; __start__ --> rewrite_query; @@ -4130,11 +4240,92 @@ ''' # --- +# name: test_message_graph[duckdb] + '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InputTokenDetails": {"description": "Breakdown of input token counts.\\n\\nDoes *not* need to sum to full input token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "cache_creation": {"title": "Cache Creation", "type": "integer"}, "cache_read": {"title": "Cache Read", "type": "integer"}}, "title": "InputTokenDetails", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "OutputTokenDetails": {"description": "Breakdown of output token counts.\\n\\nDoes *not* need to sum to full output token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "reasoning": {"title": "Reasoning", "type": "integer"}}, "title": "OutputTokenDetails", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 350,\\n \\"output_tokens\\": 240,\\n \\"total_tokens\\": 590,\\n \\"input_token_details\\": {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n },\\n \\"output_token_details\\": {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n }\\n\\n.. versionchanged:: 0.3.9\\n\\n Added ``input_token_details`` and ``output_token_details``.", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}, "input_token_details": {"$ref": "#/$defs/InputTokenDetails"}, "output_token_details": {"$ref": "#/$defs/OutputTokenDetails"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphInput", "type": "array"}' +# --- +# name: test_message_graph[duckdb].1 + '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InputTokenDetails": {"description": "Breakdown of input token counts.\\n\\nDoes *not* need to sum to full input token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "cache_creation": {"title": "Cache Creation", "type": "integer"}, "cache_read": {"title": "Cache Read", "type": "integer"}}, "title": "InputTokenDetails", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "OutputTokenDetails": {"description": "Breakdown of output token counts.\\n\\nDoes *not* need to sum to full output token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "reasoning": {"title": "Reasoning", "type": "integer"}}, "title": "OutputTokenDetails", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 350,\\n \\"output_tokens\\": 240,\\n \\"total_tokens\\": 590,\\n \\"input_token_details\\": {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n },\\n \\"output_token_details\\": {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n }\\n\\n.. versionchanged:: 0.3.9\\n\\n Added ``input_token_details`` and ``output_token_details``.", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}, "input_token_details": {"$ref": "#/$defs/InputTokenDetails"}, "output_token_details": {"$ref": "#/$defs/OutputTokenDetails"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphOutput", "type": "array"}' +# --- +# name: test_message_graph[duckdb].2 + ''' + { + "nodes": [ + { + "id": "__start__", + "type": "schema", + "data": "__start__" + }, + { + "id": "agent", + "type": "runnable", + "data": { + "id": [ + "tests", + "test_pregel", + "FakeFuntionChatModel" + ], + "name": "agent" + } + }, + { + "id": "tools", + "type": "runnable", + "data": { + "id": [ + "langgraph", + "prebuilt", + "tool_node", + "ToolNode" + ], + "name": "tools" + } + }, + { + "id": "__end__", + "type": "schema", + "data": "__end__" + } + ], + "edges": [ + { + "source": "__start__", + "target": "agent" + }, + { + "source": "tools", + "target": "agent" + }, + { + "source": "agent", + "target": "tools", + "data": "continue", + "conditional": true + }, + { + "source": "agent", + "target": "__end__", + "data": "end", + "conditional": true + } + ] + } + ''' +# --- +# name: test_message_graph[duckdb].3 + ''' + graph TD; + __start__ --> agent; + tools --> agent; + agent -.  continue  .-> tools; + agent -.  end  .-> __end__; + + ''' +# --- # name: test_message_graph[memory] - '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 10,\\n \\"output_tokens\\": 20,\\n \\"total_tokens\\": 30\\n }", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphInput", "type": "array"}' + '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InputTokenDetails": {"description": "Breakdown of input token counts.\\n\\nDoes *not* need to sum to full input token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "cache_creation": {"title": "Cache Creation", "type": "integer"}, "cache_read": {"title": "Cache Read", "type": "integer"}}, "title": "InputTokenDetails", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "OutputTokenDetails": {"description": "Breakdown of output token counts.\\n\\nDoes *not* need to sum to full output token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "reasoning": {"title": "Reasoning", "type": "integer"}}, "title": "OutputTokenDetails", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 350,\\n \\"output_tokens\\": 240,\\n \\"total_tokens\\": 590,\\n \\"input_token_details\\": {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n },\\n \\"output_token_details\\": {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n }\\n\\n.. versionchanged:: 0.3.9\\n\\n Added ``input_token_details`` and ``output_token_details``.", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}, "input_token_details": {"$ref": "#/$defs/InputTokenDetails"}, "output_token_details": {"$ref": "#/$defs/OutputTokenDetails"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphInput", "type": "array"}' # --- # name: test_message_graph[memory].1 - '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 10,\\n \\"output_tokens\\": 20,\\n \\"total_tokens\\": 30\\n }", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphOutput", "type": "array"}' + '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InputTokenDetails": {"description": "Breakdown of input token counts.\\n\\nDoes *not* need to sum to full input token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "cache_creation": {"title": "Cache Creation", "type": "integer"}, "cache_read": {"title": "Cache Read", "type": "integer"}}, "title": "InputTokenDetails", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "OutputTokenDetails": {"description": "Breakdown of output token counts.\\n\\nDoes *not* need to sum to full output token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "reasoning": {"title": "Reasoning", "type": "integer"}}, "title": "OutputTokenDetails", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 350,\\n \\"output_tokens\\": 240,\\n \\"total_tokens\\": 590,\\n \\"input_token_details\\": {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n },\\n \\"output_token_details\\": {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n }\\n\\n.. versionchanged:: 0.3.9\\n\\n Added ``input_token_details`` and ``output_token_details``.", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}, "input_token_details": {"$ref": "#/$defs/InputTokenDetails"}, "output_token_details": {"$ref": "#/$defs/OutputTokenDetails"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphOutput", "type": "array"}' # --- # name: test_message_graph[memory].2 ''' @@ -4212,10 +4403,10 @@ ''' # --- # name: test_message_graph[postgres] - '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 10,\\n \\"output_tokens\\": 20,\\n \\"total_tokens\\": 30\\n }", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphInput", "type": "array"}' + '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InputTokenDetails": {"description": "Breakdown of input token counts.\\n\\nDoes *not* need to sum to full input token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "cache_creation": {"title": "Cache Creation", "type": "integer"}, "cache_read": {"title": "Cache Read", "type": "integer"}}, "title": "InputTokenDetails", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "OutputTokenDetails": {"description": "Breakdown of output token counts.\\n\\nDoes *not* need to sum to full output token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "reasoning": {"title": "Reasoning", "type": "integer"}}, "title": "OutputTokenDetails", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 350,\\n \\"output_tokens\\": 240,\\n \\"total_tokens\\": 590,\\n \\"input_token_details\\": {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n },\\n \\"output_token_details\\": {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n }\\n\\n.. versionchanged:: 0.3.9\\n\\n Added ``input_token_details`` and ``output_token_details``.", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}, "input_token_details": {"$ref": "#/$defs/InputTokenDetails"}, "output_token_details": {"$ref": "#/$defs/OutputTokenDetails"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphInput", "type": "array"}' # --- # name: test_message_graph[postgres].1 - '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 10,\\n \\"output_tokens\\": 20,\\n \\"total_tokens\\": 30\\n }", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphOutput", "type": "array"}' + '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InputTokenDetails": {"description": "Breakdown of input token counts.\\n\\nDoes *not* need to sum to full input token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "cache_creation": {"title": "Cache Creation", "type": "integer"}, "cache_read": {"title": "Cache Read", "type": "integer"}}, "title": "InputTokenDetails", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "OutputTokenDetails": {"description": "Breakdown of output token counts.\\n\\nDoes *not* need to sum to full output token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "reasoning": {"title": "Reasoning", "type": "integer"}}, "title": "OutputTokenDetails", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 350,\\n \\"output_tokens\\": 240,\\n \\"total_tokens\\": 590,\\n \\"input_token_details\\": {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n },\\n \\"output_token_details\\": {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n }\\n\\n.. versionchanged:: 0.3.9\\n\\n Added ``input_token_details`` and ``output_token_details``.", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}, "input_token_details": {"$ref": "#/$defs/InputTokenDetails"}, "output_token_details": {"$ref": "#/$defs/OutputTokenDetails"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphOutput", "type": "array"}' # --- # name: test_message_graph[postgres].2 ''' @@ -4293,10 +4484,10 @@ ''' # --- # name: test_message_graph[postgres_pipe] - '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 10,\\n \\"output_tokens\\": 20,\\n \\"total_tokens\\": 30\\n }", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphInput", "type": "array"}' + '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InputTokenDetails": {"description": "Breakdown of input token counts.\\n\\nDoes *not* need to sum to full input token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "cache_creation": {"title": "Cache Creation", "type": "integer"}, "cache_read": {"title": "Cache Read", "type": "integer"}}, "title": "InputTokenDetails", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "OutputTokenDetails": {"description": "Breakdown of output token counts.\\n\\nDoes *not* need to sum to full output token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "reasoning": {"title": "Reasoning", "type": "integer"}}, "title": "OutputTokenDetails", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 350,\\n \\"output_tokens\\": 240,\\n \\"total_tokens\\": 590,\\n \\"input_token_details\\": {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n },\\n \\"output_token_details\\": {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n }\\n\\n.. versionchanged:: 0.3.9\\n\\n Added ``input_token_details`` and ``output_token_details``.", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}, "input_token_details": {"$ref": "#/$defs/InputTokenDetails"}, "output_token_details": {"$ref": "#/$defs/OutputTokenDetails"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphInput", "type": "array"}' # --- # name: test_message_graph[postgres_pipe].1 - '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 10,\\n \\"output_tokens\\": 20,\\n \\"total_tokens\\": 30\\n }", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphOutput", "type": "array"}' + '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InputTokenDetails": {"description": "Breakdown of input token counts.\\n\\nDoes *not* need to sum to full input token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "cache_creation": {"title": "Cache Creation", "type": "integer"}, "cache_read": {"title": "Cache Read", "type": "integer"}}, "title": "InputTokenDetails", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "OutputTokenDetails": {"description": "Breakdown of output token counts.\\n\\nDoes *not* need to sum to full output token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "reasoning": {"title": "Reasoning", "type": "integer"}}, "title": "OutputTokenDetails", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 350,\\n \\"output_tokens\\": 240,\\n \\"total_tokens\\": 590,\\n \\"input_token_details\\": {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n },\\n \\"output_token_details\\": {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n }\\n\\n.. versionchanged:: 0.3.9\\n\\n Added ``input_token_details`` and ``output_token_details``.", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}, "input_token_details": {"$ref": "#/$defs/InputTokenDetails"}, "output_token_details": {"$ref": "#/$defs/OutputTokenDetails"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphOutput", "type": "array"}' # --- # name: test_message_graph[postgres_pipe].2 ''' @@ -4374,10 +4565,10 @@ ''' # --- # name: test_message_graph[postgres_pool] - '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 10,\\n \\"output_tokens\\": 20,\\n \\"total_tokens\\": 30\\n }", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphInput", "type": "array"}' + '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InputTokenDetails": {"description": "Breakdown of input token counts.\\n\\nDoes *not* need to sum to full input token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "cache_creation": {"title": "Cache Creation", "type": "integer"}, "cache_read": {"title": "Cache Read", "type": "integer"}}, "title": "InputTokenDetails", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "OutputTokenDetails": {"description": "Breakdown of output token counts.\\n\\nDoes *not* need to sum to full output token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "reasoning": {"title": "Reasoning", "type": "integer"}}, "title": "OutputTokenDetails", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 350,\\n \\"output_tokens\\": 240,\\n \\"total_tokens\\": 590,\\n \\"input_token_details\\": {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n },\\n \\"output_token_details\\": {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n }\\n\\n.. versionchanged:: 0.3.9\\n\\n Added ``input_token_details`` and ``output_token_details``.", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}, "input_token_details": {"$ref": "#/$defs/InputTokenDetails"}, "output_token_details": {"$ref": "#/$defs/OutputTokenDetails"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphInput", "type": "array"}' # --- # name: test_message_graph[postgres_pool].1 - '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 10,\\n \\"output_tokens\\": 20,\\n \\"total_tokens\\": 30\\n }", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphOutput", "type": "array"}' + '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InputTokenDetails": {"description": "Breakdown of input token counts.\\n\\nDoes *not* need to sum to full input token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "cache_creation": {"title": "Cache Creation", "type": "integer"}, "cache_read": {"title": "Cache Read", "type": "integer"}}, "title": "InputTokenDetails", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "OutputTokenDetails": {"description": "Breakdown of output token counts.\\n\\nDoes *not* need to sum to full output token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "reasoning": {"title": "Reasoning", "type": "integer"}}, "title": "OutputTokenDetails", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 350,\\n \\"output_tokens\\": 240,\\n \\"total_tokens\\": 590,\\n \\"input_token_details\\": {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n },\\n \\"output_token_details\\": {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n }\\n\\n.. versionchanged:: 0.3.9\\n\\n Added ``input_token_details`` and ``output_token_details``.", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}, "input_token_details": {"$ref": "#/$defs/InputTokenDetails"}, "output_token_details": {"$ref": "#/$defs/OutputTokenDetails"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphOutput", "type": "array"}' # --- # name: test_message_graph[postgres_pool].2 ''' @@ -4455,10 +4646,10 @@ ''' # --- # name: test_message_graph[sqlite] - '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 10,\\n \\"output_tokens\\": 20,\\n \\"total_tokens\\": 30\\n }", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphInput", "type": "array"}' + '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InputTokenDetails": {"description": "Breakdown of input token counts.\\n\\nDoes *not* need to sum to full input token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "cache_creation": {"title": "Cache Creation", "type": "integer"}, "cache_read": {"title": "Cache Read", "type": "integer"}}, "title": "InputTokenDetails", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "OutputTokenDetails": {"description": "Breakdown of output token counts.\\n\\nDoes *not* need to sum to full output token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "reasoning": {"title": "Reasoning", "type": "integer"}}, "title": "OutputTokenDetails", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 350,\\n \\"output_tokens\\": 240,\\n \\"total_tokens\\": 590,\\n \\"input_token_details\\": {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n },\\n \\"output_token_details\\": {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n }\\n\\n.. versionchanged:: 0.3.9\\n\\n Added ``input_token_details`` and ``output_token_details``.", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}, "input_token_details": {"$ref": "#/$defs/InputTokenDetails"}, "output_token_details": {"$ref": "#/$defs/OutputTokenDetails"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphInput", "type": "array"}' # --- # name: test_message_graph[sqlite].1 - '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 10,\\n \\"output_tokens\\": 20,\\n \\"total_tokens\\": 30\\n }", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphOutput", "type": "array"}' + '{"$defs": {"AIMessage": {"additionalProperties": true, "description": "Message from an AI.\\n\\nAIMessage is returned from a chat model as a response to a prompt.\\n\\nThis message represents the output of the model and consists of both\\nthe raw output as returned by the model together standardized fields\\n(e.g., tool calls, usage metadata) added by the LangChain framework.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ai", "default": "ai", "enum": ["ai"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}}, "required": ["content"], "title": "AIMessage", "type": "object"}, "AIMessageChunk": {"additionalProperties": true, "description": "Message chunk from an AI.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "AIMessageChunk", "default": "AIMessageChunk", "enum": ["AIMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}, "tool_calls": {"default": [], "items": {"$ref": "#/$defs/ToolCall"}, "title": "Tool Calls", "type": "array"}, "invalid_tool_calls": {"default": [], "items": {"$ref": "#/$defs/InvalidToolCall"}, "title": "Invalid Tool Calls", "type": "array"}, "usage_metadata": {"anyOf": [{"$ref": "#/$defs/UsageMetadata"}, {"type": "null"}], "default": null}, "tool_call_chunks": {"default": [], "items": {"$ref": "#/$defs/ToolCallChunk"}, "title": "Tool Call Chunks", "type": "array"}}, "required": ["content"], "title": "AIMessageChunk", "type": "object"}, "ChatMessage": {"additionalProperties": true, "description": "Message that can be assigned an arbitrary speaker (i.e. role).", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "chat", "default": "chat", "enum": ["chat"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessage", "type": "object"}, "ChatMessageChunk": {"additionalProperties": true, "description": "Chat Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ChatMessageChunk", "default": "ChatMessageChunk", "enum": ["ChatMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "role": {"title": "Role", "type": "string"}}, "required": ["content", "role"], "title": "ChatMessageChunk", "type": "object"}, "FunctionMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nFunctionMessage are an older version of the ToolMessage schema, and\\ndo not contain the tool_call_id field.\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "function", "default": "function", "enum": ["function"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessage", "type": "object"}, "FunctionMessageChunk": {"additionalProperties": true, "description": "Function Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "FunctionMessageChunk", "default": "FunctionMessageChunk", "enum": ["FunctionMessageChunk"], "title": "Type", "type": "string"}, "name": {"title": "Name", "type": "string"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content", "name"], "title": "FunctionMessageChunk", "type": "object"}, "HumanMessage": {"additionalProperties": true, "description": "Message from a human.\\n\\nHumanMessages are messages that are passed in from a human to the model.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Instantiate a chat model and invoke it with the messages\\n model = ...\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "human", "default": "human", "enum": ["human"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessage", "type": "object"}, "HumanMessageChunk": {"additionalProperties": true, "description": "Human Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "HumanMessageChunk", "default": "HumanMessageChunk", "enum": ["HumanMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "example": {"default": false, "title": "Example", "type": "boolean"}}, "required": ["content"], "title": "HumanMessageChunk", "type": "object"}, "InputTokenDetails": {"description": "Breakdown of input token counts.\\n\\nDoes *not* need to sum to full input token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "cache_creation": {"title": "Cache Creation", "type": "integer"}, "cache_read": {"title": "Cache Read", "type": "integer"}}, "title": "InputTokenDetails", "type": "object"}, "InvalidToolCall": {"description": "Allowance for errors made by LLM.\\n\\nHere we add an `error` key to surface errors made during generation\\n(e.g., invalid JSON arguments.)", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "error": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Error"}, "type": {"const": "invalid_tool_call", "enum": ["invalid_tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "error"], "title": "InvalidToolCall", "type": "object"}, "OutputTokenDetails": {"description": "Breakdown of output token counts.\\n\\nDoes *not* need to sum to full output token count. Does *not* need to have all keys.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n\\n.. versionadded:: 0.3.9", "properties": {"audio": {"title": "Audio", "type": "integer"}, "reasoning": {"title": "Reasoning", "type": "integer"}}, "title": "OutputTokenDetails", "type": "object"}, "SystemMessage": {"additionalProperties": true, "description": "Message for priming AI behavior.\\n\\nThe system message is usually passed in as the first of a sequence\\nof input messages.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import HumanMessage, SystemMessage\\n\\n messages = [\\n SystemMessage(\\n content=\\"You are a helpful assistant! Your name is Bob.\\"\\n ),\\n HumanMessage(\\n content=\\"What is your name?\\"\\n )\\n ]\\n\\n # Define a chat model and invoke it with the messages\\n print(model.invoke(messages))", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "system", "default": "system", "enum": ["system"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessage", "type": "object"}, "SystemMessageChunk": {"additionalProperties": true, "description": "System Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "SystemMessageChunk", "default": "SystemMessageChunk", "enum": ["SystemMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}}, "required": ["content"], "title": "SystemMessageChunk", "type": "object"}, "ToolCall": {"description": "Represents a request to call a tool.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"name\\": \\"foo\\",\\n \\"args\\": {\\"a\\": 1},\\n \\"id\\": \\"123\\"\\n }\\n\\n This represents a request to call the tool named \\"foo\\" with arguments {\\"a\\": 1}\\n and an identifier of \\"123\\".", "properties": {"name": {"title": "Name", "type": "string"}, "args": {"title": "Args", "type": "object"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "type": {"const": "tool_call", "enum": ["tool_call"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id"], "title": "ToolCall", "type": "object"}, "ToolCallChunk": {"description": "A chunk of a tool call (e.g., as part of a stream).\\n\\nWhen merging ToolCallChunks (e.g., via AIMessageChunk.__add__),\\nall string attributes are concatenated. Chunks are only merged if their\\nvalues of `index` are equal and not None.\\n\\nExample:\\n\\n.. code-block:: python\\n\\n left_chunks = [ToolCallChunk(name=\\"foo\\", args=\'{\\"a\\":\', index=0)]\\n right_chunks = [ToolCallChunk(name=None, args=\'1}\', index=0)]\\n\\n (\\n AIMessageChunk(content=\\"\\", tool_call_chunks=left_chunks)\\n + AIMessageChunk(content=\\"\\", tool_call_chunks=right_chunks)\\n ).tool_call_chunks == [ToolCallChunk(name=\'foo\', args=\'{\\"a\\":1}\', index=0)]", "properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "args": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Args"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Id"}, "index": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Index"}, "type": {"const": "tool_call_chunk", "enum": ["tool_call_chunk"], "title": "Type", "type": "string"}}, "required": ["name", "args", "id", "index"], "title": "ToolCallChunk", "type": "object"}, "ToolMessage": {"additionalProperties": true, "description": "Message for passing the result of executing a tool back to a model.\\n\\nToolMessages contain the result of a tool invocation. Typically, the result\\nis encoded inside the `content` field.\\n\\nExample: A ToolMessage representing a result of 42 from a tool call with id\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n ToolMessage(content=\'42\', tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\')\\n\\n\\nExample: A ToolMessage where only part of the tool output is sent to the model\\n and the full output is passed in to artifact.\\n\\n .. versionadded:: 0.2.17\\n\\n .. code-block:: python\\n\\n from langchain_core.messages import ToolMessage\\n\\n tool_output = {\\n \\"stdout\\": \\"From the graph we can see that the correlation between x and y is ...\\",\\n \\"stderr\\": None,\\n \\"artifacts\\": {\\"type\\": \\"image\\", \\"base64_data\\": \\"/9j/4gIcSU...\\"},\\n }\\n\\n ToolMessage(\\n content=tool_output[\\"stdout\\"],\\n artifact=tool_output,\\n tool_call_id=\'call_Jja7J89XsjrOLA5r!MEOW!SL\',\\n )\\n\\nThe tool_call_id field is used to associate the tool call request with the\\ntool call response. This is useful in situations where a chat model is able\\nto request multiple tool calls in parallel.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "tool", "default": "tool", "enum": ["tool"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessage", "type": "object"}, "ToolMessageChunk": {"additionalProperties": true, "description": "Tool Message chunk.", "properties": {"content": {"anyOf": [{"type": "string"}, {"items": {"anyOf": [{"type": "string"}, {"type": "object"}]}, "type": "array"}], "title": "Content"}, "additional_kwargs": {"title": "Additional Kwargs", "type": "object"}, "response_metadata": {"title": "Response Metadata", "type": "object"}, "type": {"const": "ToolMessageChunk", "default": "ToolMessageChunk", "enum": ["ToolMessageChunk"], "title": "Type", "type": "string"}, "name": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Name"}, "id": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Id"}, "tool_call_id": {"title": "Tool Call Id", "type": "string"}, "artifact": {"default": null, "title": "Artifact"}, "status": {"default": "success", "enum": ["success", "error"], "title": "Status", "type": "string"}}, "required": ["content", "tool_call_id"], "title": "ToolMessageChunk", "type": "object"}, "UsageMetadata": {"description": "Usage metadata for a message, such as token counts.\\n\\nThis is a standard representation of token usage that is consistent across models.\\n\\nExample:\\n\\n .. code-block:: python\\n\\n {\\n \\"input_tokens\\": 350,\\n \\"output_tokens\\": 240,\\n \\"total_tokens\\": 590,\\n \\"input_token_details\\": {\\n \\"audio\\": 10,\\n \\"cache_creation\\": 200,\\n \\"cache_read\\": 100,\\n },\\n \\"output_token_details\\": {\\n \\"audio\\": 10,\\n \\"reasoning\\": 200,\\n }\\n }\\n\\n.. versionchanged:: 0.3.9\\n\\n Added ``input_token_details`` and ``output_token_details``.", "properties": {"input_tokens": {"title": "Input Tokens", "type": "integer"}, "output_tokens": {"title": "Output Tokens", "type": "integer"}, "total_tokens": {"title": "Total Tokens", "type": "integer"}, "input_token_details": {"$ref": "#/$defs/InputTokenDetails"}, "output_token_details": {"$ref": "#/$defs/OutputTokenDetails"}}, "required": ["input_tokens", "output_tokens", "total_tokens"], "title": "UsageMetadata", "type": "object"}}, "default": null, "items": {"oneOf": [{"$ref": "#/$defs/AIMessage"}, {"$ref": "#/$defs/HumanMessage"}, {"$ref": "#/$defs/ChatMessage"}, {"$ref": "#/$defs/SystemMessage"}, {"$ref": "#/$defs/FunctionMessage"}, {"$ref": "#/$defs/ToolMessage"}, {"$ref": "#/$defs/AIMessageChunk"}, {"$ref": "#/$defs/HumanMessageChunk"}, {"$ref": "#/$defs/ChatMessageChunk"}, {"$ref": "#/$defs/SystemMessageChunk"}, {"$ref": "#/$defs/FunctionMessageChunk"}, {"$ref": "#/$defs/ToolMessageChunk"}]}, "title": "LangGraphOutput", "type": "array"}' # --- # name: test_message_graph[sqlite].2 ''' @@ -4917,6 +5108,81 @@ ''' # --- +# name: test_send_react_interrupt_control[memory] + ''' + %%{init: {'flowchart': {'curve': 'linear'}}}%% + graph TD; + __start__([

__start__

]):::first + agent(agent) + foo([foo]):::last + __start__ --> agent; + agent -.-> foo; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc + + ''' +# --- +# name: test_send_react_interrupt_control[postgres] + ''' + %%{init: {'flowchart': {'curve': 'linear'}}}%% + graph TD; + __start__([

__start__

]):::first + agent(agent) + foo([foo]):::last + __start__ --> agent; + agent -.-> foo; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc + + ''' +# --- +# name: test_send_react_interrupt_control[postgres_pipe] + ''' + %%{init: {'flowchart': {'curve': 'linear'}}}%% + graph TD; + __start__([

__start__

]):::first + agent(agent) + foo([foo]):::last + __start__ --> agent; + agent -.-> foo; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc + + ''' +# --- +# name: test_send_react_interrupt_control[postgres_pool] + ''' + %%{init: {'flowchart': {'curve': 'linear'}}}%% + graph TD; + __start__([

__start__

]):::first + agent(agent) + foo([foo]):::last + __start__ --> agent; + agent -.-> foo; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc + + ''' +# --- +# name: test_send_react_interrupt_control[sqlite] + ''' + %%{init: {'flowchart': {'curve': 'linear'}}}%% + graph TD; + __start__([

__start__

]):::first + agent(agent) + foo([foo]):::last + __start__ --> agent; + agent -.-> foo; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc + + ''' +# --- # name: test_simple_multi_edge ''' graph TD; @@ -4929,6 +5195,24 @@ ''' # --- +# name: test_start_branch_then[duckdb] + ''' + %%{init: {'flowchart': {'curve': 'linear'}}}%% + graph TD; + __start__([

__start__

]):::first + tool_two_slow(tool_two_slow) + tool_two_fast(tool_two_fast) + __end__([

__end__

]):::last + __start__ -.-> tool_two_slow; + tool_two_slow --> __end__; + __start__ -.-> tool_two_fast; + tool_two_fast --> __end__; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc + + ''' +# --- # name: test_start_branch_then[memory] ''' %%{init: {'flowchart': {'curve': 'linear'}}}%% @@ -5046,6 +5330,31 @@ # name: test_state_graph_w_config_inherited_state_keys.2 '{"$defs": {"AgentAction": {"description": "Represents a request to execute an action by an agent.\\n\\nThe action consists of the name of the tool to execute and the input to pass\\nto the tool. The log is used to pass along extra information about the action.", "properties": {"tool": {"title": "Tool", "type": "string"}, "tool_input": {"anyOf": [{"type": "string"}, {"type": "object"}], "title": "Tool Input"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentAction", "default": "AgentAction", "enum": ["AgentAction"], "title": "Type", "type": "string"}}, "required": ["tool", "tool_input", "log"], "title": "AgentAction", "type": "object"}, "AgentFinish": {"description": "Final return value of an ActionAgent.\\n\\nAgents return an AgentFinish when they have reached a stopping condition.", "properties": {"return_values": {"title": "Return Values", "type": "object"}, "log": {"title": "Log", "type": "string"}, "type": {"const": "AgentFinish", "default": "AgentFinish", "enum": ["AgentFinish"], "title": "Type", "type": "string"}}, "required": ["return_values", "log"], "title": "AgentFinish", "type": "object"}}, "properties": {"input": {"title": "Input", "type": "string"}, "agent_outcome": {"anyOf": [{"$ref": "#/$defs/AgentAction"}, {"$ref": "#/$defs/AgentFinish"}, {"type": "null"}], "default": null, "title": "Agent Outcome"}, "intermediate_steps": {"default": null, "items": {"maxItems": 2, "minItems": 2, "prefixItems": [{"$ref": "#/$defs/AgentAction"}, {"type": "string"}], "type": "array"}, "title": "Intermediate Steps", "type": "array"}}, "required": ["input"], "title": "LangGraphOutput", "type": "object"}' # --- +# name: test_weather_subgraph[duckdb] + ''' + %%{init: {'flowchart': {'curve': 'linear'}}}%% + graph TD; + __start__([

__start__

]):::first + router_node(router_node) + normal_llm_node(normal_llm_node) + weather_graph_model_node(model_node) + weather_graph_weather_node(weather_node
__interrupt = before) + __end__([

__end__

]):::last + __start__ --> router_node; + normal_llm_node --> __end__; + weather_graph_weather_node --> __end__; + router_node -.-> normal_llm_node; + router_node -.-> weather_graph_model_node; + router_node -.-> __end__; + subgraph weather_graph + weather_graph_model_node --> weather_graph_weather_node; + end + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc + + ''' +# --- # name: test_weather_subgraph[memory] ''' %%{init: {'flowchart': {'curve': 'linear'}}}%% diff --git a/libs/langgraph/tests/__snapshots__/test_pregel_async.ambr b/libs/langgraph/tests/__snapshots__/test_pregel_async.ambr index 0acb4d0a8..46916c7a4 100644 --- a/libs/langgraph/tests/__snapshots__/test_pregel_async.ambr +++ b/libs/langgraph/tests/__snapshots__/test_pregel_async.ambr @@ -221,19 +221,6 @@ +---------+ ''' # --- -# name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2 - ''' - graph TD; - __start__ --> rewrite_query; - analyzer_one --> retriever_one; - qa --> __end__; - retriever_one --> qa; - retriever_two --> qa; - rewrite_query --> analyzer_one; - rewrite_query -.-> retriever_two; - - ''' -# --- # name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2.1 dict({ '$defs': dict({ @@ -342,6 +329,127 @@ 'type': 'object', }) # --- +# name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2[duckdb_aio] + ''' + graph TD; + __start__ --> rewrite_query; + analyzer_one --> retriever_one; + qa --> __end__; + retriever_one --> qa; + retriever_two --> qa; + rewrite_query --> analyzer_one; + rewrite_query -.-> retriever_two; + + ''' +# --- +# name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2[duckdb_aio].1 + dict({ + '$defs': dict({ + 'InnerObject': dict({ + 'properties': dict({ + 'yo': dict({ + 'title': 'Yo', + 'type': 'integer', + }), + }), + 'required': list([ + 'yo', + ]), + 'title': 'InnerObject', + 'type': 'object', + }), + }), + 'properties': dict({ + 'answer': dict({ + 'anyOf': list([ + dict({ + 'type': 'string', + }), + dict({ + 'type': 'null', + }), + ]), + 'default': None, + 'title': 'Answer', + }), + 'docs': dict({ + 'items': dict({ + 'type': 'string', + }), + 'title': 'Docs', + 'type': 'array', + }), + 'inner': dict({ + '$ref': '#/$defs/InnerObject', + }), + 'query': dict({ + 'title': 'Query', + 'type': 'string', + }), + }), + 'required': list([ + 'query', + 'inner', + 'docs', + ]), + 'title': 'State', + 'type': 'object', + }) +# --- +# name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2[duckdb_aio].2 + dict({ + '$defs': dict({ + 'InnerObject': dict({ + 'properties': dict({ + 'yo': dict({ + 'title': 'Yo', + 'type': 'integer', + }), + }), + 'required': list([ + 'yo', + ]), + 'title': 'InnerObject', + 'type': 'object', + }), + }), + 'properties': dict({ + 'answer': dict({ + 'anyOf': list([ + dict({ + 'type': 'string', + }), + dict({ + 'type': 'null', + }), + ]), + 'default': None, + 'title': 'Answer', + }), + 'docs': dict({ + 'items': dict({ + 'type': 'string', + }), + 'title': 'Docs', + 'type': 'array', + }), + 'inner': dict({ + '$ref': '#/$defs/InnerObject', + }), + 'query': dict({ + 'title': 'Query', + 'type': 'string', + }), + }), + 'required': list([ + 'query', + 'inner', + 'docs', + ]), + 'title': 'State', + 'type': 'object', + }) +# --- # name: test_in_one_fan_out_state_graph_waiting_edge_custom_state_class_pydantic2[memory] ''' graph TD; @@ -1194,6 +1302,106 @@ +---------+ ''' # --- +# name: test_send_react_interrupt_control[memory] + ''' + %%{init: {'flowchart': {'curve': 'linear'}}}%% + graph TD; + __start__([

__start__

]):::first + agent(agent) + foo([foo]):::last + __start__ --> agent; + agent -.-> foo; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc + + ''' +# --- +# name: test_send_react_interrupt_control[postgres_aio] + ''' + %%{init: {'flowchart': {'curve': 'linear'}}}%% + graph TD; + __start__([

__start__

]):::first + agent(agent) + foo([foo]):::last + __start__ --> agent; + agent -.-> foo; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc + + ''' +# --- +# name: test_send_react_interrupt_control[postgres_aio_pipe] + ''' + %%{init: {'flowchart': {'curve': 'linear'}}}%% + graph TD; + __start__([

__start__

]):::first + agent(agent) + foo([foo]):::last + __start__ --> agent; + agent -.-> foo; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc + + ''' +# --- +# name: test_send_react_interrupt_control[postgres_aio_pool] + ''' + %%{init: {'flowchart': {'curve': 'linear'}}}%% + graph TD; + __start__([

__start__

]):::first + agent(agent) + foo([foo]):::last + __start__ --> agent; + agent -.-> foo; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc + + ''' +# --- +# name: test_send_react_interrupt_control[sqlite_aio] + ''' + %%{init: {'flowchart': {'curve': 'linear'}}}%% + graph TD; + __start__([

__start__

]):::first + agent(agent) + foo([foo]):::last + __start__ --> agent; + agent -.-> foo; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc + + ''' +# --- +# name: test_weather_subgraph[duckdb_aio] + ''' + %%{init: {'flowchart': {'curve': 'linear'}}}%% + graph TD; + __start__([

__start__

]):::first + router_node(router_node) + normal_llm_node(normal_llm_node) + weather_graph_model_node(model_node) + weather_graph_weather_node(weather_node
__interrupt = before) + __end__([

__end__

]):::last + __start__ --> router_node; + normal_llm_node --> __end__; + weather_graph_weather_node --> __end__; + router_node -.-> normal_llm_node; + router_node -.-> weather_graph_model_node; + router_node -.-> __end__; + subgraph weather_graph + weather_graph_model_node --> weather_graph_weather_node; + end + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc + + ''' +# --- # name: test_weather_subgraph[memory] ''' %%{init: {'flowchart': {'curve': 'linear'}}}%% diff --git a/libs/langgraph/tests/conftest.py b/libs/langgraph/tests/conftest.py index a58923f14..eae7694ff 100644 --- a/libs/langgraph/tests/conftest.py +++ b/libs/langgraph/tests/conftest.py @@ -11,14 +11,18 @@ from psycopg_pool import AsyncConnectionPool, ConnectionPool from pytest_mock import MockerFixture from langgraph.checkpoint.base import BaseCheckpointSaver +from langgraph.checkpoint.duckdb import DuckDBSaver +from langgraph.checkpoint.duckdb.aio import AsyncDuckDBSaver from langgraph.checkpoint.postgres import PostgresSaver from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver from langgraph.checkpoint.sqlite import SqliteSaver from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver from langgraph.store.base import BaseStore +from langgraph.store.duckdb import AsyncDuckDBStore, DuckDBStore from langgraph.store.memory import InMemoryStore from langgraph.store.postgres import AsyncPostgresStore, PostgresStore -from tests.memory_assert import MemorySaverAssertImmutable + +pytest.register_assert_rewrite("tests.memory_assert") DEFAULT_POSTGRES_URI = "postgres://postgres:postgres@localhost:5442/" # TODO: fix this once core is released @@ -46,6 +50,8 @@ def deterministic_uuids(mocker: MockerFixture) -> MockerFixture: @pytest.fixture(scope="function") def checkpointer_memory(): + from tests.memory_assert import MemorySaverAssertImmutable + yield MemorySaverAssertImmutable() @@ -61,6 +67,20 @@ async def _checkpointer_sqlite_aio(): yield checkpointer +@pytest.fixture(scope="function") +def checkpointer_duckdb(): + with DuckDBSaver.from_conn_string(":memory:") as checkpointer: + checkpointer.setup() + yield checkpointer + + +@asynccontextmanager +async def _checkpointer_duckdb_aio(): + async with AsyncDuckDBSaver.from_conn_string(":memory:") as checkpointer: + await checkpointer.setup() + yield checkpointer + + @pytest.fixture(scope="function") def checkpointer_postgres(): database = f"test_{uuid4().hex[:16]}" @@ -208,10 +228,15 @@ async def awith_checkpointer( if checkpointer_name is None: yield None elif checkpointer_name == "memory": + from tests.memory_assert import MemorySaverAssertImmutable + yield MemorySaverAssertImmutable() elif checkpointer_name == "sqlite_aio": async with _checkpointer_sqlite_aio() as checkpointer: yield checkpointer + elif checkpointer_name == "duckdb_aio": + async with _checkpointer_duckdb_aio() as checkpointer: + yield checkpointer elif checkpointer_name == "postgres_aio": async with _checkpointer_postgres_aio() as checkpointer: yield checkpointer @@ -247,6 +272,13 @@ async def _store_postgres_aio(): await conn.execute(f"DROP DATABASE {database}") +@asynccontextmanager +async def _store_duckdb_aio(): + async with AsyncDuckDBStore.from_conn_string(":memory:") as store: + await store.setup() + yield store + + @pytest.fixture(scope="function") def store_postgres(): database = f"test_{uuid4().hex[:16]}" @@ -264,6 +296,13 @@ def store_postgres(): conn.execute(f"DROP DATABASE {database}") +@pytest.fixture(scope="function") +def store_duckdb(): + with DuckDBStore.from_conn_string(":memory:") as store: + store.setup() + yield store + + @pytest.fixture(scope="function") def store_in_memory(): yield InMemoryStore() @@ -278,6 +317,9 @@ async def awith_store(store_name: Optional[str]) -> AsyncIterator[BaseStore]: elif store_name == "postgres_aio": async with _store_postgres_aio() as store: yield store + elif store_name == "duckdb_aio": + async with _store_duckdb_aio() as store: + yield store else: raise NotImplementedError(f"Unknown store {store_name}") @@ -300,5 +342,5 @@ ALL_CHECKPOINTERS_ASYNC_PLUS_NONE = [ *ALL_CHECKPOINTERS_ASYNC, None, ] -ALL_STORES_SYNC = ["in_memory", "postgres"] -ALL_STORES_ASYNC = ["in_memory", "postgres_aio"] +ALL_STORES_SYNC = ["in_memory", "postgres", "duckdb"] +ALL_STORES_ASYNC = ["in_memory", "postgres_aio", "duckdb_aio"] diff --git a/libs/langgraph/tests/memory_assert.py b/libs/langgraph/tests/memory_assert.py index 6b44051f7..0a9f13a47 100644 --- a/libs/langgraph/tests/memory_assert.py +++ b/libs/langgraph/tests/memory_assert.py @@ -1,5 +1,8 @@ import asyncio +import os +import tempfile from collections import defaultdict +from functools import partial from typing import Any, Optional from langchain_core.runnables import RunnableConfig @@ -12,7 +15,7 @@ from langgraph.checkpoint.base import ( SerializerProtocol, copy_checkpoint, ) -from langgraph.checkpoint.memory import MemorySaver +from langgraph.checkpoint.memory import MemorySaver, PersistentDict class NoopSerializer(SerializerProtocol): @@ -32,9 +35,13 @@ class MemorySaverAssertImmutable(MemorySaver): serde: Optional[SerializerProtocol] = None, put_sleep: Optional[float] = None, ) -> None: - super().__init__(serde=serde) + _, filename = tempfile.mkstemp() + super().__init__( + serde=serde, factory=partial(PersistentDict, filename=filename) + ) self.storage_for_copies = defaultdict(lambda: defaultdict(dict)) self.put_sleep = put_sleep + self.stack.callback(os.remove, filename) def put( self, diff --git a/libs/langgraph/tests/test_algo.py b/libs/langgraph/tests/test_algo.py index 4e259f29e..9d6ec5942 100644 --- a/libs/langgraph/tests/test_algo.py +++ b/libs/langgraph/tests/test_algo.py @@ -11,13 +11,21 @@ def test_prepare_next_tasks() -> None: with ChannelsManager({}, checkpoint, config) as (channels, managed): assert ( prepare_next_tasks( - checkpoint, processes, channels, managed, config, 0, for_execution=False + checkpoint, + {}, + processes, + channels, + managed, + config, + 0, + for_execution=False, ) == {} ) assert ( prepare_next_tasks( checkpoint, + {}, processes, channels, managed, diff --git a/libs/langgraph/tests/test_prebuilt.py b/libs/langgraph/tests/test_prebuilt.py index c1176f365..a6655a451 100644 --- a/libs/langgraph/tests/test_prebuilt.py +++ b/libs/langgraph/tests/test_prebuilt.py @@ -1,5 +1,6 @@ import dataclasses import json +from functools import partial from typing import ( Annotated, Any, @@ -28,25 +29,41 @@ from langchain_core.messages import ( ) from langchain_core.outputs import ChatGeneration, ChatResult from langchain_core.runnables import Runnable, RunnableLambda -from langchain_core.tools import BaseTool +from langchain_core.tools import BaseTool, ToolException from langchain_core.tools import tool as dec_tool -from pydantic import BaseModel +from pydantic import BaseModel, ValidationError from pydantic.v1 import BaseModel as BaseModelV1 +from pydantic.v1 import ValidationError as ValidationErrorV1 from typing_extensions import TypedDict from langgraph.checkpoint.base import BaseCheckpointSaver -from langgraph.graph import START, MessagesState, StateGraph -from langgraph.prebuilt import ToolNode, ValidationNode, create_react_agent -from langgraph.prebuilt.tool_node import InjectedState, InjectedStore +from langgraph.checkpoint.memory import MemorySaver +from langgraph.errors import NodeInterrupt +from langgraph.graph import START, MessagesState, StateGraph, add_messages +from langgraph.prebuilt import ( + ToolNode, + ValidationNode, + create_react_agent, + tools_condition, +) +from langgraph.prebuilt.chat_agent_executor import _validate_chat_history +from langgraph.prebuilt.tool_node import ( + TOOL_CALL_ERROR_TEMPLATE, + InjectedState, + InjectedStore, + _get_state_args, + _infer_handled_types, +) from langgraph.store.base import BaseStore from langgraph.store.memory import InMemoryStore +from langgraph.types import Interrupt from tests.conftest import ( ALL_CHECKPOINTERS_ASYNC, ALL_CHECKPOINTERS_SYNC, IS_LANGCHAIN_CORE_030_OR_GREATER, awith_checkpointer, ) -from tests.messages import _AnyIdHumanMessage +from tests.messages import _AnyIdHumanMessage, _AnyIdToolMessage pytestmark = pytest.mark.anyio @@ -141,6 +158,7 @@ def test_no_modifier(request: pytest.FixtureRequest, checkpointer_name: str) -> "source": "loop", "writes": {"agent": {"messages": [AIMessage(content="hi?", id="0")]}}, "step": 1, + "thread_id": "123", } assert saved.pending_writes == [] @@ -172,6 +190,7 @@ async def test_no_modifier_async(checkpointer_name: str) -> None: "source": "loop", "writes": {"agent": {"messages": [AIMessage(content="hi?", id="0")]}}, "step": 1, + "thread_id": "123", } assert saved.pending_writes == [] @@ -362,32 +381,172 @@ def test_model_with_tools(tool_style: str): create_react_agent(model.bind_tools([tool1]), [tool2]) +def test__validate_messages(): + # empty input + _validate_chat_history([]) + + # single human message + _validate_chat_history( + [ + HumanMessage(content="What's the weather?"), + ] + ) + + # human + AI + _validate_chat_history( + [ + HumanMessage(content="What's the weather?"), + AIMessage(content="The weather is sunny and 75°F."), + ] + ) + + # Answered tool calls + _validate_chat_history( + [ + HumanMessage(content="What's the weather?"), + AIMessage( + content="Let me check that for you.", + tool_calls=[{"id": "call1", "name": "get_weather", "args": {}}], + ), + ToolMessage(content="Sunny, 75°F", tool_call_id="call1"), + AIMessage(content="The weather is sunny and 75°F."), + ] + ) + + # Unanswered tool calls + with pytest.raises(ValueError): + _validate_chat_history( + [ + AIMessage( + content="I'll check that for you.", + tool_calls=[ + {"id": "call1", "name": "get_weather", "args": {}}, + {"id": "call2", "name": "get_time", "args": {}}, + ], + ) + ] + ) + + with pytest.raises(ValueError): + _validate_chat_history( + [ + HumanMessage(content="What's the weather and time?"), + AIMessage( + content="I'll check that for you.", + tool_calls=[ + {"id": "call1", "name": "get_weather", "args": {}}, + {"id": "call2", "name": "get_time", "args": {}}, + ], + ), + ToolMessage(content="Sunny, 75°F", tool_call_id="call1"), + AIMessage( + content="The weather is sunny and 75°F. Let me check the time." + ), + ] + ) + + +def test__infer_handled_types() -> None: + def handle(e): # type: ignore + return "" + + def handle2(e: Exception) -> str: + return "" + + def handle3(e: Union[ValueError, ToolException]) -> str: + return "" + + class Handler: + def handle(self, e: ValueError) -> str: + return "" + + handle4 = Handler().handle + + def handle5(e: Union[Union[TypeError, ValueError], ToolException]): + return "" + + expected: tuple = (Exception,) + actual = _infer_handled_types(handle) + assert expected == actual + + expected = (Exception,) + actual = _infer_handled_types(handle2) + assert expected == actual + + expected = (ValueError, ToolException) + actual = _infer_handled_types(handle3) + assert expected == actual + + expected = (ValueError,) + actual = _infer_handled_types(handle4) + assert expected == actual + + expected = (TypeError, ValueError, ToolException) + actual = _infer_handled_types(handle5) + assert expected == actual + + with pytest.raises(ValueError): + + def handler(e: str): + return "" + + _infer_handled_types(handler) + + with pytest.raises(ValueError): + + def handler(e: list[Exception]): + return "" + + _infer_handled_types(handler) + + with pytest.raises(ValueError): + + def handler(e: Union[str, int]): + return "" + + _infer_handled_types(handler) + + +# tools for testing Too +def tool1(some_val: int, some_other_val: str) -> str: + """Tool 1 docstring.""" + if some_val == 0: + raise ValueError("Test error") + return f"{some_val} - {some_other_val}" + + +async def tool2(some_val: int, some_other_val: str) -> str: + """Tool 2 docstring.""" + if some_val == 0: + raise ToolException("Test error") + return f"tool2: {some_val} - {some_other_val}" + + +async def tool3(some_val: int, some_other_val: str) -> str: + """Tool 3 docstring.""" + return [ + {"key_1": some_val, "key_2": "foo"}, + {"key_1": some_other_val, "key_2": "baz"}, + ] + + +async def tool4(some_val: int, some_other_val: str) -> str: + """Tool 4 docstring.""" + return [ + {"type": "image_url", "image_url": {"url": "abdc"}}, + ] + + +@dec_tool +def tool5(some_val: int): + """Tool 5 docstring.""" + raise ToolException("Test error") + + +tool5.handle_tool_error = "foo" + + async def test_tool_node(): - def tool1(some_val: int, some_other_val: str) -> str: - """Tool 1 docstring.""" - if some_val == 0: - raise ValueError("Test error") - return f"{some_val} - {some_other_val}" - - async def tool2(some_val: int, some_other_val: str) -> str: - """Tool 2 docstring.""" - if some_val == 0: - raise ValueError("Test error") - return f"tool2: {some_val} - {some_other_val}" - - async def tool3(some_val: int, some_other_val: str) -> str: - """Tool 3 docstring.""" - return [ - {"key_1": some_val, "key_2": "foo"}, - {"key_1": some_other_val, "key_2": "baz"}, - ] - - async def tool4(some_val: int, some_other_val: str) -> str: - """Tool 4 docstring.""" - return [ - {"type": "image_url", "image_url": {"url": "abdc"}}, - ] - result = ToolNode([tool1]).invoke( { "messages": [ @@ -410,31 +569,6 @@ async def test_tool_node(): assert tool_message.content == "1 - foo" assert tool_message.tool_call_id == "some 0" - result_error = ToolNode([tool1]).invoke( - { - "messages": [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": "tool1", - "args": {"some_val": 0, "some_other_val": "foo"}, - "id": "some 0", - } - ], - ) - ] - } - ) - - tool_message: ToolMessage = result_error["messages"][-1] - assert tool_message.type == "tool" - assert ( - tool_message.content - == f"Error: {repr(ValueError('Test error'))}\n Please fix your mistakes." - ) - assert tool_message.tool_call_id == "some 0" - result2 = await ToolNode([tool2]).ainvoke( { "messages": [ @@ -451,11 +585,232 @@ async def test_tool_node(): ] } ) + tool_message: ToolMessage = result2["messages"][-1] assert tool_message.type == "tool" assert tool_message.content == "tool2: 2 - bar" - with pytest.raises(ValueError): + # list of dicts tool content + result3 = await ToolNode([tool3]).ainvoke( + { + "messages": [ + AIMessage( + "hi?", + tool_calls=[ + { + "name": "tool3", + "args": {"some_val": 2, "some_other_val": "bar"}, + "id": "some 2", + } + ], + ) + ] + } + ) + tool_message: ToolMessage = result3["messages"][-1] + assert tool_message.type == "tool" + assert ( + tool_message.content + == '[{"key_1": 2, "key_2": "foo"}, {"key_1": "bar", "key_2": "baz"}]' + ) + assert tool_message.tool_call_id == "some 2" + + # list of content blocks tool content + result4 = await ToolNode([tool4]).ainvoke( + { + "messages": [ + AIMessage( + "hi?", + tool_calls=[ + { + "name": "tool4", + "args": {"some_val": 2, "some_other_val": "bar"}, + "id": "some 3", + } + ], + ) + ] + } + ) + tool_message: ToolMessage = result4["messages"][-1] + assert tool_message.type == "tool" + assert tool_message.content == [{"type": "image_url", "image_url": {"url": "abdc"}}] + assert tool_message.tool_call_id == "some 3" + + +async def test_tool_node_error_handling(): + def handle_all(e: Union[ValueError, ToolException, ValidationError]): + return TOOL_CALL_ERROR_TEMPLATE.format(error=repr(e)) + + # test catching all exceptions, via: + # - handle_tool_errors = True + # - passing a tuple of all exceptions + # - passing a callable with all exceptions in the signature + for handle_tool_errors in ( + True, + (ValueError, ToolException, ValidationError), + handle_all, + ): + result_error = await ToolNode( + [tool1, tool2, tool3], handle_tool_errors=handle_tool_errors + ).ainvoke( + { + "messages": [ + AIMessage( + "hi?", + tool_calls=[ + { + "name": "tool1", + "args": {"some_val": 0, "some_other_val": "foo"}, + "id": "some id", + }, + { + "name": "tool2", + "args": {"some_val": 0, "some_other_val": "bar"}, + "id": "some other id", + }, + { + "name": "tool3", + "args": {"some_val": 0}, + "id": "another id", + }, + ], + ) + ] + } + ) + + assert all(m.type == "tool" for m in result_error["messages"]) + assert all(m.status == "error" for m in result_error["messages"]) + assert ( + result_error["messages"][0].content + == f"Error: {repr(ValueError('Test error'))}\n Please fix your mistakes." + ) + assert ( + result_error["messages"][1].content + == f"Error: {repr(ToolException('Test error'))}\n Please fix your mistakes." + ) + assert ( + "ValidationError" in result_error["messages"][2].content + or "validation error" in result_error["messages"][2].content + ) + + assert result_error["messages"][0].tool_call_id == "some id" + assert result_error["messages"][1].tool_call_id == "some other id" + assert result_error["messages"][2].tool_call_id == "another id" + + +async def test_tool_node_error_handling_callable(): + def handle_value_error(e: ValueError): + return "Value error" + + def handle_tool_exception(e: ToolException): + return "Tool exception" + + for handle_tool_errors in ("Value error", handle_value_error): + result_error = await ToolNode( + [tool1], handle_tool_errors=handle_tool_errors + ).ainvoke( + { + "messages": [ + AIMessage( + "hi?", + tool_calls=[ + { + "name": "tool1", + "args": {"some_val": 0, "some_other_val": "foo"}, + "id": "some id", + }, + ], + ) + ] + } + ) + tool_message: ToolMessage = result_error["messages"][-1] + assert tool_message.type == "tool" + assert tool_message.status == "error" + assert tool_message.content == "Value error" + + # test raising for an unhandled exception, via: + # - passing a tuple of all exceptions + # - passing a callable with all exceptions in the signature + for handle_tool_errors in ((ValueError,), handle_value_error): + with pytest.raises(ToolException) as exc_info: + await ToolNode( + [tool1, tool2], handle_tool_errors=handle_tool_errors + ).ainvoke( + { + "messages": [ + AIMessage( + "hi?", + tool_calls=[ + { + "name": "tool1", + "args": {"some_val": 0, "some_other_val": "foo"}, + "id": "some id", + }, + { + "name": "tool2", + "args": {"some_val": 0, "some_other_val": "bar"}, + "id": "some other id", + }, + ], + ) + ] + } + ) + assert str(exc_info.value) == "Test error" + + for handle_tool_errors in ((ToolException,), handle_tool_exception): + with pytest.raises(ValueError) as exc_info: + await ToolNode( + [tool1, tool2], handle_tool_errors=handle_tool_errors + ).ainvoke( + { + "messages": [ + AIMessage( + "hi?", + tool_calls=[ + { + "name": "tool1", + "args": {"some_val": 0, "some_other_val": "foo"}, + "id": "some id", + }, + { + "name": "tool2", + "args": {"some_val": 0, "some_other_val": "bar"}, + "id": "some other id", + }, + ], + ) + ] + } + ) + assert str(exc_info.value) == "Test error" + + +async def test_tool_node_handle_tool_errors_false(): + with pytest.raises(ValueError) as exc_info: + ToolNode([tool1], handle_tool_errors=False).invoke( + { + "messages": [ + AIMessage( + "hi?", + tool_calls=[ + { + "name": "tool1", + "args": {"some_val": 0, "some_other_val": "foo"}, + "id": "some id", + } + ], + ) + ] + } + ) + + assert str(exc_info.value) == "Test error" + + with pytest.raises(ToolException): await ToolNode([tool2], handle_tool_errors=False).ainvoke( { "messages": [ @@ -465,7 +820,7 @@ async def test_tool_node(): { "name": "tool2", "args": {"some_val": 0, "some_other_val": "bar"}, - "id": "some 1", + "id": "some id", } ], ) @@ -473,7 +828,57 @@ async def test_tool_node(): } ) - # incorrect tool name + assert str(exc_info.value) == "Test error" + + # test validation errors get raised if handle_tool_errors is False + with pytest.raises((ValidationError, ValidationErrorV1)): + ToolNode([tool1], handle_tool_errors=False).invoke( + { + "messages": [ + AIMessage( + "hi?", + tool_calls=[ + { + "name": "tool1", + "args": {"some_val": 0}, + "id": "some id", + } + ], + ) + ] + } + ) + + +def test_tool_node_individual_tool_error_handling(): + # test error handling on individual tools (and that it overrides overall error handling!) + result_individual_tool_error_handler = ToolNode( + [tool5], handle_tool_errors="bar" + ).invoke( + { + "messages": [ + AIMessage( + "hi?", + tool_calls=[ + { + "name": "tool5", + "args": {"some_val": 0}, + "id": "some 0", + } + ], + ) + ] + } + ) + + tool_message: ToolMessage = result_individual_tool_error_handler["messages"][-1] + assert tool_message.type == "tool" + assert tool_message.status == "error" + assert tool_message.content == "foo" + assert tool_message.tool_call_id == "some 0" + + +def test_tool_node_incorrect_tool_name(): result_incorrect_name = ToolNode([tool1, tool2]).invoke( { "messages": [ @@ -490,60 +895,94 @@ async def test_tool_node(): ] } ) + tool_message: ToolMessage = result_incorrect_name["messages"][-1] assert tool_message.type == "tool" + assert tool_message.status == "error" assert ( tool_message.content == "Error: tool3 is not a valid tool, try one of [tool1, tool2]." ) assert tool_message.tool_call_id == "some 0" - # list of dicts tool content - result3 = await ToolNode([tool3]).ainvoke( - { - "messages": [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": "tool3", - "args": {"some_val": 2, "some_other_val": "bar"}, - "id": "some 0", - } - ], - ) - ] - } - ) - tool_message: ToolMessage = result3["messages"][-1] - assert tool_message.type == "tool" - assert ( - tool_message.content - == '[{"key_1": 2, "key_2": "foo"}, {"key_1": "bar", "key_2": "baz"}]' - ) - assert tool_message.tool_call_id == "some 0" - # list of content blocks tool content - result4 = await ToolNode([tool4]).ainvoke( - { - "messages": [ - AIMessage( - "hi?", - tool_calls=[ - { - "name": "tool4", - "args": {"some_val": 2, "some_other_val": "bar"}, - "id": "some 0", - } - ], - ) - ] - } +def test_tool_node_node_interrupt(): + def tool_normal(some_val: int) -> str: + """Tool docstring.""" + return "normal" + + def tool_interrupt(some_val: int) -> str: + """Tool docstring.""" + raise NodeInterrupt("foo") + + def handle(e: NodeInterrupt): + return "handled" + + for handle_tool_errors in (True, (NodeInterrupt,), "handled", handle, False): + node = ToolNode([tool_interrupt], handle_tool_errors=handle_tool_errors) + with pytest.raises(NodeInterrupt) as exc_info: + node.invoke( + { + "messages": [ + AIMessage( + "hi?", + tool_calls=[ + { + "name": "tool_interrupt", + "args": {"some_val": 0}, + "id": "some 0", + } + ], + ) + ] + } + ) + assert exc_info.value == "foo" + + # test inside react agent + model = FakeToolCallingModel( + tool_calls=[ + [ + ToolCall(name="tool_interrupt", args={"some_val": 0}, id="1"), + ToolCall(name="tool_normal", args={"some_val": 1}, id="2"), + ], + [], + ] ) - tool_message: ToolMessage = result4["messages"][-1] - assert tool_message.type == "tool" - assert tool_message.content == [{"type": "image_url", "image_url": {"url": "abdc"}}] - assert tool_message.tool_call_id == "some 0" + checkpointer = MemorySaver() + config = {"configurable": {"thread_id": "1"}} + agent = create_react_agent( + model, [tool_interrupt, tool_normal], checkpointer=checkpointer + ) + result = agent.invoke({"messages": [HumanMessage("hi?")]}, config) + assert result["messages"] == [ + _AnyIdHumanMessage( + content="hi?", + ), + AIMessage( + content="hi?", + id="0", + tool_calls=[ + { + "name": "tool_interrupt", + "args": {"some_val": 0}, + "id": "1", + "type": "tool_call", + }, + { + "name": "tool_normal", + "args": {"some_val": 1}, + "id": "2", + "type": "tool_call", + }, + ], + ), + ] + state = agent.get_state(config) + assert state.next == ("tools",) + task = state.tasks[0] + assert task.name == "tools" + assert task.interrupts == (Interrupt(value="foo", when="during"),) def my_function(some_val: int, some_other_val: str) -> str: @@ -826,6 +1265,47 @@ def test_tool_node_ensure_utf8() -> None: assert outputs[0].content == json.dumps(data, ensure_ascii=False) +def test_tool_node_messages_key() -> None: + @dec_tool + def add(a: int, b: int): + """Adds a and b.""" + return a + b + + model = FakeToolCallingModel( + tool_calls=[[ToolCall(name=add.name, args={"a": 1, "b": 2}, id="test_id")]] + ) + + class State(TypedDict): + subgraph_messages: Annotated[list[AnyMessage], add_messages] + + def call_model(state: State): + response = model.invoke(state["subgraph_messages"]) + model.tool_calls = [] + return {"subgraph_messages": response} + + builder = StateGraph(State) + builder.add_node("agent", call_model) + builder.add_node("tools", ToolNode([add], messages_key="subgraph_messages")) + builder.add_conditional_edges( + "agent", partial(tools_condition, messages_key="subgraph_messages") + ) + builder.add_edge(START, "agent") + builder.add_edge("tools", "agent") + + graph = builder.compile() + result = graph.invoke({"subgraph_messages": [HumanMessage(content="hi")]}) + assert result["subgraph_messages"] == [ + _AnyIdHumanMessage(content="hi"), + AIMessage( + content="hi", + id="0", + tool_calls=[ToolCall(name=add.name, args={"a": 1, "b": 2}, id="test_id")], + ), + _AnyIdToolMessage(content="3", name=add.name, tool_call_id="test_id"), + AIMessage(content="hi-hi-3", id="1"), + ] + + async def test_return_direct() -> None: @dec_tool(return_direct=True) def tool_return_direct(input: str) -> str: @@ -921,3 +1401,18 @@ async def test_return_direct() -> None: id=result["messages"][3].id, ), ] + + +def test__get_state_args() -> None: + class Schema1(BaseModel): + a: Annotated[str, InjectedState] + + class Schema2(Schema1): + b: Annotated[int, InjectedState("bar")] + + @dec_tool(args_schema=Schema2) + def foo(a: str, b: int) -> float: + """return""" + return 0.0 + + assert _get_state_args(foo) == {"a": None, "b": "bar"} diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index a73083290..fda881b15 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -8,6 +8,7 @@ import warnings from collections import Counter from concurrent.futures import ThreadPoolExecutor from contextlib import contextmanager +from dataclasses import replace from random import randrange from typing import ( Annotated, @@ -54,12 +55,17 @@ from langgraph.checkpoint.base import ( CheckpointTuple, ) from langgraph.checkpoint.memory import MemorySaver -from langgraph.constants import ERROR, PULL, PUSH +from langgraph.constants import ( + CONFIG_KEY_NODE_FINISHED, + ERROR, + FF_SEND_V2, + PULL, + PUSH, + START, +) from langgraph.errors import InvalidUpdateError, MultipleSubgraphsError, NodeInterrupt -from langgraph.graph import END, Graph -from langgraph.graph.graph import START -from langgraph.graph.message import MessageGraph, add_messages -from langgraph.graph.state import StateGraph +from langgraph.graph import END, Graph, GraphCommand, StateGraph +from langgraph.graph.message import MessageGraph, MessagesState, add_messages from langgraph.managed.shared_value import SharedValue from langgraph.prebuilt.chat_agent_executor import ( create_tool_calling_executor, @@ -74,7 +80,14 @@ from langgraph.pregel import ( from langgraph.pregel.retry import RetryPolicy from langgraph.store.base import BaseStore from langgraph.store.memory import InMemoryStore -from langgraph.types import Interrupt, PregelTask, Send, StreamWriter +from langgraph.types import ( + Command, + Interrupt, + PregelTask, + Send, + StreamWriter, + interrupt, +) from tests.any_str import AnyDict, AnyStr, AnyVersion, FloatBetween, UnsortedSequence from tests.conftest import ( ALL_CHECKPOINTERS_SYNC, @@ -748,7 +761,13 @@ def test_invoke_two_processes_in_out_interrupt( "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "step": 6, "writes": {"two": 5}}, + metadata={ + "parents": {}, + "source": "loop", + "step": 6, + "writes": {"two": 5}, + "thread_id": "1", + }, created_at=AnyStr(), parent_config=history[1].config, ), @@ -768,6 +787,7 @@ def test_invoke_two_processes_in_out_interrupt( "source": "loop", "step": 5, "writes": {"one": None}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[2].config, @@ -788,6 +808,7 @@ def test_invoke_two_processes_in_out_interrupt( "source": "input", "step": 4, "writes": {"input": 3}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[3].config, @@ -808,6 +829,7 @@ def test_invoke_two_processes_in_out_interrupt( "source": "loop", "step": 3, "writes": {"one": None}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[4].config, @@ -828,6 +850,7 @@ def test_invoke_two_processes_in_out_interrupt( "source": "input", "step": 2, "writes": {"input": 20}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[5].config, @@ -843,7 +866,13 @@ def test_invoke_two_processes_in_out_interrupt( "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "step": 1, "writes": {"two": 4}}, + metadata={ + "parents": {}, + "source": "loop", + "step": 1, + "writes": {"two": 4}, + "thread_id": "1", + }, created_at=AnyStr(), parent_config=history[6].config, ), @@ -863,6 +892,7 @@ def test_invoke_two_processes_in_out_interrupt( "source": "loop", "step": 0, "writes": {"one": None}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[7].config, @@ -883,6 +913,7 @@ def test_invoke_two_processes_in_out_interrupt( "source": "input", "step": -1, "writes": {"input": 2}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -949,6 +980,7 @@ def test_fork_always_re_runs_nodes( "source": "loop", "step": 5, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[1].config, @@ -969,6 +1001,7 @@ def test_fork_always_re_runs_nodes( "source": "loop", "step": 4, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[2].config, @@ -989,6 +1022,7 @@ def test_fork_always_re_runs_nodes( "source": "loop", "step": 3, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[3].config, @@ -1009,6 +1043,7 @@ def test_fork_always_re_runs_nodes( "source": "loop", "step": 2, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[4].config, @@ -1029,6 +1064,7 @@ def test_fork_always_re_runs_nodes( "source": "loop", "step": 1, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[5].config, @@ -1044,7 +1080,13 @@ def test_fork_always_re_runs_nodes( "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "thread_id": "1", + }, created_at=AnyStr(), parent_config=history[6].config, ), @@ -1064,6 +1106,7 @@ def test_fork_always_re_runs_nodes( "source": "input", "step": -1, "writes": {"__start__": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -1087,6 +1130,86 @@ def test_fork_always_re_runs_nodes( ] +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) +def test_run_from_checkpoint_id_retains_previous_writes( + request: pytest.FixtureRequest, checkpointer_name: str, mocker: MockerFixture +) -> None: + checkpointer = request.getfixturevalue(f"checkpointer_{checkpointer_name}") + + class MyState(TypedDict): + myval: Annotated[int, operator.add] + otherval: bool + + class Anode: + def __init__(self): + self.switch = False + + def __call__(self, state: MyState): + self.switch = not self.switch + return {"myval": 2 if self.switch else 1, "otherval": self.switch} + + builder = StateGraph(MyState) + thenode = Anode() # Fun. + builder.add_node("node_one", thenode) + builder.add_node("node_two", thenode) + builder.add_edge(START, "node_one") + + def _getedge(src: str): + swap = "node_one" if src == "node_two" else "node_two" + + def _edge(st: MyState) -> Literal["__end__", "node_one", "node_two"]: + if st["myval"] > 3: + return END + if st["otherval"]: + return swap + return src + + return _edge + + builder.add_conditional_edges("node_one", _getedge("node_one")) + builder.add_conditional_edges("node_two", _getedge("node_two")) + graph = builder.compile(checkpointer=checkpointer) + + thread_id = uuid.uuid4() + thread1 = {"configurable": {"thread_id": str(thread_id)}} + + result = graph.invoke({"myval": 1}, thread1) + assert result["myval"] == 4 + history = [c for c in graph.get_state_history(thread1)] + + assert len(history) == 4 + assert history[-1].values == {"myval": 0} + assert history[0].values == {"myval": 4, "otherval": False} + + second_run_config = { + **thread1, + "configurable": { + **thread1["configurable"], + "checkpoint_id": history[1].config["configurable"]["checkpoint_id"], + }, + } + second_result = graph.invoke(None, second_run_config) + assert second_result == {"myval": 5, "otherval": True} + + new_history = [ + c + for c in graph.get_state_history( + {"configurable": {"thread_id": str(thread_id), "checkpoint_ns": ""}} + ) + ] + + assert len(new_history) == len(history) + 1 + for original, new in zip(history, new_history[1:]): + assert original.values == new.values + assert original.next == new.next + assert original.metadata["step"] == new.metadata["step"] + + def _get_tasks(hist: list, start: int): + return [h.tasks for h in hist[start:]] + + assert _get_tasks(new_history, 1) == _get_tasks(history, 0) + + def test_invoke_two_processes_in_dict_out(mocker: MockerFixture) -> None: add_one = mocker.Mock(side_effect=lambda x: x + 1) one = Channel.subscribe_to("input") | add_one | Channel.write_to("inbox") @@ -1483,10 +1606,11 @@ def test_pending_writes_resume( assert two.calls == 2 # two attempts # latest checkpoint should be before nodes "one", "two" + # but we should have applied the write from "one" state = graph.get_state(thread1) assert state is not None - assert state.values == {"value": 1} - assert state.next == ("one", "two") + assert state.values == {"value": 3} + assert state.next == ("two",) assert state.tasks == ( PregelTask(AnyStr(), "one", (PULL, "one"), result={"value": 2}), PregelTask(AnyStr(), "two", (PULL, "two"), 'ConnectionError("I\'m not good")'), @@ -1496,7 +1620,13 @@ def test_pending_writes_resume( "source": "loop", "step": 0, "writes": None, + "thread_id": "1", } + # get_state with checkpoint_id should not apply any pending writes + state = graph.get_state(state.config) + assert state is not None + assert state.values == {"value": 1} + assert state.next == ("one", "two") # should contain pending write of "one" checkpoint = checkpointer.get_tuple(thread1) assert checkpoint is not None @@ -1584,6 +1714,7 @@ def test_pending_writes_resume( "step": 1, "source": "loop", "writes": {"one": {"value": 2}, "two": {"value": 3}}, + "thread_id": "1", }, parent_config={ "configurable": { @@ -1628,7 +1759,13 @@ def test_pending_writes_resume( "start:two": "__start__", }, }, - metadata={"parents": {}, "step": 0, "source": "loop", "writes": None}, + metadata={ + "parents": {}, + "step": 0, + "source": "loop", + "writes": None, + "thread_id": "1", + }, parent_config={ "configurable": { "thread_id": "1", @@ -1668,6 +1805,7 @@ def test_pending_writes_resume( "step": -1, "source": "input", "writes": {"__start__": {"value": 1}}, + "thread_id": "1", }, parent_config=None, pending_writes=UnsortedSequence( @@ -1704,6 +1842,1199 @@ def test_cond_edge_after_send() -> None: assert graph.invoke(["0"]) == ["0", "1", "2", "2", "3"] +def test_concurrent_emit_sends() -> None: + class Node: + def __init__(self, name: str): + self.name = name + setattr(self, "__name__", name) + + def __call__(self, state): + return ( + [self.name] + if isinstance(state, list) + else ["|".join((self.name, str(state)))] + ) + + def send_for_fun(state): + return [Send("2", 1), Send("2", 2), "3.1"] + + def send_for_profit(state): + return [Send("2", 3), Send("2", 4)] + + def route_to_three(state) -> Literal["3"]: + return "3" + + builder = StateGraph(Annotated[list, operator.add]) + builder.add_node(Node("1")) + builder.add_node(Node("1.1")) + builder.add_node(Node("2")) + builder.add_node(Node("3")) + builder.add_node(Node("3.1")) + builder.add_edge(START, "1") + builder.add_edge(START, "1.1") + builder.add_conditional_edges("1", send_for_fun) + builder.add_conditional_edges("1.1", send_for_profit) + builder.add_conditional_edges("2", route_to_three) + graph = builder.compile() + assert graph.invoke(["0"]) == ( + [ + "0", + "1", + "1.1", + "2|1", + "2|2", + "2|3", + "2|4", + "3", + "3.1", + ] + if FF_SEND_V2 + else [ + "0", + "1", + "1.1", + "3.1", + "2|1", + "2|2", + "2|3", + "2|4", + "3", + ] + ) + + +def test_send_sequences() -> None: + class Node: + def __init__(self, name: str): + self.name = name + setattr(self, "__name__", name) + + def __call__(self, state): + update = ( + [self.name] + if isinstance(state, list) + else ["|".join((self.name, str(state)))] + ) + if isinstance(state, Command): + return replace(state, update=update) + else: + return update + + def send_for_fun(state): + return [ + Send("2", GraphCommand(send=Send("2", 3))), + Send("2", GraphCommand(send=Send("2", 4))), + "3.1", + ] + + def route_to_three(state) -> Literal["3"]: + return "3" + + builder = StateGraph(Annotated[list, operator.add]) + builder.add_node(Node("1")) + builder.add_node(Node("2")) + builder.add_node(Node("3")) + builder.add_node(Node("3.1")) + builder.add_edge(START, "1") + builder.add_conditional_edges("1", send_for_fun) + builder.add_conditional_edges("2", route_to_three) + graph = builder.compile() + assert ( + graph.invoke(["0"]) + == [ + "0", + "1", + "2|Command(send=Send(node='2', arg=3))", + "2|Command(send=Send(node='2', arg=4))", + "2|3", + "2|4", + "3", + "3.1", + ] + if FF_SEND_V2 + else [ + "0", + "1", + "3.1", + "2|Command(send=Send(node='2', arg=3))", + "2|Command(send=Send(node='2', arg=4))", + "3", + "2|3", + "2|4", + "3", + ] + ) + + +@pytest.mark.repeat(20) +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) +def test_send_dedupe_on_resume( + request: pytest.FixtureRequest, checkpointer_name: str +) -> None: + if not FF_SEND_V2: + pytest.skip("Send deduplication is only available in Send V2") + checkpointer = request.getfixturevalue(f"checkpointer_{checkpointer_name}") + + class InterruptOnce: + ticks: int = 0 + + def __call__(self, state): + self.ticks += 1 + if self.ticks == 1: + raise NodeInterrupt("Bahh") + return ["|".join(("flaky", str(state)))] + + class Node: + def __init__(self, name: str): + self.name = name + self.ticks = 0 + setattr(self, "__name__", name) + + def __call__(self, state): + self.ticks += 1 + update = ( + [self.name] + if isinstance(state, list) + else ["|".join((self.name, str(state)))] + ) + if isinstance(state, GraphCommand): + return replace(state, update=update) + else: + return update + + def send_for_fun(state): + return [ + Send("2", GraphCommand(send=Send("2", 3))), + Send("2", GraphCommand(send=Send("flaky", 4))), + "3.1", + ] + + def route_to_three(state) -> Literal["3"]: + return "3" + + builder = StateGraph(Annotated[list, operator.add]) + builder.add_node(Node("1")) + builder.add_node(Node("2")) + builder.add_node(Node("3")) + builder.add_node(Node("3.1")) + builder.add_node("flaky", InterruptOnce()) + builder.add_edge(START, "1") + builder.add_conditional_edges("1", send_for_fun) + builder.add_conditional_edges("2", route_to_three) + + graph = builder.compile(checkpointer=checkpointer) + thread1 = {"configurable": {"thread_id": "1"}} + assert graph.invoke(["0"], thread1, debug=1) == [ + "0", + "1", + "2|Command(send=Send(node='2', arg=3))", + "2|Command(send=Send(node='flaky', arg=4))", + "2|3", + ] + assert builder.nodes["2"].runnable.func.ticks == 3 + assert builder.nodes["flaky"].runnable.func.ticks == 1 + # check state + state = graph.get_state(thread1) + assert state.next == ("flaky",) + # check history + history = [c for c in graph.get_state_history(thread1)] + assert len(history) == 2 + # resume execution + assert graph.invoke(None, thread1, debug=1) == [ + "0", + "1", + "2|Command(send=Send(node='2', arg=3))", + "2|Command(send=Send(node='flaky', arg=4))", + "2|3", + "flaky|4", + "3", + "3.1", + ] + # node "2" doesn't get called again, as we recover writes saved before + assert builder.nodes["2"].runnable.func.ticks == 3 + # node "flaky" gets called again, as it was interrupted + assert builder.nodes["flaky"].runnable.func.ticks == 2 + # check state + state = graph.get_state(thread1) + assert state.next == () + # check history + history = [c for c in graph.get_state_history(thread1)] + assert ( + history[1] + == [ + StateSnapshot( + values=[ + "0", + "1", + "2|Command(send=Send(node='2', arg=3))", + "2|Command(send=Send(node='flaky', arg=4))", + "2|3", + "flaky|4", + "3", + "3.1", + ], + next=(), + config={ + "configurable": { + "thread_id": "1", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "source": "loop", + "writes": {"3": ["3"], "3.1": ["3.1"]}, + "thread_id": "1", + "step": 2, + "parents": {}, + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "1", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=(), + ), + StateSnapshot( + values=[ + "0", + "1", + "2|Command(send=Send(node='2', arg=3))", + "2|Command(send=Send(node='flaky', arg=4))", + "2|3", + "flaky|4", + ], + next=("3", "3.1"), + config={ + "configurable": { + "thread_id": "1", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "source": "loop", + "writes": { + "1": ["1"], + "2": [ + ["2|Command(send=Send(node='2', arg=3))"], + ["2|Command(send=Send(node='flaky', arg=4))"], + ["2|3"], + ], + "flaky": ["flaky|4"], + }, + "thread_id": "1", + "step": 1, + "parents": {}, + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "1", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="3", + path=("__pregel_pull", "3"), + error=None, + interrupts=(), + state=None, + result=["3"], + ), + PregelTask( + id=AnyStr(), + name="3.1", + path=("__pregel_pull", "3.1"), + error=None, + interrupts=(), + state=None, + result=["3.1"], + ), + ), + ), + StateSnapshot( + values=["0"], + next=("1", "2", "2", "2", "flaky"), + config={ + "configurable": { + "thread_id": "1", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "source": "loop", + "writes": None, + "thread_id": "1", + "step": 0, + "parents": {}, + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "1", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="1", + path=("__pregel_pull", "1"), + error=None, + interrupts=(), + state=None, + result=["1"], + ), + PregelTask( + id=AnyStr(), + name="2", + path=( + "__pregel_push", + ("__pregel_pull", "1"), + 2, + AnyStr(), + ), + error=None, + interrupts=(), + state=None, + result=["2|Command(send=Send(node='2', arg=3))"], + ), + PregelTask( + id=AnyStr(), + name="2", + path=( + "__pregel_push", + ("__pregel_pull", "1"), + 3, + AnyStr(), + ), + error=None, + interrupts=(), + state=None, + result=["2|Command(send=Send(node='flaky', arg=4))"], + ), + PregelTask( + id=AnyStr(), + name="2", + path=( + "__pregel_push", + ( + "__pregel_push", + ("__pregel_pull", "1"), + 2, + AnyStr(), + ), + 2, + AnyStr(), + ), + error=None, + interrupts=(), + state=None, + result=["2|3"], + ), + PregelTask( + id=AnyStr(), + name="flaky", + path=( + "__pregel_push", + ( + "__pregel_push", + ("__pregel_pull", "1"), + 3, + AnyStr(), + ), + 2, + AnyStr(), + ), + error=None, + interrupts=(Interrupt(value="Bahh", when="during"),), + state=None, + result=["flaky|4"], + ), + ), + ), + StateSnapshot( + values=[], + next=("__start__",), + config={ + "configurable": { + "thread_id": "1", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "source": "input", + "writes": {"__start__": ["0"]}, + "thread_id": "1", + "step": -1, + "parents": {}, + }, + created_at=AnyStr(), + parent_config=None, + tasks=( + PregelTask( + id=AnyStr(), + name="__start__", + path=("__pregel_pull", "__start__"), + error=None, + interrupts=(), + state=None, + result=["0"], + ), + ), + ), + ][1] + ) + + +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) +def test_send_react_interrupt( + request: pytest.FixtureRequest, checkpointer_name: str +) -> None: + from langchain_core.messages import AIMessage, HumanMessage, ToolCall, ToolMessage + + checkpointer = request.getfixturevalue(f"checkpointer_{checkpointer_name}") + + ai_message = AIMessage( + "", + id="ai1", + tool_calls=[ToolCall(name="foo", args={"hi": [1, 2, 3]}, id=AnyStr())], + ) + + def agent(state): + return {"messages": ai_message} + + def route(state): + if isinstance(state["messages"][-1], AIMessage): + return [ + Send(call["name"], call) for call in state["messages"][-1].tool_calls + ] + + foo_called = 0 + + def foo(call: ToolCall): + nonlocal foo_called + foo_called += 1 + return {"messages": ToolMessage(str(call["args"]), tool_call_id=call["id"])} + + builder = StateGraph(MessagesState) + builder.add_node(agent) + builder.add_node(foo) + builder.add_edge(START, "agent") + builder.add_conditional_edges("agent", route) + graph = builder.compile() + + assert graph.invoke({"messages": [HumanMessage("hello")]}) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage( + content="{'hi': [1, 2, 3]}", + tool_call_id=AnyStr(), + ), + ] + } + assert foo_called == 1 + + # simple interrupt-resume flow + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "1"}} + assert graph.invoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + assert graph.invoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage( + content="{'hi': [1, 2, 3]}", + tool_call_id=AnyStr(), + ), + ] + } + assert foo_called == 1 + + # interrupt-update-resume flow + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "2"}} + assert graph.invoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + + if not FF_SEND_V2: + return + + # get state should show the pending task + state = graph.get_state(thread1) + assert state == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + }, + next=("foo",), + config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 0, + "source": "loop", + "writes": None, + "parents": {}, + "thread_id": "2", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="agent", + path=("__pregel_pull", "agent"), + error=None, + interrupts=(), + state=None, + result={ + "messages": AIMessage( + content="", + additional_kwargs={}, + response_metadata={}, + id="ai1", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ) + }, + ), + PregelTask( + id=AnyStr(), + name="foo", + path=("__pregel_push", ("__pregel_pull", "agent"), 2, AnyStr()), + error=None, + interrupts=(), + state=None, + result=None, + ), + ), + ) + + # remove the tool call, clearing the pending task + graph.update_state( + thread1, {"messages": AIMessage("Bye now", id=ai_message.id, tool_calls=[])} + ) + + # tool call no longer in pending tasks + assert graph.get_state(thread1) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="Bye now", + tool_calls=[], + ), + ] + }, + next=(), + config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 1, + "source": "update", + "writes": { + "agent": { + "messages": _AnyIdAIMessage( + content="Bye now", + tool_calls=[], + ) + } + }, + "parents": {}, + "thread_id": "2", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=(), + ) + + # tool call not executed + assert graph.invoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage(content="Bye now"), + ] + } + assert foo_called == 0 + + # interrupt-update-resume flow, creating new Send in update call + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "3"}} + assert graph.invoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + + # get state should show the pending task + state = graph.get_state(thread1) + assert state == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + }, + next=("foo",), + config={ + "configurable": { + "thread_id": "3", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 0, + "source": "loop", + "writes": None, + "parents": {}, + "thread_id": "3", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "3", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="agent", + path=("__pregel_pull", "agent"), + error=None, + interrupts=(), + state=None, + result={ + "messages": AIMessage( + "", + id="ai1", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ) + }, + ), + PregelTask( + id=AnyStr(), + name="foo", + path=("__pregel_push", ("__pregel_pull", "agent"), 2, AnyStr()), + error=None, + interrupts=(), + state=None, + result=None, + ), + ), + ) + + # replace the tool call, should clear previous send, create new one + graph.update_state( + thread1, + { + "messages": AIMessage( + "", + id=ai_message.id, + tool_calls=[ + { + "name": "foo", + "args": {"hi": [4, 5, 6]}, + "id": "tool1", + "type": "tool_call", + } + ], + ) + }, + ) + + # prev tool call no longer in pending tasks, new tool call is + assert graph.get_state(thread1) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [4, 5, 6]}, + "id": "tool1", + "type": "tool_call", + } + ], + ), + ] + }, + next=("foo",), + config={ + "configurable": { + "thread_id": "3", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 1, + "source": "update", + "writes": { + "agent": { + "messages": _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [4, 5, 6]}, + "id": "tool1", + "type": "tool_call", + } + ], + ) + } + }, + "parents": {}, + "thread_id": "3", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "3", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="foo", + path=("__pregel_push", (), 0, AnyStr()), + error=None, + interrupts=(), + state=None, + result=None, + ), + ), + ) + + # prev tool call not executed, new tool call is + assert graph.invoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + AIMessage( + "", + id="ai1", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [4, 5, 6]}, + "id": "tool1", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage(content="{'hi': [4, 5, 6]}", tool_call_id="tool1"), + ] + } + assert foo_called == 1 + + +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) +def test_send_react_interrupt_control( + request: pytest.FixtureRequest, checkpointer_name: str, snapshot: SnapshotAssertion +) -> None: + from langchain_core.messages import AIMessage, HumanMessage, ToolCall, ToolMessage + + checkpointer = request.getfixturevalue(f"checkpointer_{checkpointer_name}") + + ai_message = AIMessage( + "", + id="ai1", + tool_calls=[ToolCall(name="foo", args={"hi": [1, 2, 3]}, id=AnyStr())], + ) + + def agent(state) -> GraphCommand[Literal["foo"]]: + return GraphCommand( + update={"messages": ai_message}, + send=[Send(call["name"], call) for call in ai_message.tool_calls], + ) + + foo_called = 0 + + def foo(call: ToolCall): + nonlocal foo_called + foo_called += 1 + return {"messages": ToolMessage(str(call["args"]), tool_call_id=call["id"])} + + builder = StateGraph(MessagesState) + builder.add_node(agent) + builder.add_node(foo) + builder.add_edge(START, "agent") + graph = builder.compile() + assert graph.get_graph().draw_mermaid() == snapshot + + assert graph.invoke({"messages": [HumanMessage("hello")]}) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage( + content="{'hi': [1, 2, 3]}", + tool_call_id=AnyStr(), + ), + ] + } + assert foo_called == 1 + + # simple interrupt-resume flow + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "1"}} + assert graph.invoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + assert graph.invoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage( + content="{'hi': [1, 2, 3]}", + tool_call_id=AnyStr(), + ), + ] + } + assert foo_called == 1 + + if not FF_SEND_V2: + return + + # interrupt-update-resume flow + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "2"}} + assert graph.invoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + + # get state should show the pending task + state = graph.get_state(thread1) + assert state == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + }, + next=("foo",), + config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 0, + "source": "loop", + "writes": None, + "parents": {}, + "thread_id": "2", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="agent", + path=("__pregel_pull", "agent"), + error=None, + interrupts=(), + state=None, + result={ + "messages": AIMessage( + content="", + additional_kwargs={}, + response_metadata={}, + id="ai1", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ) + }, + ), + PregelTask( + id=AnyStr(), + name="foo", + path=("__pregel_push", ("__pregel_pull", "agent"), 2, AnyStr()), + error=None, + interrupts=(), + state=None, + result=None, + ), + ), + ) + + # remove the tool call, clearing the pending task + graph.update_state( + thread1, {"messages": AIMessage("Bye now", id=ai_message.id, tool_calls=[])} + ) + + # tool call no longer in pending tasks + assert graph.get_state(thread1) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="Bye now", + tool_calls=[], + ), + ] + }, + next=(), + config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 1, + "source": "update", + "writes": { + "agent": { + "messages": _AnyIdAIMessage( + content="Bye now", + tool_calls=[], + ) + } + }, + "parents": {}, + "thread_id": "2", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=(), + ) + + # tool call not executed + assert graph.invoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage(content="Bye now"), + ] + } + assert foo_called == 0 + + # interrupt-update-resume flow, creating new Send in update call + + # TODO add here test with invoke(Command()) + + @pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) def test_invoke_checkpoint_three( mocker: MockerFixture, request: pytest.FixtureRequest, checkpointer_name: str @@ -2117,7 +3448,9 @@ def test_conditional_graph( workflow.add_node("agent", agent) workflow.add_node( - "tools", execute_tools, metadata={"parents": {}, "version": 2, "variant": "b"} + "tools", + execute_tools, + metadata={"parents": {}, "version": 2, "variant": "b"}, ) workflow.set_entry_point("agent") @@ -2311,6 +3644,7 @@ def test_conditional_graph( } }, }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -2362,6 +3696,7 @@ def test_conditional_graph( "input": "what is weather in sf", }, }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -2482,6 +3817,7 @@ def test_conditional_graph( ), } }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -2537,6 +3873,7 @@ def test_conditional_graph( } } }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -2582,6 +3919,7 @@ def test_conditional_graph( "input": "what is weather in sf", } }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -2702,6 +4040,7 @@ def test_conditional_graph( ), } }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -2757,6 +4096,7 @@ def test_conditional_graph( } } }, + "thread_id": "3", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3230,6 +4570,7 @@ def test_conditional_state_graph( ), } }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3272,6 +4613,7 @@ def test_conditional_state_graph( ) }, }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3348,6 +4690,7 @@ def test_conditional_state_graph( ) } }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3399,6 +4742,7 @@ def test_conditional_state_graph( ), } }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3440,6 +4784,7 @@ def test_conditional_state_graph( ) } }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3514,6 +4859,7 @@ def test_conditional_state_graph( ) } }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3541,7 +4887,13 @@ def test_conditional_state_graph( next=("agent",), config=app_w_interrupt.checkpointer.get_tuple(config).config, created_at=app_w_interrupt.checkpointer.get_tuple(config).checkpoint["ts"], - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "thread_id": "3", + }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3580,6 +4932,7 @@ def test_conditional_state_graph( ), } }, + "thread_id": "3", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3640,6 +4993,7 @@ def test_conditional_state_graph( ], } }, + "thread_id": "3", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3702,6 +5056,7 @@ def test_conditional_state_graph( ), } }, + "thread_id": "4", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -3762,6 +5117,7 @@ def test_conditional_state_graph( ], } }, + "thread_id": "4", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -4078,18 +5434,6 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: stream_mode="messages", ) ] == [ - ( - _AnyIdHumanMessage( - content="what is weather in sf", - ), - { - "langgraph_step": 0, - "langgraph_node": "__start__", - "langgraph_triggers": ["__start__"], - "langgraph_path": ("__pregel_pull", "__start__"), - "langgraph_checkpoint_ns": AnyStr("__start__:"), - }, - ), ( _AnyIdAIMessageChunk( content="", @@ -4115,7 +5459,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: "langgraph_step": 1, "langgraph_node": "agent", "langgraph_triggers": ["start:agent"], - "langgraph_path": ("__pregel_pull", "agent"), + "langgraph_path": (PULL, "agent"), "langgraph_checkpoint_ns": AnyStr("agent:"), "checkpoint_ns": AnyStr("agent:"), "ls_provider": "fakechatmodel", @@ -4132,7 +5476,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: "langgraph_step": 2, "langgraph_node": "tools", "langgraph_triggers": ["branch:agent:should_continue:tools"], - "langgraph_path": ("__pregel_pull", "tools"), + "langgraph_path": (PULL, "tools"), "langgraph_checkpoint_ns": AnyStr("tools:"), }, ), @@ -4174,7 +5518,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: "langgraph_step": 3, "langgraph_node": "agent", "langgraph_triggers": ["tools"], - "langgraph_path": ("__pregel_pull", "agent"), + "langgraph_path": (PULL, "agent"), "langgraph_checkpoint_ns": AnyStr("agent:"), "checkpoint_ns": AnyStr("agent:"), "ls_provider": "fakechatmodel", @@ -4191,7 +5535,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: "langgraph_step": 4, "langgraph_node": "tools", "langgraph_triggers": ["branch:agent:should_continue:tools"], - "langgraph_path": ("__pregel_pull", "tools"), + "langgraph_path": (PULL, "tools"), "langgraph_checkpoint_ns": AnyStr("tools:"), }, ), @@ -4205,7 +5549,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: "langgraph_step": 4, "langgraph_node": "tools", "langgraph_triggers": ["branch:agent:should_continue:tools"], - "langgraph_path": ("__pregel_pull", "tools"), + "langgraph_path": (PULL, "tools"), "langgraph_checkpoint_ns": AnyStr("tools:"), }, ), @@ -4217,7 +5561,7 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None: "langgraph_step": 5, "langgraph_node": "agent", "langgraph_triggers": ["tools"], - "langgraph_path": ("__pregel_pull", "agent"), + "langgraph_path": (PULL, "agent"), "langgraph_checkpoint_ns": AnyStr("agent:"), "checkpoint_ns": AnyStr("agent:"), "ls_provider": "fakechatmodel", @@ -4620,6 +5964,8 @@ def test_state_graph_packets( {"agent": {"messages": AIMessage(content="answer", id="ai3")}}, ] + # interrupt after agent + app_w_interrupt = workflow.compile( checkpointer=checkpointer, interrupt_after=["agent"], @@ -4650,6 +5996,9 @@ def test_state_graph_packets( {"__interrupt__": ()}, ] + if not FF_SEND_V2: + return + assert app_w_interrupt.get_state(config) == StateSnapshot( values={ "messages": [ @@ -4667,29 +6016,44 @@ def test_state_graph_packets( ), ] }, - tasks=(PregelTask(AnyStr(), "tools", (PUSH, 0)),), + tasks=( + PregelTask( + id=AnyStr(), + name="agent", + path=("__pregel_pull", "agent"), + error=None, + interrupts=(), + state=None, + result={ + "messages": AIMessage( + content="", + additional_kwargs={}, + response_metadata={}, + id="ai1", + tool_calls=[ + { + "name": "search_api", + "args": {"query": "query"}, + "id": "tool_call123", + "type": "tool_call", + } + ], + ) + }, + ), + PregelTask( + AnyStr(), "tools", (PUSH, ("__pregel_pull", "agent"), 2, AnyStr()) + ), + ), next=("tools",), config=(app_w_interrupt.checkpointer.get_tuple(config)).config, created_at=(app_w_interrupt.checkpointer.get_tuple(config)).checkpoint["ts"], metadata={ "parents": {}, "source": "loop", - "step": 1, - "writes": { - "agent": { - "messages": AIMessage( - id="ai1", - content="", - tool_calls=[ - { - "id": "tool_call123", - "name": "search_api", - "args": {"query": "query"}, - }, - ], - ) - } - }, + "step": 0, + "writes": None, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -4719,14 +6083,14 @@ def test_state_graph_packets( ), ] }, - tasks=(PregelTask(AnyStr(), "tools", (PUSH, 0)),), + tasks=(PregelTask(AnyStr(), "tools", (PUSH, (), 0, AnyStr())),), next=("tools",), config=app_w_interrupt.checkpointer.get_tuple(config).config, created_at=(app_w_interrupt.checkpointer.get_tuple(config)).checkpoint["ts"], metadata={ "parents": {}, "source": "update", - "step": 2, + "step": 1, "writes": { "agent": { "messages": AIMessage( @@ -4743,6 +6107,7 @@ def test_state_graph_packets( "something_extra": "hi there", } }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -4819,8 +6184,40 @@ def test_state_graph_packets( ] }, tasks=( - PregelTask(AnyStr(), "tools", (PUSH, 0)), - PregelTask(AnyStr(), "tools", (PUSH, 1)), + PregelTask( + id=AnyStr(), + name="agent", + path=("__pregel_pull", "agent"), + error=None, + interrupts=(), + state=None, + result={ + "messages": AIMessage( + "", + id="ai2", + tool_calls=[ + { + "name": "search_api", + "args": {"query": "another", "idx": 0}, + "id": "tool_call234", + "type": "tool_call", + }, + { + "name": "search_api", + "args": {"query": "a third one", "idx": 1}, + "id": "tool_call567", + "type": "tool_call", + }, + ], + ) + }, + ), + PregelTask( + AnyStr(), "tools", (PUSH, ("__pregel_pull", "agent"), 2, AnyStr()) + ), + PregelTask( + AnyStr(), "tools", (PUSH, ("__pregel_pull", "agent"), 3, AnyStr()) + ), ), next=("tools", "tools"), config=app_w_interrupt.checkpointer.get_tuple(config).config, @@ -4828,27 +6225,17 @@ def test_state_graph_packets( metadata={ "parents": {}, "source": "loop", - "step": 4, + "step": 2, "writes": { - "agent": { - "messages": AIMessage( - id="ai2", - content="", - tool_calls=[ - { - "id": "tool_call234", - "name": "search_api", - "args": {"query": "another", "idx": 0}, - }, - { - "id": "tool_call567", - "name": "search_api", - "args": {"query": "a third one", "idx": 1}, - }, - ], - ) + "tools": { + "messages": _AnyIdToolMessage( + content="result for a different query", + name="search_api", + tool_call_id="tool_call123", + ), }, }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -4892,13 +6279,337 @@ def test_state_graph_packets( metadata={ "parents": {}, "source": "update", - "step": 5, + "step": 3, "writes": { "agent": { "messages": AIMessage(content="answer", id="ai2"), "something_extra": "hi there", } }, + "thread_id": "1", + }, + parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, + ) + + # interrupt before tools + + app_w_interrupt = workflow.compile( + checkpointer=checkpointer, + interrupt_before=["tools"], + ) + config = {"configurable": {"thread_id": "2"}} + model.i = 0 + + assert [ + c + for c in app_w_interrupt.stream( + {"messages": HumanMessage(content="what is weather in sf")}, config + ) + ] == [ + { + "agent": { + "messages": AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + }, + ], + ) + } + }, + {"__interrupt__": ()}, + ] + + assert app_w_interrupt.get_state(config) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="what is weather in sf"), + AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + }, + ], + ), + ] + }, + tasks=( + PregelTask( + id=AnyStr(), + name="agent", + path=("__pregel_pull", "agent"), + error=None, + interrupts=(), + state=None, + result={ + "messages": AIMessage( + "", + id="ai1", + tool_calls=[ + { + "name": "search_api", + "args": {"query": "query"}, + "id": "tool_call123", + "type": "tool_call", + } + ], + ) + }, + ), + PregelTask( + AnyStr(), "tools", (PUSH, ("__pregel_pull", "agent"), 2, AnyStr()) + ), + ), + next=("tools",), + config=(app_w_interrupt.checkpointer.get_tuple(config)).config, + created_at=(app_w_interrupt.checkpointer.get_tuple(config)).checkpoint["ts"], + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "thread_id": "2", + }, + parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, + ) + + # modify ai message + last_message = (app_w_interrupt.get_state(config)).values["messages"][-1] + last_message.tool_calls[0]["args"]["query"] = "a different query" + app_w_interrupt.update_state( + config, {"messages": last_message, "something_extra": "hi there"} + ) + + # message was replaced instead of appended + assert app_w_interrupt.get_state(config) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="what is weather in sf"), + AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + }, + ], + ), + ] + }, + tasks=(PregelTask(AnyStr(), "tools", (PUSH, (), 0, AnyStr())),), + next=("tools",), + config=app_w_interrupt.checkpointer.get_tuple(config).config, + created_at=(app_w_interrupt.checkpointer.get_tuple(config)).checkpoint["ts"], + metadata={ + "parents": {}, + "source": "update", + "step": 1, + "writes": { + "agent": { + "messages": AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + }, + ], + ), + "something_extra": "hi there", + } + }, + "thread_id": "2", + }, + parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, + ) + + assert [c for c in app_w_interrupt.stream(None, config)] == [ + { + "tools": { + "messages": _AnyIdToolMessage( + content="result for a different query", + name="search_api", + tool_call_id="tool_call123", + ) + } + }, + { + "agent": { + "messages": AIMessage( + id="ai2", + content="", + tool_calls=[ + { + "id": "tool_call234", + "name": "search_api", + "args": {"query": "another", "idx": 0}, + }, + { + "id": "tool_call567", + "name": "search_api", + "args": {"query": "a third one", "idx": 1}, + }, + ], + ) + }, + }, + {"__interrupt__": ()}, + ] + + assert app_w_interrupt.get_state(config) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="what is weather in sf"), + AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + }, + ], + ), + _AnyIdToolMessage( + content="result for a different query", + name="search_api", + tool_call_id="tool_call123", + ), + AIMessage( + id="ai2", + content="", + tool_calls=[ + { + "id": "tool_call234", + "name": "search_api", + "args": {"query": "another", "idx": 0}, + }, + { + "id": "tool_call567", + "name": "search_api", + "args": {"query": "a third one", "idx": 1}, + }, + ], + ), + ] + }, + tasks=( + PregelTask( + id=AnyStr(), + name="agent", + path=("__pregel_pull", "agent"), + error=None, + interrupts=(), + state=None, + result={ + "messages": AIMessage( + "", + id="ai2", + tool_calls=[ + { + "name": "search_api", + "args": {"query": "another", "idx": 0}, + "id": "tool_call234", + "type": "tool_call", + }, + { + "name": "search_api", + "args": {"query": "a third one", "idx": 1}, + "id": "tool_call567", + "type": "tool_call", + }, + ], + ) + }, + ), + PregelTask( + AnyStr(), "tools", (PUSH, ("__pregel_pull", "agent"), 2, AnyStr()) + ), + PregelTask( + AnyStr(), "tools", (PUSH, ("__pregel_pull", "agent"), 3, AnyStr()) + ), + ), + next=("tools", "tools"), + config=app_w_interrupt.checkpointer.get_tuple(config).config, + created_at=(app_w_interrupt.checkpointer.get_tuple(config)).checkpoint["ts"], + metadata={ + "parents": {}, + "source": "loop", + "step": 2, + "writes": { + "tools": { + "messages": _AnyIdToolMessage( + content="result for a different query", + name="search_api", + tool_call_id="tool_call123", + ), + }, + }, + "thread_id": "2", + }, + parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, + ) + + app_w_interrupt.update_state( + config, + { + "messages": AIMessage(content="answer", id="ai2"), + "something_extra": "hi there", + }, + ) + + # replaces message even if object identity is different, as long as id is the same + assert app_w_interrupt.get_state(config) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="what is weather in sf"), + AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + }, + ], + ), + _AnyIdToolMessage( + content="result for a different query", + name="search_api", + tool_call_id="tool_call123", + ), + AIMessage(content="answer", id="ai2"), + ] + }, + tasks=(), + next=(), + config=app_w_interrupt.checkpointer.get_tuple(config).config, + created_at=(app_w_interrupt.checkpointer.get_tuple(config)).checkpoint["ts"], + metadata={ + "parents": {}, + "source": "update", + "step": 3, + "writes": { + "agent": { + "messages": AIMessage(content="answer", id="ai2"), + "something_extra": "hi there", + } + }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5188,6 +6899,7 @@ def test_message_graph( id="ai1", ) }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5234,6 +6946,7 @@ def test_message_graph( id="ai1", ) }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5316,6 +7029,7 @@ def test_message_graph( id="ai2", ) }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5356,6 +7070,7 @@ def test_message_graph( "source": "update", "step": 5, "writes": {"agent": AIMessage(content="answer", id="ai2")}, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5420,6 +7135,7 @@ def test_message_graph( id="ai1", ) }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5466,6 +7182,7 @@ def test_message_graph( id="ai1", ) }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5548,6 +7265,7 @@ def test_message_graph( id="ai2", ) }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5589,6 +7307,7 @@ def test_message_graph( "source": "update", "step": 5, "writes": {"agent": AIMessage(content="answer", id="ai2")}, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5630,6 +7349,7 @@ def test_message_graph( "source": "update", "step": 6, "writes": {"tools": UnsortedSequence("ai", "an extra message")}, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5918,6 +7638,7 @@ def test_root_graph( id="ai1", ) }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -5964,6 +7685,7 @@ def test_root_graph( id="ai1", ) }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -6047,6 +7769,7 @@ def test_root_graph( id="ai2", ) }, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -6088,6 +7811,7 @@ def test_root_graph( "source": "update", "step": 5, "writes": {"agent": AIMessage(content="answer", id="ai2")}, + "thread_id": "1", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -6152,6 +7876,7 @@ def test_root_graph( id="ai1", ) }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -6198,6 +7923,7 @@ def test_root_graph( id="ai1", ) }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -6281,6 +8007,7 @@ def test_root_graph( id="ai2", ) }, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -6321,6 +8048,7 @@ def test_root_graph( "source": "update", "step": 5, "writes": {"agent": AIMessage(content="answer", id="ai2")}, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -6362,6 +8090,7 @@ def test_root_graph( "source": "update", "step": 6, "writes": {"tools": UnsortedSequence("ai", "an extra message")}, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -6434,6 +8163,7 @@ def test_root_graph( "source": "update", "step": 6, "writes": {"tools": UnsortedSequence("ai", "an extra message")}, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -6470,7 +8200,7 @@ def test_root_graph( ), AIMessage(content="answer", id="ai2"), AIMessage( - content="an extra message", id="00000000-0000-4000-8000-000000000091" + content="an extra message", id="00000000-0000-4000-8000-000000000092" ), HumanMessage(content="what is weather in la"), ], @@ -6718,8 +8448,10 @@ def test_dynamic_interrupt( nonlocal tool_two_node_count tool_two_node_count += 1 if s["market"] == "DE": - raise NodeInterrupt("Just because...") - return {"my_key": " all good"} + answer = interrupt("Just because...") + else: + answer = " all good" + return {"my_key": answer} tool_two_graph = StateGraph(State) tool_two_graph.add_node("tool_two", tool_two_node, retry=RetryPolicy()) @@ -6751,6 +8483,28 @@ def test_dynamic_interrupt( with pytest.raises(ValueError, match="thread_id"): tool_two.invoke({"my_key": "value", "market": "DE"}) + # flow: interrupt -> resume with answer + thread2 = {"configurable": {"thread_id": "2"}} + # stop when about to enter node + assert [ + c for c in tool_two.stream({"my_key": "value ⛰️", "market": "DE"}, thread2) + ] == [ + { + "__interrupt__": ( + Interrupt( + value="Just because...", + resumable=True, + ns=[AnyStr("tool_two:")], + ), + ) + }, + ] + # resume with answer + assert [c for c in tool_two.stream(Command(resume=" my answer"), thread2)] == [ + {"tool_two": {"my_key": " my answer"}}, + ] + + # flow: interrupt -> clear tasks thread1 = {"configurable": {"thread_id": "1"}} # stop when about to enter node assert tool_two.invoke({"my_key": "value ⛰️", "market": "DE"}, thread1) == { @@ -6763,12 +8517,14 @@ def test_dynamic_interrupt( "source": "loop", "step": 0, "writes": None, + "thread_id": "1", }, { "parents": {}, "source": "input", "step": -1, "writes": {"__start__": {"my_key": "value ⛰️", "market": "DE"}}, + "thread_id": "1", }, ] assert tool_two.get_state(thread1) == StateSnapshot( @@ -6779,12 +8535,203 @@ def test_dynamic_interrupt( AnyStr(), "tool_two", (PULL, "tool_two"), - interrupts=(Interrupt("Just because..."),), + interrupts=( + Interrupt( + value="Just because...", + resumable=True, + ns=[AnyStr("tool_two:")], + ), + ), ), ), config=tool_two.checkpointer.get_tuple(thread1).config, created_at=tool_two.checkpointer.get_tuple(thread1).checkpoint["ts"], - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "thread_id": "1", + }, + parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, + ) + # clear the interrupt and next tasks + tool_two.update_state(thread1, None, as_node=END) + # interrupt and next tasks are cleared + assert tool_two.get_state(thread1) == StateSnapshot( + values={"my_key": "value ⛰️", "market": "DE"}, + next=(), + tasks=(), + config=tool_two.checkpointer.get_tuple(thread1).config, + created_at=tool_two.checkpointer.get_tuple(thread1).checkpoint["ts"], + metadata={ + "parents": {}, + "source": "update", + "step": 1, + "writes": {}, + "thread_id": "1", + }, + parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, + ) + + +@pytest.mark.skipif(not FF_SEND_V2, reason="send v2 is not enabled") +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) +def test_copy_checkpoint( + request: pytest.FixtureRequest, checkpointer_name: str +) -> None: + checkpointer = request.getfixturevalue(f"checkpointer_{checkpointer_name}") + + class State(TypedDict): + my_key: Annotated[str, operator.add] + market: str + + def tool_one(s: State) -> State: + return {"my_key": " one"} + + tool_two_node_count = 0 + + def tool_two_node(s: State) -> State: + nonlocal tool_two_node_count + tool_two_node_count += 1 + if s["market"] == "DE": + answer = interrupt("Just because...") + else: + answer = " all good" + return {"my_key": answer} + + def start(state: State) -> list[Union[Send, str]]: + return ["tool_two", Send("tool_one", state)] + + tool_two_graph = StateGraph(State) + tool_two_graph.add_node("tool_two", tool_two_node, retry=RetryPolicy()) + tool_two_graph.add_node("tool_one", tool_one) + tool_two_graph.set_conditional_entry_point(start) + tool_two = tool_two_graph.compile() + + tracer = FakeTracer() + assert tool_two.invoke( + {"my_key": "value", "market": "DE"}, {"callbacks": [tracer]} + ) == { + "my_key": "value one", + "market": "DE", + } + assert tool_two_node_count == 1, "interrupts aren't retried" + assert len(tracer.runs) == 1 + run = tracer.runs[0] + assert run.end_time is not None + assert run.error is None + assert run.outputs == {"market": "DE", "my_key": "value one"} + + assert tool_two.invoke({"my_key": "value", "market": "US"}) == { + "my_key": "value one all good", + "market": "US", + } + + tool_two = tool_two_graph.compile(checkpointer=checkpointer) + + # missing thread_id + with pytest.raises(ValueError, match="thread_id"): + tool_two.invoke({"my_key": "value", "market": "DE"}) + + # flow: interrupt -> resume with answer + thread2 = {"configurable": {"thread_id": "2"}} + # stop when about to enter node + assert [ + c for c in tool_two.stream({"my_key": "value ⛰️", "market": "DE"}, thread2) + ] == [ + { + "tool_one": {"my_key": " one"}, + }, + { + "__interrupt__": ( + Interrupt( + value="Just because...", + resumable=True, + ns=[AnyStr("tool_two:")], + ), + ) + }, + ] + # resume with answer + assert [c for c in tool_two.stream(Command(resume=" my answer"), thread2)] == [ + {"tool_two": {"my_key": " my answer"}}, + ] + + # flow: interrupt -> clear tasks + thread1 = {"configurable": {"thread_id": "1"}} + # stop when about to enter node + assert tool_two.invoke({"my_key": "value ⛰️", "market": "DE"}, thread1) == { + "my_key": "value ⛰️ one", + "market": "DE", + } + assert [c.metadata for c in tool_two.checkpointer.list(thread1)] == [ + { + "parents": {}, + "source": "loop", + "step": 0, + "writes": {"tool_one": {"my_key": " one"}}, + "thread_id": "1", + }, + { + "parents": {}, + "source": "input", + "step": -1, + "writes": {"__start__": {"my_key": "value ⛰️", "market": "DE"}}, + "thread_id": "1", + }, + ] + assert tool_two.get_state(thread1) == StateSnapshot( + values={"my_key": "value ⛰️ one", "market": "DE"}, + next=("tool_two",), + tasks=( + PregelTask( + AnyStr(), + "tool_two", + (PULL, "tool_two"), + interrupts=( + Interrupt( + value="Just because...", + resumable=True, + ns=[AnyStr("tool_two:")], + ), + ), + ), + ), + config=tool_two.checkpointer.get_tuple(thread1).config, + created_at=tool_two.checkpointer.get_tuple(thread1).checkpoint["ts"], + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": {"tool_one": {"my_key": " one"}}, + "thread_id": "1", + }, + parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, + ) + # clear the interrupt and next tasks + tool_two.update_state(thread1, None) + # interrupt is cleared, next task is kept + assert tool_two.get_state(thread1) == StateSnapshot( + values={"my_key": "value ⛰️ one", "market": "DE"}, + next=("tool_two",), + tasks=( + PregelTask( + AnyStr(), + "tool_two", + (PULL, "tool_two"), + interrupts=(), + ), + ), + config=tool_two.checkpointer.get_tuple(thread1).config, + created_at=tool_two.checkpointer.get_tuple(thread1).checkpoint["ts"], + metadata={ + "parents": {}, + "source": "update", + "step": 1, + "writes": {}, + "thread_id": "1", + }, parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, ) @@ -6861,12 +8808,16 @@ def test_start_branch_then( "source": "loop", "step": 0, "writes": None, + "assistant_id": "a", + "thread_id": "1", }, { "parents": {}, "source": "input", "step": -1, "writes": {"__start__": {"my_key": "value ⛰️", "market": "DE"}}, + "assistant_id": "a", + "thread_id": "1", }, ] assert tool_two.get_state(thread1) == StateSnapshot( @@ -6875,7 +8826,14 @@ def test_start_branch_then( next=("tool_two_slow",), config=tool_two.checkpointer.get_tuple(thread1).config, created_at=tool_two.checkpointer.get_tuple(thread1).checkpoint["ts"], - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "assistant_id": "a", + "thread_id": "1", + }, parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, ) # resume, for same result as above @@ -6894,6 +8852,8 @@ def test_start_branch_then( "source": "loop", "step": 1, "writes": {"tool_two_slow": {"my_key": " slow"}}, + "assistant_id": "a", + "thread_id": "1", }, parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, ) @@ -6910,7 +8870,14 @@ def test_start_branch_then( next=("tool_two_fast",), config=tool_two.checkpointer.get_tuple(thread2).config, created_at=tool_two.checkpointer.get_tuple(thread2).checkpoint["ts"], - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "assistant_id": "a", + "thread_id": "2", + }, parent_config=[*tool_two.checkpointer.list(thread2, limit=2)][-1].config, ) # resume, for same result as above @@ -6929,6 +8896,8 @@ def test_start_branch_then( "source": "loop", "step": 1, "writes": {"tool_two_fast": {"my_key": " fast"}}, + "assistant_id": "a", + "thread_id": "2", }, parent_config=[*tool_two.checkpointer.list(thread2, limit=2)][-1].config, ) @@ -6945,7 +8914,14 @@ def test_start_branch_then( next=("tool_two_fast",), config=tool_two.checkpointer.get_tuple(thread3).config, created_at=tool_two.checkpointer.get_tuple(thread3).checkpoint["ts"], - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "assistant_id": "b", + "thread_id": "3", + }, parent_config=[*tool_two.checkpointer.list(thread3, limit=2)][-1].config, ) # update state @@ -6961,6 +8937,8 @@ def test_start_branch_then( "source": "update", "step": 1, "writes": {START: {"my_key": "key"}}, + "assistant_id": "b", + "thread_id": "3", }, parent_config=[*tool_two.checkpointer.list(thread3, limit=2)][-1].config, ) @@ -6980,6 +8958,8 @@ def test_start_branch_then( "source": "loop", "step": 2, "writes": {"tool_two_fast": {"my_key": " fast"}}, + "assistant_id": "b", + "thread_id": "3", }, parent_config=[*tool_two.checkpointer.list(thread3, limit=2)][-1].config, ) @@ -7053,6 +9033,7 @@ def test_branch_then( "source": "input", "step": -1, "writes": {"__start__": {"my_key": "value", "market": "DE"}}, + "thread_id": "10", }, "parent_config": None, "next": ["__start__"], @@ -7091,6 +9072,7 @@ def test_branch_then( "source": "loop", "step": 0, "writes": None, + "thread_id": "10", }, "parent_config": { "tags": [], @@ -7157,6 +9139,7 @@ def test_branch_then( "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "10", }, "parent_config": { "tags": [], @@ -7228,6 +9211,7 @@ def test_branch_then( "source": "loop", "step": 2, "writes": {"tool_two_slow": {"my_key": " slow"}}, + "thread_id": "10", }, "parent_config": { "tags": [], @@ -7294,6 +9278,7 @@ def test_branch_then( "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "10", }, "parent_config": { "tags": [], @@ -7337,6 +9322,7 @@ def test_branch_then( "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "1", }, parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, ) @@ -7356,6 +9342,7 @@ def test_branch_then( "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "1", }, parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, ) @@ -7377,6 +9364,7 @@ def test_branch_then( "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "2", }, parent_config=[*tool_two.checkpointer.list(thread2, limit=2)][-1].config, ) @@ -7396,6 +9384,7 @@ def test_branch_then( "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "2", }, parent_config=[*tool_two.checkpointer.list(thread2, limit=2)][-1].config, ) @@ -7425,6 +9414,7 @@ def test_branch_then( "source": "loop", "step": 2, "writes": {"tool_two_slow": {"my_key": " slow"}}, + "thread_id": "11", }, parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, ) @@ -7445,6 +9435,7 @@ def test_branch_then( "source": "update", "step": 3, "writes": {"tool_two_slow": {"my_key": "er"}}, + "thread_id": "11", }, parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, ) @@ -7474,6 +9465,7 @@ def test_branch_then( "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "21", }, parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, ) @@ -7493,6 +9485,7 @@ def test_branch_then( "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "21", }, parent_config=[*tool_two.checkpointer.list(thread1, limit=2)][-1].config, ) @@ -7514,6 +9507,7 @@ def test_branch_then( "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "22", }, parent_config=[*tool_two.checkpointer.list(thread2, limit=2)][-1].config, ) @@ -7533,6 +9527,7 @@ def test_branch_then( "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "22", }, parent_config=[*tool_two.checkpointer.list(thread2, limit=2)][-1].config, ) @@ -7552,6 +9547,7 @@ def test_branch_then( "source": "update", "step": 0, "writes": {START: {"my_key": "key", "market": "DE"}}, + "thread_id": "23", }, parent_config=None, ) @@ -7572,6 +9568,7 @@ def test_branch_then( "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "23", }, parent_config=uconfig, ) @@ -7591,6 +9588,7 @@ def test_branch_then( "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "23", }, parent_config=[*tool_two.checkpointer.list(thread3, limit=2)][-1].config, ) @@ -7718,6 +9716,7 @@ def test_in_one_fan_out_state_graph_waiting_edge( "source": "update", "step": 4, "writes": {"retriever_one": {"docs": ["doc5"]}}, + "thread_id": "2", }, parent_config=[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config, ) @@ -8701,14 +10700,14 @@ def test_stream_subgraphs_during_execution( ), (FloatBetween(0.2, 0.3), ((), {"outer_1": {"my_key": " and parallel"}})), ( - FloatBetween(0.5, 0.6), + FloatBetween(0.5, 0.8), ( (AnyStr("inner:"),), {"inner_2": {"my_key": " and there", "my_other_key": "got here"}}, ), ), - (FloatBetween(0.5, 0.6), ((), {"inner": {"my_key": "got here and there"}})), - (FloatBetween(0.5, 0.6), ((), {"outer_2": {"my_key": " and back again"}})), + (FloatBetween(0.5, 0.8), ((), {"inner": {"my_key": "got here and there"}})), + (FloatBetween(0.5, 0.8), ((), {"outer_2": {"my_key": " and back again"}})), ] @@ -8796,7 +10795,7 @@ def test_nested_graph_interrupts_parallel( # test invoke w/ nested interrupt config = {"configurable": {"thread_id": "1"}} assert app.invoke({"my_key": ""}, config, debug=True) == { - "my_key": "", + "my_key": " and parallel", } assert app.invoke(None, config, debug=True) == { @@ -8824,6 +10823,7 @@ def test_nested_graph_interrupts_parallel( config = {"configurable": {"thread_id": "3"}} assert [*app.stream({"my_key": ""}, config, stream_mode="values")] == [ {"my_key": ""}, + {"my_key": " and parallel"}, ] assert [*app.stream(None, config, stream_mode="values")] == [ {"my_key": ""}, @@ -8840,6 +10840,7 @@ def test_nested_graph_interrupts_parallel( # while we're waiting for the node w/ interrupt inside to finish assert [*app.stream(None, config, stream_mode="values")] == [ {"my_key": ""}, + {"my_key": " and parallel"}, ] assert [*app.stream(None, config, stream_mode="values")] == [ {"my_key": ""}, @@ -8851,7 +10852,8 @@ def test_nested_graph_interrupts_parallel( app = graph.compile(checkpointer=checkpointer, interrupt_after=["outer_1"]) config = {"configurable": {"thread_id": "5"}} assert [*app.stream({"my_key": ""}, config, stream_mode="values")] == [ - {"my_key": ""} + {"my_key": ""}, + {"my_key": " and parallel"}, ] assert [*app.stream(None, config, stream_mode="values")] == [ {"my_key": ""}, @@ -8929,15 +10931,27 @@ def test_doubly_nested_graph_interrupts( } # test stream updates w/ nested interrupt - config = {"configurable": {"thread_id": "2"}} + nodes: list[str] = [] + config = { + "configurable": {"thread_id": "2", CONFIG_KEY_NODE_FINISHED: nodes.append} + } assert [*app.stream({"my_key": "my value"}, config)] == [ {"parent_1": {"my_key": "hi my value"}}, {"__interrupt__": ()}, ] + assert nodes == ["parent_1", "grandchild_1"] assert [*app.stream(None, config)] == [ {"child": {"my_key": "hi my value here and there"}}, {"parent_2": {"my_key": "hi my value here and there and back again"}}, ] + assert nodes == [ + "parent_1", + "grandchild_1", + "grandchild_2", + "child_1", + "child", + "parent_2", + ] # test stream values w/ nested interrupt config = {"configurable": {"thread_id": "3"}} @@ -9032,6 +11046,7 @@ def test_nested_graph_state( "source": "loop", "writes": {"outer_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9085,6 +11100,13 @@ def test_nested_graph_state( } }, "step": 1, + "thread_id": "1", + "checkpoint_ns": AnyStr("inner:"), + "langgraph_node": "inner", + "langgraph_path": [PULL, "inner"], + "langgraph_step": 2, + "langgraph_triggers": ["outer_1"], + "langgraph_checkpoint_ns": AnyStr("inner:"), }, created_at=AnyStr(), parent_config={ @@ -9113,6 +11135,7 @@ def test_nested_graph_state( "source": "loop", "writes": {"outer_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9154,6 +11177,7 @@ def test_nested_graph_state( "source": "loop", "writes": {"outer_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9182,7 +11206,13 @@ def test_nested_graph_state( "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "writes": None, "step": 0}, + metadata={ + "parents": {}, + "source": "loop", + "writes": None, + "step": 0, + "thread_id": "1", + }, created_at=AnyStr(), parent_config={ "configurable": { @@ -9215,6 +11245,7 @@ def test_nested_graph_state( "source": "input", "writes": {"__start__": {"my_key": "my value"}}, "step": -1, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -9246,6 +11277,13 @@ def test_nested_graph_state( }, "step": 1, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("inner:"), + "langgraph_node": "inner", + "langgraph_path": [PULL, "inner"], + "langgraph_step": 2, + "langgraph_triggers": ["outer_1"], + "langgraph_checkpoint_ns": AnyStr("inner:"), }, created_at=AnyStr(), parent_config={ @@ -9278,6 +11316,13 @@ def test_nested_graph_state( "writes": None, "step": 0, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("inner:"), + "langgraph_node": "inner", + "langgraph_path": [PULL, "inner"], + "langgraph_step": 2, + "langgraph_triggers": ["outer_1"], + "langgraph_checkpoint_ns": AnyStr("inner:"), }, created_at=AnyStr(), parent_config={ @@ -9320,6 +11365,13 @@ def test_nested_graph_state( "writes": {"__start__": {"my_key": "hi my value"}}, "step": -1, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("inner:"), + "langgraph_node": "inner", + "langgraph_path": [PULL, "inner"], + "langgraph_step": 2, + "langgraph_triggers": ["outer_1"], + "langgraph_checkpoint_ns": AnyStr("inner:"), }, created_at=AnyStr(), parent_config=None, @@ -9355,6 +11407,7 @@ def test_nested_graph_state( "outer_2": {"my_key": "hi my value here and there and back again"} }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9386,6 +11439,7 @@ def test_nested_graph_state( "outer_2": {"my_key": "hi my value here and there and back again"} }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9419,6 +11473,7 @@ def test_nested_graph_state( "source": "loop", "writes": {"inner": {"my_key": "hi my value here and there"}}, "step": 2, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9455,6 +11510,7 @@ def test_nested_graph_state( "source": "loop", "writes": {"outer_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9483,7 +11539,13 @@ def test_nested_graph_state( "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "writes": None, "step": 0}, + metadata={ + "parents": {}, + "source": "loop", + "writes": None, + "step": 0, + "thread_id": "1", + }, created_at=AnyStr(), parent_config={ "configurable": { @@ -9516,6 +11578,7 @@ def test_nested_graph_state( "source": "input", "writes": {"__start__": {"my_key": "my value"}}, "step": -1, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -9622,6 +11685,7 @@ def test_doubly_nested_graph_state( "source": "loop", "writes": {"parent_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9663,6 +11727,7 @@ def test_doubly_nested_graph_state( "source": "loop", "writes": None, "step": 0, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9709,6 +11774,13 @@ def test_doubly_nested_graph_state( "source": "loop", "writes": {"grandchild_1": {"my_key": "hi my value here"}}, "step": 1, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [PULL, AnyStr("child_1")], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -9779,6 +11851,16 @@ def test_doubly_nested_graph_state( "grandchild_1": {"my_key": "hi my value here"} }, "step": 1, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -9816,6 +11898,13 @@ def test_doubly_nested_graph_state( "source": "loop", "writes": None, "step": 0, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child", + "langgraph_path": [PULL, AnyStr("child")], + "langgraph_step": 2, + "langgraph_triggers": [AnyStr("parent_1")], + "langgraph_checkpoint_ns": AnyStr("child:"), }, created_at=AnyStr(), parent_config={ @@ -9844,6 +11933,7 @@ def test_doubly_nested_graph_state( "source": "loop", "writes": {"parent_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9854,7 +11944,7 @@ def test_doubly_nested_graph_state( } }, ) - # resume + # # resume assert [c for c in app.stream(None, config, subgraphs=True)] == [ ( (AnyStr("child:"), AnyStr("child_1:")), @@ -9886,6 +11976,7 @@ def test_doubly_nested_graph_state( "parent_2": {"my_key": "hi my value here and there and back again"} }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9918,6 +12009,7 @@ def test_doubly_nested_graph_state( "parent_2": {"my_key": "hi my value here and there and back again"} }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9943,6 +12035,7 @@ def test_doubly_nested_graph_state( "writes": {"child": {"my_key": "hi my value here and there"}}, "step": 2, "parents": {}, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9990,6 +12083,7 @@ def test_doubly_nested_graph_state( "source": "loop", "writes": {"parent_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -10010,7 +12104,13 @@ def test_doubly_nested_graph_state( "checkpoint_id": AnyStr(), } }, - metadata={"source": "loop", "writes": None, "step": 0, "parents": {}}, + metadata={ + "source": "loop", + "writes": None, + "step": 0, + "parents": {}, + "thread_id": "1", + }, created_at=AnyStr(), parent_config={ "configurable": { @@ -10043,6 +12143,7 @@ def test_doubly_nested_graph_state( "writes": {"__start__": {"my_key": "my value"}}, "step": -1, "parents": {}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -10077,6 +12178,13 @@ def test_doubly_nested_graph_state( "writes": {"child_1": {"my_key": "hi my value here and there"}}, "step": 1, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child", + "langgraph_path": [PULL, AnyStr("child")], + "langgraph_step": 2, + "langgraph_triggers": [AnyStr("parent_1")], + "langgraph_checkpoint_ns": AnyStr("child:"), }, created_at=AnyStr(), parent_config={ @@ -10109,6 +12217,13 @@ def test_doubly_nested_graph_state( "writes": None, "step": 0, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child", + "langgraph_path": [PULL, AnyStr("child")], + "langgraph_step": 2, + "langgraph_triggers": [AnyStr("parent_1")], + "langgraph_checkpoint_ns": AnyStr("child:"), }, created_at=AnyStr(), parent_config={ @@ -10154,6 +12269,13 @@ def test_doubly_nested_graph_state( "writes": {"__start__": {"my_key": "hi my value"}}, "step": -1, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child", + "langgraph_path": [PULL, AnyStr("child")], + "langgraph_step": 2, + "langgraph_triggers": [AnyStr("parent_1")], + "langgraph_checkpoint_ns": AnyStr("child:"), }, created_at=AnyStr(), parent_config=None, @@ -10197,6 +12319,16 @@ def test_doubly_nested_graph_state( AnyStr("child:"): AnyStr(), } ), + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -10242,6 +12374,16 @@ def test_doubly_nested_graph_state( AnyStr("child:"): AnyStr(), } ), + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -10294,6 +12436,16 @@ def test_doubly_nested_graph_state( AnyStr("child:"): AnyStr(), } ), + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -10346,6 +12498,16 @@ def test_doubly_nested_graph_state( AnyStr("child:"): AnyStr(), } ), + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config=None, @@ -10426,13 +12588,37 @@ def test_send_to_nested_graphs( # check state outer_state = graph.get_state(config) + + if not FF_SEND_V2: + # update state of dogs joke graph + graph.update_state(outer_state.tasks[1].state, {"subject": "turtles - hohoho"}) + + # continue past interrupt + assert sorted( + graph.stream(None, config=config), + key=lambda d: d["generate_joke"]["jokes"][0], + ) == [ + {"generate_joke": {"jokes": ["Joke about cats - hohoho"]}}, + {"generate_joke": {"jokes": ["Joke about turtles - hohoho"]}}, + ] + return + assert outer_state == StateSnapshot( values={"subjects": ["cats", "dogs"], "jokes": []}, tasks=( + PregelTask( + id=AnyStr(), + name="__start__", + path=("__pregel_pull", "__start__"), + error=None, + interrupts=(), + state=None, + result={"subjects": ["cats", "dogs"]}, + ), PregelTask( AnyStr(), "generate_joke", - (PUSH, 0), + (PUSH, ("__pregel_pull", "__start__"), 1, AnyStr()), state={ "configurable": { "thread_id": "1", @@ -10443,7 +12629,7 @@ def test_send_to_nested_graphs( PregelTask( AnyStr(), "generate_joke", - (PUSH, 1), + (PUSH, ("__pregel_pull", "__start__"), 2, AnyStr()), state={ "configurable": { "thread_id": "1", @@ -10460,18 +12646,18 @@ def test_send_to_nested_graphs( "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "writes": None, "step": 0}, - created_at=AnyStr(), - parent_config={ - "configurable": { - "thread_id": "1", - "checkpoint_ns": "", - "checkpoint_id": AnyStr(), - } + metadata={ + "parents": {}, + "source": "input", + "writes": {"__start__": {"subjects": ["cats", "dogs"]}}, + "step": -1, + "thread_id": "1", }, + created_at=AnyStr(), + parent_config=None, ) # check state of each of the inner tasks - assert graph.get_state(outer_state.tasks[0].state) == StateSnapshot( + assert graph.get_state(outer_state.tasks[1].state) == StateSnapshot( values={"subject": "cats - hohoho", "jokes": []}, next=("generate",), config={ @@ -10492,6 +12678,13 @@ def test_send_to_nested_graphs( "source": "loop", "writes": {"edit": None}, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("generate_joke:"), + "langgraph_checkpoint_ns": AnyStr("generate_joke:"), + "langgraph_node": "generate_joke", + "langgraph_path": [PUSH, ["__pregel_pull", "__start__"], 1, AnyStr()], + "langgraph_step": 0, + "langgraph_triggers": [PUSH], }, created_at=AnyStr(), parent_config={ @@ -10509,7 +12702,7 @@ def test_send_to_nested_graphs( }, tasks=(PregelTask(id=AnyStr(""), name="generate", path=(PULL, "generate")),), ) - assert graph.get_state(outer_state.tasks[1].state) == StateSnapshot( + assert graph.get_state(outer_state.tasks[2].state) == StateSnapshot( values={"subject": "dogs - hohoho", "jokes": []}, next=("generate",), config={ @@ -10530,6 +12723,13 @@ def test_send_to_nested_graphs( "source": "loop", "writes": {"edit": None}, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("generate_joke:"), + "langgraph_checkpoint_ns": AnyStr("generate_joke:"), + "langgraph_node": "generate_joke", + "langgraph_path": [PUSH, ["__pregel_pull", "__start__"], 2, AnyStr()], + "langgraph_step": 0, + "langgraph_triggers": [PUSH], }, created_at=AnyStr(), parent_config={ @@ -10548,7 +12748,9 @@ def test_send_to_nested_graphs( tasks=(PregelTask(id=AnyStr(""), name="generate", path=(PULL, "generate")),), ) # update state of dogs joke graph - graph.update_state(outer_state.tasks[1].state, {"subject": "turtles - hohoho"}) + graph.update_state( + outer_state.tasks[2 if FF_SEND_V2 else 1].state, {"subject": "turtles - hohoho"} + ) # continue past interrupt assert sorted( @@ -10582,7 +12784,8 @@ def test_send_to_nested_graphs( {"jokes": ["Joke about turtles - hohoho"]}, ] }, - "step": 1, + "step": 0, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -10623,7 +12826,8 @@ def test_send_to_nested_graphs( {"jokes": ["Joke about turtles - hohoho"]}, ] }, - "step": 1, + "step": 0, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -10634,63 +12838,44 @@ def test_send_to_nested_graphs( } }, ), - StateSnapshot( - values={"subjects": ["cats", "dogs"], "jokes": []}, - tasks=( - PregelTask( - AnyStr(), - "generate_joke", - (PUSH, 0), - state={ - "configurable": { - "thread_id": "1", - "checkpoint_ns": AnyStr("generate_joke:"), - } - }, - result={"jokes": ["Joke about cats - hohoho"]}, - ), - PregelTask( - AnyStr(), - "generate_joke", - (PUSH, 1), - state={ - "configurable": { - "thread_id": "1", - "checkpoint_ns": AnyStr("generate_joke:"), - } - }, - result={"jokes": ["Joke about turtles - hohoho"]}, - ), - ), - next=("generate_joke", "generate_joke"), - config={ - "configurable": { - "thread_id": "1", - "checkpoint_ns": "", - "checkpoint_id": AnyStr(), - } - }, - metadata={"parents": {}, "source": "loop", "writes": None, "step": 0}, - created_at=AnyStr(), - parent_config={ - "configurable": { - "thread_id": "1", - "checkpoint_ns": "", - "checkpoint_id": AnyStr(), - } - }, - ), StateSnapshot( values={"jokes": []}, tasks=( PregelTask( - AnyStr(), - "__start__", - (PULL, "__start__"), + id=AnyStr(), + name="__start__", + path=("__pregel_pull", "__start__"), + error=None, + interrupts=(), + state=None, result={"subjects": ["cats", "dogs"]}, ), + PregelTask( + AnyStr(), + "generate_joke", + (PUSH, ("__pregel_pull", "__start__"), 1, AnyStr()), + state={ + "configurable": { + "thread_id": "1", + "checkpoint_ns": AnyStr("generate_joke:"), + } + }, + result={"jokes": ["Joke about cats - hohoho"]}, + ), + PregelTask( + AnyStr(), + "generate_joke", + (PUSH, ("__pregel_pull", "__start__"), 2, AnyStr()), + state={ + "configurable": { + "thread_id": "1", + "checkpoint_ns": AnyStr("generate_joke:"), + } + }, + result={"jokes": ["Joke about turtles - hohoho"]}, + ), ), - next=("__start__",), + next=("__start__", "generate_joke", "generate_joke"), config={ "configurable": { "thread_id": "1", @@ -10703,6 +12888,7 @@ def test_send_to_nested_graphs( "source": "input", "writes": {"__start__": {"subjects": ["cats", "dogs"]}}, "step": -1, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -10864,6 +13050,7 @@ def test_weather_subgraph( "writes": {"router_node": {"route": "weather"}}, "step": 1, "parents": {}, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -10951,6 +13138,7 @@ def test_weather_subgraph( "writes": {"router_node": {"route": "weather"}}, "step": 1, "parents": {}, + "thread_id": "14", }, created_at=AnyStr(), parent_config={ @@ -10991,6 +13179,15 @@ def test_weather_subgraph( "writes": {"model_node": {"city": "San Francisco"}}, "step": 1, "parents": {"": AnyStr()}, + "thread_id": "14", + "checkpoint_ns": AnyStr("weather_graph:"), + "langgraph_node": "weather_graph", + "langgraph_path": [PULL, "weather_graph"], + "langgraph_step": 2, + "langgraph_triggers": [ + "branch:router_node:route_after_prediction:weather_graph" + ], + "langgraph_checkpoint_ns": AnyStr("weather_graph:"), }, created_at=AnyStr(), parent_config={ @@ -11041,6 +13238,7 @@ def test_weather_subgraph( "writes": {"router_node": {"route": "weather"}}, "step": 1, "parents": {}, + "thread_id": "14", }, created_at=AnyStr(), parent_config={ @@ -11078,14 +13276,24 @@ def test_weather_subgraph( } }, metadata={ - "source": "update", "step": 2, + "source": "update", "writes": { "weather_node": { "messages": [{"role": "assistant", "content": "rainy"}] } }, "parents": {"": AnyStr()}, + "thread_id": "14", + "checkpoint_id": AnyStr(), + "checkpoint_ns": AnyStr("weather_graph:"), + "langgraph_node": "weather_graph", + "langgraph_path": [PULL, "weather_graph"], + "langgraph_step": 2, + "langgraph_triggers": [ + "branch:router_node:route_after_prediction:weather_graph" + ], + "langgraph_checkpoint_ns": AnyStr("weather_graph:"), }, created_at=AnyStr(), parent_config={ @@ -12021,3 +14229,152 @@ def test_debug_nested_subgraphs(): assert stream_task["interrupts"] == history_task.interrupts assert stream_task.get("error") == history_task.error assert stream_task.get("state") == history_task.state + + +def test_add_sequence(): + class State(TypedDict): + foo: Annotated[list[str], operator.add] + bar: str + + def step1(state: State): + return {"foo": ["step1"], "bar": "baz"} + + def step2(state: State): + return {"foo": ["step2"]} + + # test raising if less than 1 steps + with pytest.raises(ValueError): + StateGraph(State).add_sequence([]) + + # test raising if duplicate step names + with pytest.raises(ValueError): + StateGraph(State).add_sequence([step1, step1]) + + with pytest.raises(ValueError): + StateGraph(State).add_sequence([("foo", step1), ("foo", step1)]) + + # test unnamed steps + builder = StateGraph(State) + builder.add_sequence([step1, step2]) + builder.add_edge(START, "step1") + graph = builder.compile() + result = graph.invoke({"foo": []}) + assert result == {"foo": ["step1", "step2"], "bar": "baz"} + stream_chunks = list(graph.stream({"foo": []})) + assert stream_chunks == [ + {"step1": {"foo": ["step1"], "bar": "baz"}}, + {"step2": {"foo": ["step2"]}}, + ] + + # test named steps + builder_named_steps = StateGraph(State) + builder_named_steps.add_sequence([("meow1", step1), ("meow2", step2)]) + builder_named_steps.add_edge(START, "meow1") + graph_named_steps = builder_named_steps.compile() + result = graph_named_steps.invoke({"foo": []}) + stream_chunks = list(graph_named_steps.stream({"foo": []})) + assert result == {"foo": ["step1", "step2"], "bar": "baz"} + assert stream_chunks == [ + {"meow1": {"foo": ["step1"], "bar": "baz"}}, + {"meow2": {"foo": ["step2"]}}, + ] + + builder_named_steps = StateGraph(State) + builder_named_steps.add_sequence( + [ + ("meow1", lambda state: {"foo": ["foo"]}), + ("meow2", lambda state: {"bar": state["foo"][0] + "bar"}), + ], + ) + builder_named_steps.add_edge(START, "meow1") + graph_named_steps = builder_named_steps.compile() + result = graph_named_steps.invoke({"foo": []}) + stream_chunks = list(graph_named_steps.stream({"foo": []})) + # filtered by output schema + assert result == {"bar": "foobar", "foo": ["foo"]} + assert stream_chunks == [ + {"meow1": {"foo": ["foo"]}}, + {"meow2": {"bar": "foobar"}}, + ] + + # test two sequences + + def a(state: State): + return {"foo": ["a"]} + + def b(state: State): + return {"foo": ["b"]} + + builder_two_sequences = StateGraph(State) + builder_two_sequences.add_sequence([a]) + builder_two_sequences.add_sequence([b]) + builder_two_sequences.add_edge(START, "a") + builder_two_sequences.add_edge("a", "b") + graph_two_sequences = builder_two_sequences.compile() + + result = graph_two_sequences.invoke({"foo": []}) + assert result == {"foo": ["a", "b"]} + + stream_chunks = list(graph_two_sequences.stream({"foo": []})) + assert stream_chunks == [ + {"a": {"foo": ["a"]}}, + {"b": {"foo": ["b"]}}, + ] + + # test mixed nodes and sequences + + def c(state: State): + return {"foo": ["c"]} + + def d(state: State): + return {"foo": ["d"]} + + def e(state: State): + return {"foo": ["e"]} + + def foo(state: State): + if state["foo"][0] == "a": + return "d" + else: + return "c" + + builder_complex = StateGraph(State) + builder_complex.add_sequence([a, b]) + builder_complex.add_conditional_edges("b", foo) + builder_complex.add_node(c) + builder_complex.add_sequence([d, e]) + builder_complex.add_edge(START, "a") + graph_complex = builder_complex.compile() + + result = graph_complex.invoke({"foo": []}) + assert result == {"foo": ["a", "b", "d", "e"]} + + result = graph_complex.invoke({"foo": ["start"]}) + assert result == {"foo": ["start", "a", "b", "c"]} + + stream_chunks = list(graph_complex.stream({"foo": []})) + assert stream_chunks == [ + {"a": {"foo": ["a"]}}, + {"b": {"foo": ["b"]}}, + {"d": {"foo": ["d"]}}, + {"e": {"foo": ["e"]}}, + ] + + +def test_runnable_passthrough_node_graph() -> None: + class State(TypedDict): + changeme: str + + async def dummy(state): + return state + + agent = dummy | RunnablePassthrough.assign(prediction=RunnableLambda(lambda x: x)) + + graph_builder = StateGraph(State) + + graph_builder.add_node("agent", agent) + graph_builder.add_edge(START, "agent") + + graph = graph_builder.compile() + + assert graph.get_graph(xray=True).to_json() == graph.get_graph(xray=False).to_json() diff --git a/libs/langgraph/tests/test_pregel_async.py b/libs/langgraph/tests/test_pregel_async.py index 1c45d414f..a31e444e1 100644 --- a/libs/langgraph/tests/test_pregel_async.py +++ b/libs/langgraph/tests/test_pregel_async.py @@ -1,10 +1,12 @@ import asyncio import operator +import random import re import sys import uuid from collections import Counter from contextlib import asynccontextmanager, contextmanager +from dataclasses import replace from time import perf_counter from typing import ( Annotated, @@ -50,11 +52,17 @@ from langgraph.checkpoint.base import ( CheckpointTuple, ) from langgraph.checkpoint.memory import MemorySaver -from langgraph.constants import ERROR, PULL, PUSH +from langgraph.constants import ( + CONFIG_KEY_NODE_FINISHED, + ERROR, + FF_SEND_V2, + PULL, + PUSH, + START, +) from langgraph.errors import InvalidUpdateError, MultipleSubgraphsError, NodeInterrupt -from langgraph.graph import END, Graph, StateGraph -from langgraph.graph.graph import START -from langgraph.graph.message import MessageGraph, add_messages +from langgraph.graph import END, Graph, GraphCommand, StateGraph +from langgraph.graph.message import MessageGraph, MessagesState, add_messages from langgraph.managed.shared_value import SharedValue from langgraph.prebuilt.chat_agent_executor import create_tool_calling_executor from langgraph.prebuilt.tool_node import ToolNode @@ -62,7 +70,14 @@ from langgraph.pregel import Channel, GraphRecursionError, Pregel, StateSnapshot from langgraph.pregel.retry import RetryPolicy from langgraph.store.base import BaseStore from langgraph.store.memory import InMemoryStore -from langgraph.types import Interrupt, PregelTask, Send, StreamWriter +from langgraph.types import ( + Command, + Interrupt, + PregelTask, + Send, + StreamWriter, + interrupt, +) from tests.any_str import AnyDict, AnyStr, AnyVersion, FloatBetween, UnsortedSequence from tests.conftest import ( ALL_CHECKPOINTERS_ASYNC, @@ -243,6 +258,10 @@ async def test_node_cancellation_on_other_node_exception_two() -> None: await graph.ainvoke(1) +@pytest.mark.skipif( + sys.version_info < (3, 11), + reason="Python 3.11+ is required for async contextvars support", +) @pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) async def test_dynamic_interrupt(checkpointer_name: str) -> None: class State(TypedDict): @@ -255,8 +274,10 @@ async def test_dynamic_interrupt(checkpointer_name: str) -> None: nonlocal tool_two_node_count tool_two_node_count += 1 if s["market"] == "DE": - raise NodeInterrupt("Just because...") - return {"my_key": " all good"} + answer = interrupt("Just because...") + else: + answer = " all good" + return {"my_key": answer} tool_two_graph = StateGraph(State) tool_two_graph.add_node("tool_two", tool_two_node, retry=RetryPolicy()) @@ -289,6 +310,33 @@ async def test_dynamic_interrupt(checkpointer_name: str) -> None: with pytest.raises(ValueError, match="thread_id"): await tool_two.ainvoke({"my_key": "value", "market": "DE"}) + # flow: interrupt -> resume with answer + thread2 = {"configurable": {"thread_id": "2"}} + # stop when about to enter node + assert [ + c + async for c in tool_two.astream( + {"my_key": "value ⛰️", "market": "DE"}, thread2 + ) + ] == [ + { + "__interrupt__": ( + Interrupt( + value="Just because...", + resumable=True, + ns=[AnyStr("tool_two:")], + ), + ) + }, + ] + # resume with answer + assert [ + c async for c in tool_two.astream(Command(resume=" my answer"), thread2) + ] == [ + {"tool_two": {"my_key": " my answer"}}, + ] + + # flow: interrupt -> clear thread1 = {"configurable": {"thread_id": "1"}} # stop when about to enter node assert [ @@ -297,7 +345,15 @@ async def test_dynamic_interrupt(checkpointer_name: str) -> None: {"my_key": "value ⛰️", "market": "DE"}, thread1 ) ] == [ - {"__interrupt__": [Interrupt(value="Just because...", when="during")]}, + { + "__interrupt__": ( + Interrupt( + value="Just because...", + resumable=True, + ns=[AnyStr("tool_two:")], + ), + ) + }, ] assert [c.metadata async for c in tool_two.checkpointer.alist(thread1)] == [ { @@ -305,12 +361,14 @@ async def test_dynamic_interrupt(checkpointer_name: str) -> None: "source": "loop", "step": 0, "writes": None, + "thread_id": "1", }, { "parents": {}, "source": "input", "step": -1, "writes": {"__start__": {"my_key": "value ⛰️", "market": "DE"}}, + "thread_id": "1", }, ] tup = await tool_two.checkpointer.aget_tuple(thread1) @@ -322,24 +380,237 @@ async def test_dynamic_interrupt(checkpointer_name: str) -> None: AnyStr(), "tool_two", (PULL, "tool_two"), - interrupts=(Interrupt("Just because..."),), + interrupts=( + Interrupt( + value="Just because...", + resumable=True, + ns=[AnyStr("tool_two:")], + ), + ), ), ), config=tup.config, created_at=tup.checkpoint["ts"], - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "thread_id": "1", + }, + parent_config=[ + c async for c in tool_two.checkpointer.alist(thread1, limit=2) + ][-1].config, + ) + + # clear the interrupt and next tasks + await tool_two.aupdate_state(thread1, None, as_node=END) + # interrupt is cleared, as well as the next tasks + tup = await tool_two.checkpointer.aget_tuple(thread1) + assert await tool_two.aget_state(thread1) == StateSnapshot( + values={"my_key": "value ⛰️", "market": "DE"}, + next=(), + tasks=(), + config=tup.config, + created_at=tup.checkpoint["ts"], + metadata={ + "parents": {}, + "source": "update", + "step": 1, + "writes": {}, + "thread_id": "1", + }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread1, limit=2) ][-1].config, ) +@pytest.mark.skipif(not FF_SEND_V2, reason="send v2 is not enabled") +@pytest.mark.skipif( + sys.version_info < (3, 11), + reason="Python 3.11+ is required for async contextvars support", +) +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) +async def test_copy_checkpoint(checkpointer_name: str) -> None: + class State(TypedDict): + my_key: Annotated[str, operator.add] + market: str + + def tool_one(s: State) -> State: + return {"my_key": " one"} + + tool_two_node_count = 0 + + def tool_two_node(s: State) -> State: + nonlocal tool_two_node_count + tool_two_node_count += 1 + if s["market"] == "DE": + answer = interrupt("Just because...") + else: + answer = " all good" + return {"my_key": answer} + + def start(state: State) -> list[Union[Send, str]]: + return ["tool_two", Send("tool_one", state)] + + tool_two_graph = StateGraph(State) + tool_two_graph.add_node("tool_two", tool_two_node, retry=RetryPolicy()) + tool_two_graph.add_node("tool_one", tool_one) + tool_two_graph.set_conditional_entry_point(start) + tool_two = tool_two_graph.compile() + + tracer = FakeTracer() + assert await tool_two.ainvoke( + {"my_key": "value", "market": "DE"}, {"callbacks": [tracer]} + ) == { + "my_key": "value one", + "market": "DE", + } + assert tool_two_node_count == 1, "interrupts aren't retried" + assert len(tracer.runs) == 1 + run = tracer.runs[0] + assert run.end_time is not None + assert run.error is None + assert run.outputs == {"market": "DE", "my_key": "value one"} + + assert await tool_two.ainvoke({"my_key": "value", "market": "US"}) == { + "my_key": "value one all good", + "market": "US", + } + + async with awith_checkpointer(checkpointer_name) as checkpointer: + tool_two = tool_two_graph.compile(checkpointer=checkpointer) + + # missing thread_id + with pytest.raises(ValueError, match="thread_id"): + await tool_two.ainvoke({"my_key": "value", "market": "DE"}) + + # flow: interrupt -> resume with answer + thread2 = {"configurable": {"thread_id": "2"}} + # stop when about to enter node + assert [ + c + async for c in tool_two.astream( + {"my_key": "value ⛰️", "market": "DE"}, thread2 + ) + ] == [ + { + "tool_one": {"my_key": " one"}, + }, + { + "__interrupt__": ( + Interrupt( + value="Just because...", + resumable=True, + ns=[AnyStr("tool_two:")], + ), + ) + }, + ] + # resume with answer + assert [ + c async for c in tool_two.astream(Command(resume=" my answer"), thread2) + ] == [ + {"tool_two": {"my_key": " my answer"}}, + ] + + # flow: interrupt -> clear tasks + thread1 = {"configurable": {"thread_id": "1"}} + # stop when about to enter node + assert await tool_two.ainvoke( + {"my_key": "value ⛰️", "market": "DE"}, thread1 + ) == { + "my_key": "value ⛰️ one", + "market": "DE", + } + assert [c.metadata async for c in tool_two.checkpointer.alist(thread1)] == [ + { + "parents": {}, + "source": "loop", + "step": 0, + "writes": {"tool_one": {"my_key": " one"}}, + "thread_id": "1", + }, + { + "parents": {}, + "source": "input", + "step": -1, + "writes": {"__start__": {"my_key": "value ⛰️", "market": "DE"}}, + "thread_id": "1", + }, + ] + tup = await tool_two.checkpointer.aget_tuple(thread1) + assert await tool_two.aget_state(thread1) == StateSnapshot( + values={"my_key": "value ⛰️ one", "market": "DE"}, + next=("tool_two",), + tasks=( + PregelTask( + AnyStr(), + "tool_two", + (PULL, "tool_two"), + interrupts=( + Interrupt( + value="Just because...", + resumable=True, + ns=[AnyStr("tool_two:")], + ), + ), + ), + ), + config=tup.config, + created_at=tup.checkpoint["ts"], + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": {"tool_one": {"my_key": " one"}}, + "thread_id": "1", + }, + parent_config=[ + c async for c in tool_two.checkpointer.alist(thread1, limit=2) + ][-1].config, + ) + # clear the interrupt and next tasks + await tool_two.aupdate_state(thread1, None) + # interrupt is cleared, next task is kept + tup = await tool_two.checkpointer.aget_tuple(thread1) + assert await tool_two.aget_state(thread1) == StateSnapshot( + values={"my_key": "value ⛰️ one", "market": "DE"}, + next=("tool_two",), + tasks=( + PregelTask( + AnyStr(), + "tool_two", + (PULL, "tool_two"), + interrupts=(), + ), + ), + config=tup.config, + created_at=tup.checkpoint["ts"], + metadata={ + "parents": {}, + "source": "update", + "step": 1, + "writes": {}, + "thread_id": "1", + }, + parent_config=[ + c async for c in tool_two.checkpointer.alist(thread1, limit=2) + ][-1].config, + ) + + +@pytest.mark.skipif( + sys.version_info < (3, 11), + reason="Python 3.11+ is required for async contextvars support", +) @pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) async def test_node_not_cancelled_on_other_node_interrupted( checkpointer_name: str, ) -> None: class State(TypedDict): - hello: str + hello: Annotated[str, operator.add] awhiles = 0 inner_task_cancelled = False @@ -350,15 +621,14 @@ async def test_node_not_cancelled_on_other_node_interrupted( awhiles += 1 try: await asyncio.sleep(1) - return {"hello": "again"} + return {"hello": " again"} except asyncio.CancelledError: nonlocal inner_task_cancelled inner_task_cancelled = True raise async def iambad(input: State) -> None: - if input["hello"] != "bye": - raise NodeInterrupt("I am bad") + return {"hello": interrupt("I am bad")} builder = StateGraph(State) builder.add_node("agent", awhile) @@ -369,20 +639,26 @@ async def test_node_not_cancelled_on_other_node_interrupted( graph = builder.compile(checkpointer=checkpointer) thread = {"configurable": {"thread_id": "1"}} - assert await graph.ainvoke({"hello": "world"}, thread) == {"hello": "world"} + # writes from "awhile" are applied to last chunk + assert await graph.ainvoke({"hello": "world"}, thread) == { + "hello": "world again" + } assert not inner_task_cancelled assert awhiles == 1 - assert await graph.ainvoke(None, thread, debug=True) == {"hello": "world"} + assert await graph.ainvoke(None, thread, debug=True) == {"hello": "world again"} assert not inner_task_cancelled assert awhiles == 1 - assert await graph.ainvoke({"hello": "bye"}, thread) == {"hello": "again"} + # resume with answer + assert await graph.ainvoke(Command(resume=" okay"), thread) == { + "hello": "world again okay" + } assert not inner_task_cancelled - assert awhiles == 2 + assert awhiles == 1 @pytest.mark.repeat(10) @@ -473,19 +749,18 @@ async def test_cancel_graph_astream(checkpointer_name: str) -> None: assert awhile.started is False # checkpoint with output of "alittlewhile" should not be saved + # but we should have applied pending writes if checkpointer is not None: state = await graph.aget_state(thread1) assert state is not None - assert state.values == {"value": 1} - assert state.next == ( - "aparallelwhile", - "alittlewhile", - ) + assert state.values == {"value": 3} # 1 + 2 + assert state.next == ("aparallelwhile",) assert state.metadata == { "parents": {}, "source": "loop", "step": 0, "writes": None, + "thread_id": "1", } @@ -562,6 +837,7 @@ async def test_cancel_graph_astream_events_v2(checkpointer_name: Optional[str]) "source": "loop", "step": 1, "writes": {"alittlewhile": {"value": 2}}, + "thread_id": "2", } @@ -945,6 +1221,7 @@ async def test_invoke_two_processes_in_out_interrupt( "source": "loop", "step": 6, "writes": {"two": 5}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[1].config, @@ -967,6 +1244,7 @@ async def test_invoke_two_processes_in_out_interrupt( "source": "loop", "step": 5, "writes": {"one": None}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[2].config, @@ -989,6 +1267,7 @@ async def test_invoke_two_processes_in_out_interrupt( "source": "input", "step": 4, "writes": {"input": 3}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[3].config, @@ -1009,6 +1288,7 @@ async def test_invoke_two_processes_in_out_interrupt( "source": "loop", "step": 3, "writes": {"one": None}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[4].config, @@ -1031,6 +1311,7 @@ async def test_invoke_two_processes_in_out_interrupt( "source": "input", "step": 2, "writes": {"input": 20}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[5].config, @@ -1051,6 +1332,7 @@ async def test_invoke_two_processes_in_out_interrupt( "source": "loop", "step": 1, "writes": {"two": 4}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[6].config, @@ -1073,6 +1355,7 @@ async def test_invoke_two_processes_in_out_interrupt( "source": "loop", "step": 0, "writes": {"one": None}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[7].config, @@ -1095,6 +1378,7 @@ async def test_invoke_two_processes_in_out_interrupt( "source": "input", "step": -1, "writes": {"input": 2}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -1170,6 +1454,7 @@ async def test_fork_always_re_runs_nodes( "source": "loop", "step": 5, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[1].config, @@ -1190,6 +1475,7 @@ async def test_fork_always_re_runs_nodes( "source": "loop", "step": 4, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[2].config, @@ -1210,6 +1496,7 @@ async def test_fork_always_re_runs_nodes( "source": "loop", "step": 3, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[3].config, @@ -1230,6 +1517,7 @@ async def test_fork_always_re_runs_nodes( "source": "loop", "step": 2, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[4].config, @@ -1250,6 +1538,7 @@ async def test_fork_always_re_runs_nodes( "source": "loop", "step": 1, "writes": {"add_one": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=history[5].config, @@ -1265,7 +1554,13 @@ async def test_fork_always_re_runs_nodes( "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "thread_id": "1", + }, created_at=AnyStr(), parent_config=history[6].config, ), @@ -1287,6 +1582,7 @@ async def test_fork_always_re_runs_nodes( "source": "input", "step": -1, "writes": {"__start__": 1}, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -1691,10 +1987,11 @@ async def test_pending_writes_resume( assert two.calls == 2 # latest checkpoint should be before nodes "one", "two" + # but we should have applied pending writes from "one" state = await graph.aget_state(thread1) assert state is not None - assert state.values == {"value": 1} - assert state.next == ("one", "two") + assert state.values == {"value": 3} + assert state.next == ("two",) assert state.tasks == ( PregelTask(AnyStr(), "one", (PULL, "one"), result={"value": 2}), PregelTask( @@ -1709,7 +2006,13 @@ async def test_pending_writes_resume( "source": "loop", "step": 0, "writes": None, + "thread_id": "1", } + # get_state with checkpoint_id should not apply any pending writes + state = await graph.aget_state(state.config) + assert state is not None + assert state.values == {"value": 1} + assert state.next == ("one", "two") # should contain pending write of "one" checkpoint = await checkpointer.aget_tuple(thread1) assert checkpoint is not None @@ -1797,6 +2100,7 @@ async def test_pending_writes_resume( "step": 1, "source": "loop", "writes": {"one": {"value": 2}, "two": {"value": 3}}, + "thread_id": "1", }, parent_config={ "configurable": { @@ -1843,7 +2147,13 @@ async def test_pending_writes_resume( "start:two": "__start__", }, }, - metadata={"parents": {}, "step": 0, "source": "loop", "writes": None}, + metadata={ + "parents": {}, + "step": 0, + "source": "loop", + "writes": None, + "thread_id": "1", + }, parent_config={ "configurable": { "thread_id": "1", @@ -1885,6 +2195,7 @@ async def test_pending_writes_resume( "step": -1, "source": "input", "writes": {"__start__": {"value": 1}}, + "thread_id": "1", }, parent_config=None, pending_writes=UnsortedSequence( @@ -1895,6 +2206,85 @@ async def test_pending_writes_resume( ) +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) +async def test_run_from_checkpoint_id_retains_previous_writes( + request: pytest.FixtureRequest, checkpointer_name: str, mocker: MockerFixture +) -> None: + class MyState(TypedDict): + myval: Annotated[int, operator.add] + otherval: bool + + class Anode: + def __init__(self): + self.switch = False + + async def __call__(self, state: MyState): + self.switch = not self.switch + return {"myval": 2 if self.switch else 1, "otherval": self.switch} + + builder = StateGraph(MyState) + thenode = Anode() # Fun. + builder.add_node("node_one", thenode) + builder.add_node("node_two", thenode) + builder.add_edge(START, "node_one") + + def _getedge(src: str): + swap = "node_one" if src == "node_two" else "node_two" + + def _edge(st: MyState) -> Literal["__end__", "node_one", "node_two"]: + if st["myval"] > 3: + return END + if st["otherval"]: + return swap + return src + + return _edge + + builder.add_conditional_edges("node_one", _getedge("node_one")) + builder.add_conditional_edges("node_two", _getedge("node_two")) + async with awith_checkpointer(checkpointer_name) as checkpointer: + graph = builder.compile(checkpointer=checkpointer) + + thread_id = uuid.uuid4() + thread1 = {"configurable": {"thread_id": str(thread_id)}} + + result = await graph.ainvoke({"myval": 1}, thread1) + assert result["myval"] == 4 + history = [c async for c in graph.aget_state_history(thread1)] + + assert len(history) == 4 + assert history[-1].values == {"myval": 0} + assert history[0].values == {"myval": 4, "otherval": False} + + second_run_config = { + **thread1, + "configurable": { + **thread1["configurable"], + "checkpoint_id": history[1].config["configurable"]["checkpoint_id"], + }, + } + second_result = await graph.ainvoke(None, second_run_config) + assert second_result == {"myval": 5, "otherval": True} + + new_history = [ + c + async for c in graph.aget_state_history( + {"configurable": {"thread_id": str(thread_id), "checkpoint_ns": ""}} + ) + ] + + assert len(new_history) == len(history) + 1 + for original, new in zip(history, new_history[1:]): + assert original.values == new.values + assert original.next == new.next + assert original.metadata["step"] == new.metadata["step"] + + def _get_tasks(hist: list, start: int): + return [h.tasks for h in hist[start:]] + + assert _get_tasks(new_history, 1) == _get_tasks(history, 0) + + async def test_cond_edge_after_send() -> None: class Node: def __init__(self, name: str): @@ -1922,6 +2312,1334 @@ async def test_cond_edge_after_send() -> None: assert await graph.ainvoke(["0"]) == ["0", "1", "2", "2", "3"] +async def test_concurrent_emit_sends() -> None: + class Node: + def __init__(self, name: str): + self.name = name + setattr(self, "__name__", name) + + async def __call__(self, state): + return ( + [self.name] + if isinstance(state, list) + else ["|".join((self.name, str(state)))] + ) + + async def send_for_fun(state): + return [Send("2", 1), Send("2", 2), "3.1"] + + async def send_for_profit(state): + return [Send("2", 3), Send("2", 4)] + + async def route_to_three(state) -> Literal["3"]: + return "3" + + builder = StateGraph(Annotated[list, operator.add]) + builder.add_node(Node("1")) + builder.add_node(Node("1.1")) + builder.add_node(Node("2")) + builder.add_node(Node("3")) + builder.add_node(Node("3.1")) + builder.add_edge(START, "1") + builder.add_edge(START, "1.1") + builder.add_conditional_edges("1", send_for_fun) + builder.add_conditional_edges("1.1", send_for_profit) + builder.add_conditional_edges("2", route_to_three) + graph = builder.compile() + assert await graph.ainvoke(["0"]) == ( + [ + "0", + "1", + "1.1", + "2|1", + "2|2", + "2|3", + "2|4", + "3", + "3.1", + ] + if FF_SEND_V2 + else [ + "0", + "1", + "1.1", + "3.1", + "2|1", + "2|2", + "2|3", + "2|4", + "3", + ] + ) + + +@pytest.mark.repeat(10) +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) +async def test_send_sequences(checkpointer_name: str) -> None: + class Node: + def __init__(self, name: str): + self.name = name + setattr(self, "__name__", name) + + async def __call__(self, state): + update = ( + [self.name] + if isinstance(state, list) # or isinstance(state, Control) + else ["|".join((self.name, str(state)))] + ) + if isinstance(state, GraphCommand): + return replace(state, update=update) + else: + return update + + async def send_for_fun(state): + return [ + Send("2", GraphCommand(send=Send("2", 3))), + Send("2", GraphCommand(send=Send("2", 4))), + "3.1", + ] + + async def route_to_three(state) -> Literal["3"]: + return "3" + + builder = StateGraph(Annotated[list, operator.add]) + builder.add_node(Node("1")) + builder.add_node(Node("2")) + builder.add_node(Node("3")) + builder.add_node(Node("3.1")) + builder.add_edge(START, "1") + builder.add_conditional_edges("1", send_for_fun) + builder.add_conditional_edges("2", route_to_three) + graph = builder.compile() + assert ( + await graph.ainvoke(["0"]) + == [ + "0", + "1", + "2|Command(send=Send(node='2', arg=3))", + "2|Command(send=Send(node='2', arg=4))", + "2|3", + "2|4", + "3", + "3.1", + ] + if FF_SEND_V2 + else [ + "0", + "1", + "3.1", + "2|Command(send=Send(node='2', arg=3))", + "2|Command(send=Send(node='2', arg=4))", + "3", + "2|3", + "2|4", + "3", + ] + ) + + if not FF_SEND_V2: + return + + async with awith_checkpointer(checkpointer_name) as checkpointer: + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["3.1"]) + thread1 = {"configurable": {"thread_id": "1"}} + assert await graph.ainvoke(["0"], thread1) == [ + "0", + "1", + "2|Command(send=Send(node='2', arg=3))", + "2|Command(send=Send(node='2', arg=4))", + "2|3", + "2|4", + ] + assert await graph.ainvoke(None, thread1) == [ + "0", + "1", + "2|Command(send=Send(node='2', arg=3))", + "2|Command(send=Send(node='2', arg=4))", + "2|3", + "2|4", + "3", + "3.1", + ] + + +@pytest.mark.repeat(20) +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) +async def test_send_dedupe_on_resume(checkpointer_name: str) -> None: + if not FF_SEND_V2: + pytest.skip("Send deduplication is only available in Send V2") + + class InterruptOnce: + ticks: int = 0 + + def __call__(self, state): + self.ticks += 1 + if self.ticks == 1: + raise NodeInterrupt("Bahh") + return ["|".join(("flaky", str(state)))] + + class Node: + def __init__(self, name: str): + self.name = name + self.ticks = 0 + setattr(self, "__name__", name) + + def __call__(self, state): + self.ticks += 1 + update = ( + [self.name] + if isinstance(state, list) + else ["|".join((self.name, str(state)))] + ) + if isinstance(state, GraphCommand): + return replace(state, update=update) + else: + return update + + def send_for_fun(state): + return [ + Send("2", GraphCommand(send=Send("2", 3))), + Send("2", GraphCommand(send=Send("flaky", 4))), + "3.1", + ] + + def route_to_three(state) -> Literal["3"]: + return "3" + + builder = StateGraph(Annotated[list, operator.add]) + builder.add_node(Node("1")) + builder.add_node(Node("2")) + builder.add_node(Node("3")) + builder.add_node(Node("3.1")) + builder.add_node("flaky", InterruptOnce()) + builder.add_edge(START, "1") + builder.add_conditional_edges("1", send_for_fun) + builder.add_conditional_edges("2", route_to_three) + + async with awith_checkpointer(checkpointer_name) as checkpointer: + graph = builder.compile(checkpointer=checkpointer) + thread1 = {"configurable": {"thread_id": "1"}} + assert await graph.ainvoke(["0"], thread1, debug=1) == [ + "0", + "1", + "2|Command(send=Send(node='2', arg=3))", + "2|Command(send=Send(node='flaky', arg=4))", + "2|3", + ] + assert builder.nodes["2"].runnable.func.ticks == 3 + assert builder.nodes["flaky"].runnable.func.ticks == 1 + # resume execution + assert await graph.ainvoke(None, thread1, debug=1) == [ + "0", + "1", + "2|Command(send=Send(node='2', arg=3))", + "2|Command(send=Send(node='flaky', arg=4))", + "2|3", + "flaky|4", + "3", + "3.1", + ] + # node "2" doesn't get called again, as we recover writes saved before + assert builder.nodes["2"].runnable.func.ticks == 3 + # node "flaky" gets called again, as it was interrupted + assert builder.nodes["flaky"].runnable.func.ticks == 2 + # check history + history = [c async for c in graph.aget_state_history(thread1)] + assert history == [ + StateSnapshot( + values=[ + "0", + "1", + "2|Command(send=Send(node='2', arg=3))", + "2|Command(send=Send(node='flaky', arg=4))", + "2|3", + "flaky|4", + "3", + "3.1", + ], + next=(), + config={ + "configurable": { + "thread_id": "1", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "source": "loop", + "writes": {"3": ["3"], "3.1": ["3.1"]}, + "thread_id": "1", + "step": 2, + "parents": {}, + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "1", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=(), + ), + StateSnapshot( + values=[ + "0", + "1", + "2|Command(send=Send(node='2', arg=3))", + "2|Command(send=Send(node='flaky', arg=4))", + "2|3", + "flaky|4", + ], + next=("3", "3.1"), + config={ + "configurable": { + "thread_id": "1", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "source": "loop", + "writes": { + "1": ["1"], + "2": [ + ["2|Command(send=Send(node='2', arg=3))"], + ["2|Command(send=Send(node='flaky', arg=4))"], + ["2|3"], + ], + "flaky": ["flaky|4"], + }, + "thread_id": "1", + "step": 1, + "parents": {}, + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "1", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="3", + path=("__pregel_pull", "3"), + error=None, + interrupts=(), + state=None, + result=["3"], + ), + PregelTask( + id=AnyStr(), + name="3.1", + path=("__pregel_pull", "3.1"), + error=None, + interrupts=(), + state=None, + result=["3.1"], + ), + ), + ), + StateSnapshot( + values=["0"], + next=("1", "2", "2", "2", "flaky"), + config={ + "configurable": { + "thread_id": "1", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "source": "loop", + "writes": None, + "thread_id": "1", + "step": 0, + "parents": {}, + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "1", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="1", + path=("__pregel_pull", "1"), + error=None, + interrupts=(), + state=None, + result=["1"], + ), + PregelTask( + id=AnyStr(), + name="2", + path=( + "__pregel_push", + ("__pregel_pull", "1"), + 2, + AnyStr(), + ), + error=None, + interrupts=(), + state=None, + result=["2|Command(send=Send(node='2', arg=3))"], + ), + PregelTask( + id=AnyStr(), + name="2", + path=( + "__pregel_push", + ("__pregel_pull", "1"), + 3, + AnyStr(), + ), + error=None, + interrupts=(), + state=None, + result=["2|Command(send=Send(node='flaky', arg=4))"], + ), + PregelTask( + id=AnyStr(), + name="2", + path=( + "__pregel_push", + ( + "__pregel_push", + ("__pregel_pull", "1"), + 2, + AnyStr(), + ), + 2, + AnyStr(), + ), + error=None, + interrupts=(), + state=None, + result=["2|3"], + ), + PregelTask( + id=AnyStr(), + name="flaky", + path=( + "__pregel_push", + ( + "__pregel_push", + ("__pregel_pull", "1"), + 3, + AnyStr(), + ), + 2, + AnyStr(), + ), + error=None, + interrupts=(Interrupt(value="Bahh", when="during"),), + state=None, + result=["flaky|4"], + ), + ), + ), + StateSnapshot( + values=[], + next=("__start__",), + config={ + "configurable": { + "thread_id": "1", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "source": "input", + "writes": {"__start__": ["0"]}, + "thread_id": "1", + "step": -1, + "parents": {}, + }, + created_at=AnyStr(), + parent_config=None, + tasks=( + PregelTask( + id=AnyStr(), + name="__start__", + path=("__pregel_pull", "__start__"), + error=None, + interrupts=(), + state=None, + result=["0"], + ), + ), + ), + ] + + +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) +async def test_send_react_interrupt(checkpointer_name: str) -> None: + from langchain_core.messages import AIMessage, HumanMessage, ToolCall, ToolMessage + + ai_message = AIMessage( + "", + id="ai1", + tool_calls=[ToolCall(name="foo", args={"hi": [1, 2, 3]}, id=AnyStr())], + ) + + async def agent(state): + return {"messages": ai_message} + + def route(state): + if isinstance(state["messages"][-1], AIMessage): + return [ + Send(call["name"], call) for call in state["messages"][-1].tool_calls + ] + + foo_called = 0 + + async def foo(call: ToolCall): + nonlocal foo_called + foo_called += 1 + return {"messages": ToolMessage(str(call["args"]), tool_call_id=call["id"])} + + builder = StateGraph(MessagesState) + builder.add_node(agent) + builder.add_node(foo) + builder.add_edge(START, "agent") + builder.add_conditional_edges("agent", route) + graph = builder.compile() + + assert await graph.ainvoke({"messages": [HumanMessage("hello")]}) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage( + content="{'hi': [1, 2, 3]}", + tool_call_id=AnyStr(), + ), + ] + } + assert foo_called == 1 + + async with awith_checkpointer(checkpointer_name) as checkpointer: + # simple interrupt-resume flow + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "1"}} + assert await graph.ainvoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + assert await graph.ainvoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage( + content="{'hi': [1, 2, 3]}", + tool_call_id=AnyStr(), + ), + ] + } + assert foo_called == 1 + + # interrupt-update-resume flow + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "2"}} + assert await graph.ainvoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + + if not FF_SEND_V2: + return + + # get state should show the pending task + state = await graph.aget_state(thread1) + assert state == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + }, + next=("foo",), + config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 0, + "source": "loop", + "writes": None, + "parents": {}, + "thread_id": "2", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="agent", + path=("__pregel_pull", "agent"), + error=None, + interrupts=(), + state=None, + result={ + "messages": AIMessage( + content="", + additional_kwargs={}, + response_metadata={}, + id="ai1", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ) + }, + ), + PregelTask( + id=AnyStr(), + name="foo", + path=("__pregel_push", ("__pregel_pull", "agent"), 2, AnyStr()), + error=None, + interrupts=(), + state=None, + result=None, + ), + ), + ) + + # remove the tool call, clearing the pending task + await graph.aupdate_state( + thread1, {"messages": AIMessage("Bye now", id=ai_message.id, tool_calls=[])} + ) + + # tool call no longer in pending tasks + assert await graph.aget_state(thread1) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="Bye now", + tool_calls=[], + ), + ] + }, + next=(), + config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 1, + "source": "update", + "writes": { + "agent": { + "messages": _AnyIdAIMessage( + content="Bye now", + tool_calls=[], + ) + } + }, + "parents": {}, + "thread_id": "2", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=(), + ) + + # tool call not executed + assert await graph.ainvoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage(content="Bye now"), + ] + } + assert foo_called == 0 + + # interrupt-update-resume flow, creating new Send in update call + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "3"}} + assert await graph.ainvoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + + # get state should show the pending task + state = await graph.aget_state(thread1) + assert state == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + }, + next=("foo",), + config={ + "configurable": { + "thread_id": "3", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 0, + "source": "loop", + "writes": None, + "parents": {}, + "thread_id": "3", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "3", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="agent", + path=("__pregel_pull", "agent"), + error=None, + interrupts=(), + state=None, + result={ + "messages": AIMessage( + "", + id="ai1", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ) + }, + ), + PregelTask( + id=AnyStr(), + name="foo", + path=("__pregel_push", ("__pregel_pull", "agent"), 2, AnyStr()), + error=None, + interrupts=(), + state=None, + result=None, + ), + ), + ) + + # replace the tool call, should clear previous send, create new one + await graph.aupdate_state( + thread1, + { + "messages": AIMessage( + "", + id=ai_message.id, + tool_calls=[ + { + "name": "foo", + "args": {"hi": [4, 5, 6]}, + "id": "tool1", + "type": "tool_call", + } + ], + ) + }, + ) + + # prev tool call no longer in pending tasks, new tool call is + assert await graph.aget_state(thread1) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [4, 5, 6]}, + "id": "tool1", + "type": "tool_call", + } + ], + ), + ] + }, + next=("foo",), + config={ + "configurable": { + "thread_id": "3", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 1, + "source": "update", + "writes": { + "agent": { + "messages": _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [4, 5, 6]}, + "id": "tool1", + "type": "tool_call", + } + ], + ) + } + }, + "parents": {}, + "thread_id": "3", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "3", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="foo", + path=("__pregel_push", (), 0, AnyStr()), + error=None, + interrupts=(), + state=None, + result=None, + ), + ), + ) + + # prev tool call not executed, new tool call is + assert await graph.ainvoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + AIMessage( + "", + id="ai1", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [4, 5, 6]}, + "id": "tool1", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage(content="{'hi': [4, 5, 6]}", tool_call_id="tool1"), + ] + } + assert foo_called == 1 + + +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) +async def test_send_react_interrupt_control( + checkpointer_name: str, snapshot: SnapshotAssertion +) -> None: + from langchain_core.messages import AIMessage, HumanMessage, ToolCall, ToolMessage + + ai_message = AIMessage( + "", + id="ai1", + tool_calls=[ToolCall(name="foo", args={"hi": [1, 2, 3]}, id=AnyStr())], + ) + + async def agent(state) -> Command[Literal["foo"]]: + return GraphCommand( + update={"messages": ai_message}, + send=[Send(call["name"], call) for call in ai_message.tool_calls], + ) + + foo_called = 0 + + async def foo(call: ToolCall): + nonlocal foo_called + foo_called += 1 + return {"messages": ToolMessage(str(call["args"]), tool_call_id=call["id"])} + + builder = StateGraph(MessagesState) + builder.add_node(agent) + builder.add_node(foo) + builder.add_edge(START, "agent") + graph = builder.compile() + assert graph.get_graph().draw_mermaid() == snapshot + + assert await graph.ainvoke({"messages": [HumanMessage("hello")]}) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage( + content="{'hi': [1, 2, 3]}", + tool_call_id=AnyStr(), + ), + ] + } + assert foo_called == 1 + + async with awith_checkpointer(checkpointer_name) as checkpointer: + # simple interrupt-resume flow + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "1"}} + assert await graph.ainvoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + assert await graph.ainvoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + _AnyIdToolMessage( + content="{'hi': [1, 2, 3]}", + tool_call_id=AnyStr(), + ), + ] + } + assert foo_called == 1 + + # interrupt-update-resume flow + foo_called = 0 + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["foo"]) + thread1 = {"configurable": {"thread_id": "2"}} + assert await graph.ainvoke({"messages": [HumanMessage("hello")]}, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + } + assert foo_called == 0 + + if not FF_SEND_V2: + return + + # get state should show the pending task + state = await graph.aget_state(thread1) + assert state == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ), + ] + }, + next=("foo",), + config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 0, + "source": "loop", + "writes": None, + "parents": {}, + "thread_id": "2", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=( + PregelTask( + id=AnyStr(), + name="agent", + path=("__pregel_pull", "agent"), + error=None, + interrupts=(), + state=None, + result={ + "messages": AIMessage( + content="", + additional_kwargs={}, + response_metadata={}, + id="ai1", + tool_calls=[ + { + "name": "foo", + "args": {"hi": [1, 2, 3]}, + "id": "", + "type": "tool_call", + } + ], + ) + }, + ), + PregelTask( + id=AnyStr(), + name="foo", + path=("__pregel_push", ("__pregel_pull", "agent"), 2, AnyStr()), + error=None, + interrupts=(), + state=None, + result=None, + ), + ), + ) + + # remove the tool call, clearing the pending task + await graph.aupdate_state( + thread1, {"messages": AIMessage("Bye now", id=ai_message.id, tool_calls=[])} + ) + + # tool call no longer in pending tasks + assert await graph.aget_state(thread1) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage( + content="Bye now", + tool_calls=[], + ), + ] + }, + next=(), + config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + metadata={ + "step": 1, + "source": "update", + "writes": { + "agent": { + "messages": _AnyIdAIMessage( + content="Bye now", + tool_calls=[], + ) + } + }, + "parents": {}, + "thread_id": "2", + }, + created_at=AnyStr(), + parent_config={ + "configurable": { + "thread_id": "2", + "checkpoint_ns": "", + "checkpoint_id": AnyStr(), + } + }, + tasks=(), + ) + + # tool call not executed + assert await graph.ainvoke(None, thread1) == { + "messages": [ + _AnyIdHumanMessage(content="hello"), + _AnyIdAIMessage(content="Bye now"), + ] + } + assert foo_called == 0 + + +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) +async def test_max_concurrency(checkpointer_name: str) -> None: + class Node: + def __init__(self, name: str): + self.name = name + setattr(self, "__name__", name) + self.currently = 0 + self.max_currently = 0 + + async def __call__(self, state): + self.currently += 1 + if self.currently > self.max_currently: + self.max_currently = self.currently + await asyncio.sleep(random.random() / 10) + self.currently -= 1 + return [state] + + def one(state): + return ["1"] + + def three(state): + return ["3"] + + async def send_to_many(state): + return [Send("2", idx) for idx in range(100)] + + async def route_to_three(state) -> Literal["3"]: + return "3" + + node2 = Node("2") + builder = StateGraph(Annotated[list, operator.add]) + builder.add_node("1", one) + builder.add_node(node2) + builder.add_node("3", three) + builder.add_edge(START, "1") + builder.add_conditional_edges("1", send_to_many) + builder.add_conditional_edges("2", route_to_three) + graph = builder.compile() + + assert await graph.ainvoke(["0"]) == ["0", "1", *range(100), "3"] + assert node2.max_currently == 100 + assert node2.currently == 0 + node2.max_currently = 0 + + assert await graph.ainvoke(["0"], {"max_concurrency": 10}) == [ + "0", + "1", + *range(100), + "3", + ] + assert node2.max_currently == 10 + assert node2.currently == 0 + + async with awith_checkpointer(checkpointer_name) as checkpointer: + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["2"]) + thread1 = {"max_concurrency": 10, "configurable": {"thread_id": "1"}} + + assert await graph.ainvoke(["0"], thread1, debug=True) == ["0", "1"] + state = await graph.aget_state(thread1) + assert state.values == ["0", "1"] + assert await graph.ainvoke(None, thread1) == ["0", "1", *range(100), "3"] + + +@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) +async def test_max_concurrency_control(checkpointer_name: str) -> None: + async def node1(state) -> GraphCommand[Literal["2"]]: + return GraphCommand(update=["1"], send=[Send("2", idx) for idx in range(100)]) + + node2_currently = 0 + node2_max_currently = 0 + + async def node2(state) -> GraphCommand[Literal["3"]]: + nonlocal node2_currently, node2_max_currently + node2_currently += 1 + if node2_currently > node2_max_currently: + node2_max_currently = node2_currently + await asyncio.sleep(0.1) + node2_currently -= 1 + + return GraphCommand(update=[state], goto="3") + + async def node3(state) -> Literal["3"]: + return ["3"] + + builder = StateGraph(Annotated[list, operator.add]) + builder.add_node("1", node1) + builder.add_node("2", node2) + builder.add_node("3", node3) + builder.add_edge(START, "1") + graph = builder.compile() + + assert ( + graph.get_graph().draw_mermaid() + == """%%{init: {'flowchart': {'curve': 'linear'}}}%% +graph TD; + __start__([

__start__

]):::first + 1(1) + 2(2) + 3([3]):::last + __start__ --> 1; + 1 -.-> 2; + 2 -.-> 3; + classDef default fill:#f2f0ff,line-height:1.2 + classDef first fill-opacity:0 + classDef last fill:#bfb6fc +""" + ) + + assert await graph.ainvoke(["0"], debug=True) == ["0", "1", *range(100), "3"] + assert node2_max_currently == 100 + assert node2_currently == 0 + node2_max_currently = 0 + + assert await graph.ainvoke(["0"], {"max_concurrency": 10}) == [ + "0", + "1", + *range(100), + "3", + ] + assert node2_max_currently == 10 + assert node2_currently == 0 + + async with awith_checkpointer(checkpointer_name) as checkpointer: + graph = builder.compile(checkpointer=checkpointer, interrupt_before=["2"]) + thread1 = {"max_concurrency": 10, "configurable": {"thread_id": "1"}} + + assert await graph.ainvoke(["0"], thread1) == ["0", "1"] + assert await graph.ainvoke(None, thread1) == ["0", "1", *range(100), "3"] + + @pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC) async def test_invoke_checkpoint_three( mocker: MockerFixture, checkpointer_name: str @@ -2603,6 +4321,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: } } }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -2652,6 +4371,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: "input": "what is weather in sf", } }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -2776,6 +4496,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: ), } }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -2842,6 +4563,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: } } }, + "thread_id": "2", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -2891,6 +4613,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: "input": "what is weather in sf", } }, + "thread_id": "2", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -3015,6 +4738,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: ), } }, + "thread_id": "2", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -3081,6 +4805,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: } } }, + "thread_id": "3", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -3501,6 +5226,7 @@ async def test_conditional_graph_state( ), } }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -3547,6 +5273,7 @@ async def test_conditional_graph_state( ) } }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -3627,6 +5354,7 @@ async def test_conditional_graph_state( ) } }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -3686,6 +5414,7 @@ async def test_conditional_graph_state( ), } }, + "thread_id": "2", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -3731,6 +5460,7 @@ async def test_conditional_graph_state( ) } }, + "thread_id": "2", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -3809,6 +5539,7 @@ async def test_conditional_graph_state( ) } }, + "thread_id": "2", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -3999,18 +5730,6 @@ async def test_prebuilt_tool_chat() -> None: stream_mode="messages", ) ] == [ - ( - _AnyIdHumanMessage( - content="what is weather in sf", - ), - { - "langgraph_step": 0, - "langgraph_node": "__start__", - "langgraph_triggers": ["__start__"], - "langgraph_path": ("__pregel_pull", "__start__"), - "langgraph_checkpoint_ns": AnyStr("__start__:"), - }, - ), ( _AnyIdAIMessageChunk( content="", @@ -4443,6 +6162,8 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: ] async with awith_checkpointer(checkpointer_name) as checkpointer: + # interrupt after agent + app_w_interrupt = workflow.compile( checkpointer=checkpointer, interrupt_after=["agent"], @@ -4473,6 +6194,9 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: {"__interrupt__": ()}, ] + if not FF_SEND_V2: + return + assert await app_w_interrupt.aget_state(config) == StateSnapshot( values={ "messages": [ @@ -4490,7 +6214,33 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: ), ] }, - tasks=(PregelTask(AnyStr(), "tools", (PUSH, 0)),), + tasks=( + PregelTask( + id=AnyStr(), + name="agent", + path=("__pregel_pull", "agent"), + error=None, + interrupts=(), + state=None, + result={ + "messages": AIMessage( + "", + id="ai1", + tool_calls=[ + { + "name": "search_api", + "args": {"query": "query"}, + "id": "tool_call123", + "type": "tool_call", + } + ], + ) + }, + ), + PregelTask( + AnyStr(), "tools", (PUSH, ("__pregel_pull", "agent"), 2, AnyStr()) + ), + ), next=("tools",), config=(await app_w_interrupt.checkpointer.aget_tuple(config)).config, created_at=( @@ -4499,22 +6249,9 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: metadata={ "parents": {}, "source": "loop", - "step": 1, - "writes": { - "agent": { - "messages": AIMessage( - id="ai1", - content="", - tool_calls=[ - { - "id": "tool_call123", - "name": "search_api", - "args": {"query": "query"}, - }, - ], - ) - } - }, + "step": 0, + "writes": None, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -4545,14 +6282,14 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: ), ] }, - tasks=(PregelTask(AnyStr(), "tools", (PUSH, 0)),), + tasks=(PregelTask(AnyStr(), "tools", (PUSH, (), 0, AnyStr())),), next=("tools",), config=tup.config, created_at=tup.checkpoint["ts"], metadata={ "parents": {}, "source": "update", - "step": 2, + "step": 1, "writes": { "agent": { "messages": AIMessage( @@ -4568,6 +6305,7 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: ) } }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -4647,8 +6385,40 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: ] }, tasks=( - PregelTask(AnyStr(), "tools", (PUSH, 0)), - PregelTask(AnyStr(), "tools", (PUSH, 1)), + PregelTask( + id=AnyStr(), + name="agent", + path=("__pregel_pull", "agent"), + error=None, + interrupts=(), + state=None, + result={ + "messages": AIMessage( + "", + id="ai2", + tool_calls=[ + { + "name": "search_api", + "args": {"query": "another", "idx": 0}, + "id": "tool_call234", + "type": "tool_call", + }, + { + "name": "search_api", + "args": {"query": "a third one", "idx": 1}, + "id": "tool_call567", + "type": "tool_call", + }, + ], + ) + }, + ), + PregelTask( + AnyStr(), "tools", (PUSH, ("__pregel_pull", "agent"), 2, AnyStr()) + ), + PregelTask( + AnyStr(), "tools", (PUSH, ("__pregel_pull", "agent"), 3, AnyStr()) + ), ), next=("tools", "tools"), config=tup.config, @@ -4656,27 +6426,17 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: metadata={ "parents": {}, "source": "loop", - "step": 4, + "step": 2, "writes": { - "agent": { - "messages": AIMessage( - id="ai2", - content="", - tool_calls=[ - { - "id": "tool_call234", - "name": "search_api", - "args": {"query": "another", "idx": 0}, - }, - { - "id": "tool_call567", - "name": "search_api", - "args": {"query": "a third one", "idx": 1}, - }, - ], - ) + "tools": { + "messages": _AnyIdToolMessage( + content="result for a different query", + name="search_api", + tool_call_id="tool_call123", + ), }, }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -4720,12 +6480,346 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: metadata={ "parents": {}, "source": "update", - "step": 5, + "step": 3, "writes": { "agent": { "messages": AIMessage(content="answer", id="ai2"), } }, + "thread_id": "1", + }, + parent_config=[ + c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) + ][-1].config, + ) + + # interrupt before tools + + app_w_interrupt = workflow.compile( + checkpointer=checkpointer, + interrupt_before=["tools"], + ) + config = {"configurable": {"thread_id": "2"}} + model.i = 0 + + assert [ + c + async for c in app_w_interrupt.astream( + {"messages": HumanMessage(content="what is weather in sf")}, config + ) + ] == [ + { + "agent": { + "messages": AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + }, + ], + ) + } + }, + {"__interrupt__": ()}, + ] + + assert await app_w_interrupt.aget_state(config) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="what is weather in sf"), + AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "query"}, + }, + ], + ), + ] + }, + tasks=( + PregelTask( + id=AnyStr(), + name="agent", + path=("__pregel_pull", "agent"), + error=None, + interrupts=(), + state=None, + result={ + "messages": AIMessage( + content="", + additional_kwargs={}, + response_metadata={}, + id="ai1", + tool_calls=[ + { + "name": "search_api", + "args": {"query": "query"}, + "id": "tool_call123", + "type": "tool_call", + } + ], + ) + }, + ), + PregelTask( + AnyStr(), "tools", (PUSH, ("__pregel_pull", "agent"), 2, AnyStr()) + ), + ), + next=("tools",), + config=(await app_w_interrupt.checkpointer.aget_tuple(config)).config, + created_at=( + await app_w_interrupt.checkpointer.aget_tuple(config) + ).checkpoint["ts"], + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "thread_id": "2", + }, + parent_config=[ + c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) + ][-1].config, + ) + + # modify ai message + last_message = (await app_w_interrupt.aget_state(config)).values["messages"][-1] + last_message.tool_calls[0]["args"]["query"] = "a different query" + await app_w_interrupt.aupdate_state(config, {"messages": last_message}) + + # message was replaced instead of appended + tup = await app_w_interrupt.checkpointer.aget_tuple(config) + assert await app_w_interrupt.aget_state(config) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="what is weather in sf"), + AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + }, + ], + ), + ] + }, + tasks=(PregelTask(AnyStr(), "tools", (PUSH, (), 0, AnyStr())),), + next=("tools",), + config=tup.config, + created_at=tup.checkpoint["ts"], + metadata={ + "parents": {}, + "source": "update", + "step": 1, + "writes": { + "agent": { + "messages": AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + }, + ], + ) + } + }, + "thread_id": "2", + }, + parent_config=[ + c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) + ][-1].config, + ) + + assert [c async for c in app_w_interrupt.astream(None, config)] == [ + { + "tools": { + "messages": _AnyIdToolMessage( + content="result for a different query", + name="search_api", + tool_call_id="tool_call123", + ) + } + }, + { + "agent": { + "messages": AIMessage( + id="ai2", + content="", + tool_calls=[ + { + "id": "tool_call234", + "name": "search_api", + "args": {"query": "another", "idx": 0}, + }, + { + "id": "tool_call567", + "name": "search_api", + "args": {"query": "a third one", "idx": 1}, + }, + ], + ) + }, + }, + {"__interrupt__": ()}, + ] + + tup = await app_w_interrupt.checkpointer.aget_tuple(config) + assert await app_w_interrupt.aget_state(config) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="what is weather in sf"), + AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + }, + ], + ), + _AnyIdToolMessage( + content="result for a different query", + name="search_api", + tool_call_id="tool_call123", + ), + AIMessage( + id="ai2", + content="", + tool_calls=[ + { + "id": "tool_call234", + "name": "search_api", + "args": {"query": "another", "idx": 0}, + }, + { + "id": "tool_call567", + "name": "search_api", + "args": {"query": "a third one", "idx": 1}, + }, + ], + ), + ] + }, + tasks=( + PregelTask( + id=AnyStr(), + name="agent", + path=("__pregel_pull", "agent"), + error=None, + interrupts=(), + state=None, + result={ + "messages": AIMessage( + content="", + additional_kwargs={}, + response_metadata={}, + id="ai2", + tool_calls=[ + { + "name": "search_api", + "args": {"query": "another", "idx": 0}, + "id": "tool_call234", + "type": "tool_call", + }, + { + "name": "search_api", + "args": {"query": "a third one", "idx": 1}, + "id": "tool_call567", + "type": "tool_call", + }, + ], + ) + }, + ), + PregelTask( + AnyStr(), "tools", (PUSH, ("__pregel_pull", "agent"), 2, AnyStr()) + ), + PregelTask( + AnyStr(), "tools", (PUSH, ("__pregel_pull", "agent"), 3, AnyStr()) + ), + ), + next=("tools", "tools"), + config=tup.config, + created_at=tup.checkpoint["ts"], + metadata={ + "parents": {}, + "source": "loop", + "step": 2, + "writes": { + "tools": { + "messages": _AnyIdToolMessage( + content="result for a different query", + name="search_api", + tool_call_id="tool_call123", + ), + }, + }, + "thread_id": "2", + }, + parent_config=[ + c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) + ][-1].config, + ) + + await app_w_interrupt.aupdate_state( + config, + {"messages": AIMessage(content="answer", id="ai2")}, + ) + + # replaces message even if object identity is different, as long as id is the same + tup = await app_w_interrupt.checkpointer.aget_tuple(config) + assert await app_w_interrupt.aget_state(config) == StateSnapshot( + values={ + "messages": [ + _AnyIdHumanMessage(content="what is weather in sf"), + AIMessage( + id="ai1", + content="", + tool_calls=[ + { + "id": "tool_call123", + "name": "search_api", + "args": {"query": "a different query"}, + }, + ], + ), + _AnyIdToolMessage( + content="result for a different query", + name="search_api", + tool_call_id="tool_call123", + ), + AIMessage(content="answer", id="ai2"), + ] + }, + tasks=(), + next=(), + config=tup.config, + created_at=tup.checkpoint["ts"], + metadata={ + "parents": {}, + "source": "update", + "step": 3, + "writes": { + "agent": { + "messages": AIMessage(content="answer", id="ai2"), + } + }, + "thread_id": "2", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -4986,6 +7080,7 @@ async def test_message_graph(checkpointer_name: str) -> None: id="ai1", ) }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -5035,6 +7130,7 @@ async def test_message_graph(checkpointer_name: str) -> None: id="ai1", ) }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -5120,6 +7216,7 @@ async def test_message_graph(checkpointer_name: str) -> None: id="ai2", ) }, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -5163,6 +7260,7 @@ async def test_message_graph(checkpointer_name: str) -> None: "source": "update", "step": 5, "writes": {"agent": AIMessage(content="answer", id="ai2")}, + "thread_id": "1", }, parent_config=[ c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) @@ -5465,12 +7563,16 @@ async def test_start_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 0, "writes": None, + "assistant_id": "a", + "thread_id": "1", }, { "parents": {}, "source": "input", "step": -1, "writes": {"__start__": {"my_key": "value", "market": "DE"}}, + "assistant_id": "a", + "thread_id": "1", }, ] assert await tool_two.aget_state(thread1) == StateSnapshot( @@ -5481,7 +7583,14 @@ async def test_start_branch_then(checkpointer_name: str) -> None: created_at=(await tool_two.checkpointer.aget_tuple(thread1)).checkpoint[ "ts" ], - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "assistant_id": "a", + "thread_id": "1", + }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread1, limit=2) ][-1].config, @@ -5504,6 +7613,8 @@ async def test_start_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 1, "writes": {"tool_two_slow": {"my_key": " slow"}}, + "assistant_id": "a", + "thread_id": "1", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread1, limit=2) @@ -5524,7 +7635,14 @@ async def test_start_branch_then(checkpointer_name: str) -> None: created_at=(await tool_two.checkpointer.aget_tuple(thread2)).checkpoint[ "ts" ], - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "assistant_id": "a", + "thread_id": "2", + }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread2, limit=2) ][-1].config, @@ -5547,6 +7665,8 @@ async def test_start_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 1, "writes": {"tool_two_fast": {"my_key": " fast"}}, + "assistant_id": "a", + "thread_id": "2", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread2, limit=2) @@ -5567,7 +7687,14 @@ async def test_start_branch_then(checkpointer_name: str) -> None: created_at=(await tool_two.checkpointer.aget_tuple(thread3)).checkpoint[ "ts" ], - metadata={"parents": {}, "source": "loop", "step": 0, "writes": None}, + metadata={ + "parents": {}, + "source": "loop", + "step": 0, + "writes": None, + "assistant_id": "b", + "thread_id": "3", + }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread3, limit=2) ][-1].config, @@ -5587,6 +7714,8 @@ async def test_start_branch_then(checkpointer_name: str) -> None: "source": "update", "step": 1, "writes": {START: {"my_key": "key"}}, + "assistant_id": "b", + "thread_id": "3", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread3, limit=2) @@ -5610,6 +7739,8 @@ async def test_start_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 2, "writes": {"tool_two_fast": {"my_key": " fast"}}, + "assistant_id": "b", + "thread_id": "3", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread3, limit=2) @@ -5678,6 +7809,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "input", "step": -1, "writes": {"__start__": {"my_key": "value", "market": "DE"}}, + "thread_id": "10", }, "parent_config": None, "next": ["__start__"], @@ -5716,6 +7848,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 0, "writes": None, + "thread_id": "10", }, "parent_config": { "tags": [], @@ -5787,6 +7920,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "10", }, "parent_config": { "tags": [], @@ -5858,6 +7992,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 2, "writes": {"tool_two_slow": {"my_key": " slow"}}, + "thread_id": "10", }, "parent_config": { "tags": [], @@ -5929,6 +8064,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "10", }, "parent_config": { "tags": [], @@ -5986,6 +8122,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "input", "step": -1, "writes": {"__start__": {"my_key": "value", "market": "DE"}}, + "thread_id": "11", }, "parent_config": None, "next": ["__start__"], @@ -6024,6 +8161,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 0, "writes": None, + "thread_id": "11", }, "parent_config": { "tags": [], @@ -6095,6 +8233,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "11", }, "parent_config": { "tags": [], @@ -6132,6 +8271,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "11", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread1, limit=2) @@ -6155,6 +8295,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "11", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread1, limit=2) @@ -6180,6 +8321,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "12", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread2, limit=2) @@ -6203,6 +8345,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "12", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread2, limit=2) @@ -6236,6 +8379,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "21", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread1, limit=2) @@ -6259,6 +8403,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "21", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread1, limit=2) @@ -6284,6 +8429,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "22", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread2, limit=2) @@ -6307,6 +8453,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "22", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread2, limit=2) @@ -6330,6 +8477,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "update", "step": 0, "writes": {START: {"my_key": "key", "market": "DE"}}, + "thread_id": "23", }, parent_config=None, ) @@ -6352,6 +8500,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 1, "writes": {"prepare": {"my_key": " prepared"}}, + "thread_id": "23", }, parent_config=uconfig, ) @@ -6373,6 +8522,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "source": "loop", "step": 3, "writes": {"finish": {"my_key": " finished"}}, + "thread_id": "23", }, parent_config=[ c async for c in tool_two.checkpointer.alist(thread3, limit=2) @@ -6724,6 +8874,7 @@ async def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class( "source": "loop", "writes": {"qa": {"answer": "doc1,doc2,doc3,doc4"}}, "step": 4, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -7334,16 +9485,16 @@ async def test_stream_subgraphs_during_execution(checkpointer_name: str) -> None {"inner_1": {"my_key": "got here", "my_other_key": ""}}, ), ), - (FloatBetween(0.2, 0.3), ((), {"outer_1": {"my_key": " and parallel"}})), + (FloatBetween(0.2, 0.4), ((), {"outer_1": {"my_key": " and parallel"}})), ( - FloatBetween(0.5, 0.6), + FloatBetween(0.5, 0.7), ( (AnyStr("inner:"),), {"inner_2": {"my_key": " and there", "my_other_key": "got here"}}, ), ), - (FloatBetween(0.5, 0.6), ((), {"inner": {"my_key": "got here and there"}})), - (FloatBetween(0.5, 0.6), ((), {"outer_2": {"my_key": " and back again"}})), + (FloatBetween(0.5, 0.7), ((), {"inner": {"my_key": "got here and there"}})), + (FloatBetween(0.5, 0.7), ((), {"outer_2": {"my_key": " and back again"}})), ] @@ -7429,7 +9580,7 @@ async def test_nested_graph_interrupts_parallel(checkpointer_name: str) -> None: # test invoke w/ nested interrupt config = {"configurable": {"thread_id": "1"}} assert await app.ainvoke({"my_key": ""}, config, debug=True) == { - "my_key": "", + "my_key": " and parallel", } assert await app.ainvoke(None, config, debug=True) == { @@ -7464,6 +9615,7 @@ async def test_nested_graph_interrupts_parallel(checkpointer_name: str) -> None: c async for c in app.astream({"my_key": ""}, config, stream_mode="values") ] == [ {"my_key": ""}, + {"my_key": " and parallel"}, ] assert [c async for c in app.astream(None, config, stream_mode="values")] == [ {"my_key": ""}, @@ -7482,6 +9634,7 @@ async def test_nested_graph_interrupts_parallel(checkpointer_name: str) -> None: # while we're waiting for the node w/ interrupt inside to finish assert [c async for c in app.astream(None, config, stream_mode="values")] == [ {"my_key": ""}, + {"my_key": " and parallel"}, ] assert [c async for c in app.astream(None, config, stream_mode="values")] == [ {"my_key": ""}, @@ -7496,6 +9649,7 @@ async def test_nested_graph_interrupts_parallel(checkpointer_name: str) -> None: c async for c in app.astream({"my_key": ""}, config, stream_mode="values") ] == [ {"my_key": ""}, + {"my_key": " and parallel"}, ] assert [c async for c in app.astream(None, config, stream_mode="values")] == [ {"my_key": ""}, @@ -7570,15 +9724,27 @@ async def test_doubly_nested_graph_interrupts(checkpointer_name: str) -> None: } # test stream updates w/ nested interrupt - config = {"configurable": {"thread_id": "2"}} + nodes: list[str] = [] + config = { + "configurable": {"thread_id": "2", CONFIG_KEY_NODE_FINISHED: nodes.append} + } assert [c async for c in app.astream({"my_key": "my value"}, config)] == [ {"parent_1": {"my_key": "hi my value"}}, {"__interrupt__": ()}, ] + assert nodes == ["parent_1", "grandchild_1"] assert [c async for c in app.astream(None, config)] == [ {"child": {"my_key": "hi my value here and there"}}, {"parent_2": {"my_key": "hi my value here and there and back again"}}, ] + assert nodes == [ + "parent_1", + "grandchild_1", + "grandchild_2", + "child_1", + "child", + "parent_2", + ] # test stream values w/ nested interrupt config = {"configurable": {"thread_id": "3"}} @@ -7677,6 +9843,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"outer_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -7703,9 +9870,8 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: tasks=( PregelTask( AnyStr(), - name="inner_2", - path=(PULL, "inner_2"), - error=None, + "inner_2", + (PULL, "inner_2"), ), ), next=("inner_2",), @@ -7731,6 +9897,13 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: } }, "step": 1, + "thread_id": "1", + "checkpoint_ns": AnyStr("inner:"), + "langgraph_node": "inner", + "langgraph_path": [PULL, "inner"], + "langgraph_step": 2, + "langgraph_triggers": ["outer_1"], + "langgraph_checkpoint_ns": AnyStr("inner:"), }, created_at=AnyStr(), parent_config={ @@ -7759,6 +9932,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"outer_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -7800,6 +9974,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"outer_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -7833,6 +10008,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": None, "step": 0, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -7866,6 +10042,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "source": "input", "writes": {"__start__": {"my_key": "my value"}}, "step": -1, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -7885,7 +10062,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "checkpoint_ns": AnyStr("inner:"), "checkpoint_id": AnyStr(), "checkpoint_map": AnyDict( - {"": AnyStr(), AnyStr("inner:"): AnyStr()} + {"": AnyStr(), AnyStr("child:"): AnyStr()} ), } }, @@ -7899,6 +10076,13 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: }, "step": 1, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("inner:"), + "langgraph_node": "inner", + "langgraph_path": [PULL, "inner"], + "langgraph_step": 2, + "langgraph_triggers": ["outer_1"], + "langgraph_checkpoint_ns": AnyStr("inner:"), }, created_at=AnyStr(), parent_config={ @@ -7907,13 +10091,11 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "checkpoint_ns": AnyStr("inner:"), "checkpoint_id": AnyStr(), "checkpoint_map": AnyDict( - {"": AnyStr(), AnyStr("inner:"): AnyStr()} + {"": AnyStr(), AnyStr("child:"): AnyStr()} ), } }, - tasks=( - PregelTask(id=AnyStr(), name="inner_2", path=(PULL, "inner_2")), - ), + tasks=(PregelTask(AnyStr(), "inner_2", (PULL, "inner_2")),), ), StateSnapshot( values={"my_key": "hi my value"}, @@ -7924,7 +10106,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "checkpoint_ns": AnyStr("inner:"), "checkpoint_id": AnyStr(), "checkpoint_map": AnyDict( - {"": AnyStr(), AnyStr("inner:"): AnyStr()} + {"": AnyStr(), AnyStr("child:"): AnyStr()} ), } }, @@ -7933,6 +10115,13 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "writes": None, "step": 0, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("inner:"), + "langgraph_node": "inner", + "langgraph_path": [PULL, "inner"], + "langgraph_step": 2, + "langgraph_triggers": ["outer_1"], + "langgraph_checkpoint_ns": AnyStr("inner:"), }, created_at=AnyStr(), parent_config={ @@ -7941,15 +10130,15 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "checkpoint_ns": AnyStr("inner:"), "checkpoint_id": AnyStr(), "checkpoint_map": AnyDict( - {"": AnyStr(), AnyStr("inner:"): AnyStr()} + {"": AnyStr(), AnyStr("child:"): AnyStr()} ), } }, tasks=( PregelTask( - id=AnyStr(), - name="inner_1", - path=(PULL, "inner_1"), + AnyStr(), + "inner_1", + (PULL, "inner_1"), result={ "my_key": "hi my value here", "my_other_key": "hi my value", @@ -7966,7 +10155,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "checkpoint_ns": AnyStr("inner:"), "checkpoint_id": AnyStr(), "checkpoint_map": AnyDict( - {"": AnyStr(), AnyStr("inner:"): AnyStr()} + {"": AnyStr(), AnyStr("child:"): AnyStr()} ), } }, @@ -7975,14 +10164,21 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "writes": {"__start__": {"my_key": "hi my value"}}, "step": -1, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("inner:"), + "langgraph_node": "inner", + "langgraph_path": [PULL, "inner"], + "langgraph_step": 2, + "langgraph_triggers": ["outer_1"], + "langgraph_checkpoint_ns": AnyStr("inner:"), }, created_at=AnyStr(), parent_config=None, tasks=( PregelTask( - id=AnyStr(), - name="__start__", - path=(PULL, "__start__"), + AnyStr(), + "__start__", + (PULL, "__start__"), result={"my_key": "hi my value"}, ), ), @@ -8010,6 +10206,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "outer_2": {"my_key": "hi my value here and there and back again"} }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8043,6 +10240,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: } }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8076,6 +10274,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"inner": {"my_key": "hi my value here and there"}}, "step": 2, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8115,6 +10314,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"outer_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8148,6 +10348,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": None, "step": 0, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8181,6 +10382,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "source": "input", "writes": {"__start__": {"my_key": "my value"}}, "step": -1, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -8286,6 +10488,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"parent_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8320,9 +10523,6 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "thread_id": "1", "checkpoint_ns": AnyStr("child:"), "checkpoint_id": AnyStr(), - "checkpoint_map": AnyDict( - {"": AnyStr(), AnyStr("child:"): AnyStr()} - ), } }, metadata={ @@ -8330,6 +10530,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": None, "step": 0, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8337,9 +10538,6 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "thread_id": "1", "checkpoint_ns": AnyStr("child:"), "checkpoint_id": AnyStr(), - "checkpoint_map": AnyDict( - {"": AnyStr(), AnyStr("child:"): AnyStr()} - ), } }, ).tasks[0] @@ -8379,6 +10577,13 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"grandchild_1": {"my_key": "hi my value here"}}, "step": 1, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [PULL, AnyStr("child_1")], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -8451,6 +10656,16 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: } }, "step": 1, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -8488,6 +10703,13 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": None, "step": 0, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child", + "langgraph_path": [PULL, AnyStr("child")], + "langgraph_step": 2, + "langgraph_triggers": [AnyStr("parent_1")], + "langgraph_checkpoint_ns": AnyStr("child:"), }, created_at=AnyStr(), parent_config={ @@ -8516,6 +10738,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"parent_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8563,6 +10786,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: } }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8599,6 +10823,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: } }, "step": 3, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8624,6 +10849,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"child": {"my_key": "hi my value here and there"}}, "step": 2, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8667,6 +10893,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": {"parent_1": {"my_key": "hi my value"}}, "step": 1, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8692,6 +10919,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "source": "loop", "writes": None, "step": 0, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -8722,6 +10950,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "source": "input", "writes": {"my_key": "my value"}, "step": -1, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, @@ -8756,6 +10985,13 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "writes": {"child_1": {"my_key": "hi my value here and there"}}, "step": 1, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child", + "langgraph_path": [PULL, AnyStr("child")], + "langgraph_step": 2, + "langgraph_triggers": [AnyStr("parent_1")], + "langgraph_checkpoint_ns": AnyStr("child:"), }, created_at=AnyStr(), parent_config={ @@ -8788,6 +11024,13 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "writes": None, "step": 0, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child", + "langgraph_path": [PULL, AnyStr("child")], + "langgraph_step": 2, + "langgraph_triggers": [AnyStr("parent_1")], + "langgraph_checkpoint_ns": AnyStr("child:"), }, created_at=AnyStr(), parent_config={ @@ -8833,6 +11076,13 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "writes": {"__start__": {"my_key": "hi my value"}}, "step": -1, "parents": {"": AnyStr()}, + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child", + "langgraph_path": [PULL, AnyStr("child")], + "langgraph_step": 2, + "langgraph_triggers": [AnyStr("parent_1")], + "langgraph_checkpoint_ns": AnyStr("child:"), }, created_at=AnyStr(), parent_config=None, @@ -8880,6 +11130,16 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: AnyStr("child:"): AnyStr(), } ), + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -8925,6 +11185,16 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: AnyStr("child:"): AnyStr(), } ), + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -8977,6 +11247,16 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: AnyStr("child:"): AnyStr(), } ), + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config={ @@ -9029,6 +11309,16 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: AnyStr("child:"): AnyStr(), } ), + "thread_id": "1", + "checkpoint_ns": AnyStr("child:"), + "langgraph_checkpoint_ns": AnyStr("child:"), + "langgraph_node": "child_1", + "langgraph_path": [ + PULL, + AnyStr("child_1"), + ], + "langgraph_step": 1, + "langgraph_triggers": [AnyStr("start:child_1")], }, created_at=AnyStr(), parent_config=None, @@ -9100,7 +11390,8 @@ async def test_send_to_nested_graphs(checkpointer_name: str) -> None: # invoke and pause at nested interrupt assert await graph.ainvoke( - {"subjects": ["cats", "dogs"]}, config={**config, "callbacks": [tracer]} + {"subjects": ["cats", "dogs"]}, + config={**config, "callbacks": [tracer]}, ) == { "subjects": ["cats", "dogs"], "jokes": [], @@ -9109,13 +11400,36 @@ async def test_send_to_nested_graphs(checkpointer_name: str) -> None: # check state outer_state = await graph.aget_state(config) + + if not FF_SEND_V2: + # update state of dogs joke graph + await graph.aupdate_state( + outer_state.tasks[1].state, {"subject": "turtles - hohoho"} + ) + + # continue past interrupt + assert await graph.ainvoke(None, config=config) == { + "subjects": ["cats", "dogs"], + "jokes": ["Joke about cats - hohoho", "Joke about turtles - hohoho"], + } + return + assert outer_state == StateSnapshot( values={"subjects": ["cats", "dogs"], "jokes": []}, tasks=( + PregelTask( + id=AnyStr(), + name="__start__", + path=("__pregel_pull", "__start__"), + error=None, + interrupts=(), + state=None, + result={"subjects": ["cats", "dogs"]}, + ), PregelTask( AnyStr(), "generate_joke", - (PUSH, 0), + (PUSH, ("__pregel_pull", "__start__"), 1, AnyStr()), state={ "configurable": { "thread_id": "1", @@ -9126,7 +11440,7 @@ async def test_send_to_nested_graphs(checkpointer_name: str) -> None: PregelTask( AnyStr(), "generate_joke", - (PUSH, 1), + (PUSH, ("__pregel_pull", "__start__"), 2, AnyStr()), state={ "configurable": { "thread_id": "1", @@ -9143,20 +11457,27 @@ async def test_send_to_nested_graphs(checkpointer_name: str) -> None: "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "writes": None, "step": 0}, - created_at=AnyStr(), - parent_config={ - "configurable": { - "thread_id": "1", - "checkpoint_ns": "", - "checkpoint_id": AnyStr(), - } + metadata={ + "parents": {}, + "source": "input", + "writes": { + "__start__": { + "subjects": [ + "cats", + "dogs", + ], + } + }, + "step": -1, + "thread_id": "1", }, + created_at=AnyStr(), + parent_config=None, ) # update state of dogs joke graph await graph.aupdate_state( - outer_state.tasks[1].state, {"subject": "turtles - hohoho"} + outer_state.tasks[2].state, {"subject": "turtles - hohoho"} ) # continue past interrupt @@ -9189,7 +11510,8 @@ async def test_send_to_nested_graphs(checkpointer_name: str) -> None: {"jokes": ["Joke about turtles - hohoho"]}, ] }, - "step": 1, + "step": 0, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9231,7 +11553,8 @@ async def test_send_to_nested_graphs(checkpointer_name: str) -> None: {"jokes": ["Joke about turtles - hohoho"]}, ] }, - "step": 1, + "step": 0, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9243,13 +11566,22 @@ async def test_send_to_nested_graphs(checkpointer_name: str) -> None: }, ), StateSnapshot( - values={"subjects": ["cats", "dogs"], "jokes": []}, - next=("generate_joke", "generate_joke"), + values={"jokes": []}, + next=("__start__", "generate_joke", "generate_joke"), tasks=( + PregelTask( + id=AnyStr(), + name="__start__", + path=("__pregel_pull", "__start__"), + error=None, + interrupts=(), + state=None, + result={"subjects": ["cats", "dogs"]}, + ), PregelTask( AnyStr(), "generate_joke", - (PUSH, 0), + (PUSH, ("__pregel_pull", "__start__"), 1, AnyStr()), state={ "configurable": { "thread_id": "1", @@ -9261,7 +11593,7 @@ async def test_send_to_nested_graphs(checkpointer_name: str) -> None: PregelTask( AnyStr(), "generate_joke", - (PUSH, 1), + (PUSH, ("__pregel_pull", "__start__"), 2, AnyStr()), state={ "configurable": { "thread_id": "1", @@ -9278,45 +11610,18 @@ async def test_send_to_nested_graphs(checkpointer_name: str) -> None: "checkpoint_id": AnyStr(), } }, - metadata={"parents": {}, "source": "loop", "writes": None, "step": 0}, - created_at=AnyStr(), - parent_config={ - "configurable": { - "thread_id": "1", - "checkpoint_ns": "", - "checkpoint_id": AnyStr(), - } - }, - ), - StateSnapshot( - values={"jokes": []}, - tasks=( - PregelTask( - AnyStr(), - "__start__", - (PULL, "__start__"), - result={"subjects": ["cats", "dogs"]}, - ), - ), - next=("__start__",), - config={ - "configurable": { - "thread_id": "1", - "checkpoint_ns": "", - "checkpoint_id": AnyStr(), - } - }, metadata={ "parents": {}, "source": "input", "writes": {"__start__": {"subjects": ["cats", "dogs"]}}, "step": -1, + "thread_id": "1", }, created_at=AnyStr(), parent_config=None, ), ] - assert actual_history[1] == expected_history[1] + assert actual_history == expected_history @pytest.mark.skipif( @@ -9488,6 +11793,7 @@ async def test_weather_subgraph( "writes": {"router_node": {"route": "weather"}}, "step": 1, "parents": {}, + "thread_id": "1", }, created_at=AnyStr(), parent_config={ @@ -9579,6 +11885,7 @@ async def test_weather_subgraph( "writes": {"router_node": {"route": "weather"}}, "step": 1, "parents": {}, + "thread_id": "14", }, created_at=AnyStr(), parent_config={ @@ -9619,6 +11926,15 @@ async def test_weather_subgraph( "writes": {"model_node": {"city": "San Francisco"}}, "step": 1, "parents": {"": AnyStr()}, + "thread_id": "14", + "checkpoint_ns": AnyStr("weather_graph:"), + "langgraph_node": "weather_graph", + "langgraph_path": [PULL, "weather_graph"], + "langgraph_step": 2, + "langgraph_triggers": [ + "branch:router_node:route_after_prediction:weather_graph" + ], + "langgraph_checkpoint_ns": AnyStr("weather_graph:"), }, created_at=AnyStr(), parent_config={ @@ -9669,6 +11985,7 @@ async def test_weather_subgraph( "writes": {"router_node": {"route": "weather"}}, "step": 1, "parents": {}, + "thread_id": "14", }, created_at=AnyStr(), parent_config={ @@ -9706,8 +12023,8 @@ async def test_weather_subgraph( } }, metadata={ - "source": "update", "step": 2, + "source": "update", "writes": { "weather_node": { "messages": [ @@ -9716,6 +12033,16 @@ async def test_weather_subgraph( } }, "parents": {"": AnyStr()}, + "thread_id": "14", + "checkpoint_id": AnyStr(), + "checkpoint_ns": AnyStr("weather_graph:"), + "langgraph_node": "weather_graph", + "langgraph_path": [PULL, "weather_graph"], + "langgraph_step": 2, + "langgraph_triggers": [ + "branch:router_node:route_after_prediction:weather_graph" + ], + "langgraph_checkpoint_ns": AnyStr("weather_graph:"), }, created_at=AnyStr(), parent_config={ diff --git a/libs/langgraph/tests/test_remote_pregel.py b/libs/langgraph/tests/test_remote_graph.py similarity index 59% rename from libs/langgraph/tests/test_remote_pregel.py rename to libs/langgraph/tests/test_remote_graph.py index f2ca53583..70857ed61 100644 --- a/libs/langgraph/tests/test_remote_pregel.py +++ b/libs/langgraph/tests/test_remote_graph.py @@ -7,7 +7,9 @@ from langchain_core.runnables.graph import ( from langchain_core.runnables.graph import ( Node as DrawableNode, ) +from langgraph_sdk.schema import StreamPart +from langgraph.errors import GraphInterrupt from langgraph.pregel.remote import RemoteGraph from langgraph.pregel.types import StateSnapshot @@ -15,7 +17,7 @@ from langgraph.pregel.types import StateSnapshot def test_with_config(): # set up test remote_pregel = RemoteGraph( - graph_id="test_graph_id", + "test_graph_id", config={ "configurable": { "foo": "bar", @@ -52,7 +54,7 @@ def test_get_graph(): "type": "runnable", "data": { "id": ["langgraph", "utils", "RunnableCallable"], - "name": "agent", + "name": "agent_1", }, }, ], @@ -62,20 +64,22 @@ def test_get_graph(): ], } - remote_pregel = RemoteGraph(sync_client=mock_sync_client, graph_id="test_graph_id") + remote_pregel = RemoteGraph("test_graph_id", sync_client=mock_sync_client) # call method / assertions drawable_graph = remote_pregel.get_graph() assert drawable_graph.nodes == { "__start__": DrawableNode( - id="__start__", name="", data="__start__", metadata=None + id="__start__", name="__start__", data="__start__", metadata=None + ), + "__end__": DrawableNode( + id="__end__", name="__end__", data="__end__", metadata=None ), - "__end__": DrawableNode(id="__end__", name="", data="__end__", metadata=None), "agent": DrawableNode( id="agent", - name="", - data={"id": ["langgraph", "utils", "RunnableCallable"], "name": "agent"}, + name="agent_1", + data={"id": ["langgraph", "utils", "RunnableCallable"], "name": "agent_1"}, metadata=None, ), } @@ -99,7 +103,7 @@ async def test_aget_graph(): "type": "runnable", "data": { "id": ["langgraph", "utils", "RunnableCallable"], - "name": "agent", + "name": "agent_1", }, }, ], @@ -109,20 +113,22 @@ async def test_aget_graph(): ], } - remote_pregel = RemoteGraph(client=mock_async_client, graph_id="test_graph_id") + remote_pregel = RemoteGraph("test_graph_id", client=mock_async_client) # call method / assertions drawable_graph = await remote_pregel.aget_graph() assert drawable_graph.nodes == { "__start__": DrawableNode( - id="__start__", name="", data="__start__", metadata=None + id="__start__", name="__start__", data="__start__", metadata=None + ), + "__end__": DrawableNode( + id="__end__", name="__end__", data="__end__", metadata=None ), - "__end__": DrawableNode(id="__end__", name="", data="__end__", metadata=None), "agent": DrawableNode( id="agent", - name="", - data={"id": ["langgraph", "utils", "RunnableCallable"], "name": "agent"}, + name="agent_1", + data={"id": ["langgraph", "utils", "RunnableCallable"], "name": "agent_1"}, metadata=None, ), } @@ -133,92 +139,6 @@ async def test_aget_graph(): ] -def test_get_subgraphs(): - # set up test - mock_sync_client = MagicMock() - mock_sync_client.assistants.get_subgraphs.return_value = { - "namespace_1": { - "graph_id": "test_graph_id_2", - "input_schema": {}, - "output_schema": {}, - "state_schema": {}, - "config_schema": {}, - }, - "namespace_2": { - "graph_id": "test_graph_id_3", - "input_schema": {}, - "output_schema": {}, - "state_schema": {}, - "config_schema": {}, - }, - } - - remote_pregel = RemoteGraph( - sync_client=mock_sync_client, graph_id="test_graph_id_1" - ) - - # call method / assertions - subgraphs = list(remote_pregel.get_subgraphs()) - assert len(subgraphs) == 2 - - subgraph_1 = subgraphs[0] - ns_1 = subgraph_1[0] - remote_pregel_1: RemoteGraph = subgraph_1[1] - assert ns_1 == "namespace_1" - assert remote_pregel_1.graph_id == "test_graph_id_2" - - subgraph_2 = subgraphs[1] - ns_2 = subgraph_2[0] - remote_pregel_2: RemoteGraph = subgraph_2[1] - assert ns_2 == "namespace_2" - assert remote_pregel_2.graph_id == "test_graph_id_3" - - -@pytest.mark.anyio -async def test_aget_subgraphs(): - # set up test - mock_async_client = AsyncMock() - mock_async_client.assistants.get_subgraphs.return_value = { - "namespace_1": { - "graph_id": "test_graph_id_2", - "input_schema": {}, - "output_schema": {}, - "state_schema": {}, - "config_schema": {}, - }, - "namespace_2": { - "graph_id": "test_graph_id_3", - "input_schema": {}, - "output_schema": {}, - "state_schema": {}, - "config_schema": {}, - }, - } - - remote_pregel = RemoteGraph( - client=mock_async_client, - graph_id="test_graph_id_1", - ) - - # call method / assertions - subgraphs = [] - async for subgraph in remote_pregel.aget_subgraphs(): - subgraphs.append(subgraph) - assert len(subgraphs) == 2 - - subgraph_1 = subgraphs[0] - ns_1 = subgraph_1[0] - remote_pregel_1: RemoteGraph = subgraph_1[1] - assert ns_1 == "namespace_1" - assert remote_pregel_1.graph_id == "test_graph_id_2" - - subgraph_2 = subgraphs[1] - ns_2 = subgraph_2[0] - remote_pregel_2: RemoteGraph = subgraph_2[1] - assert ns_2 == "namespace_2" - assert remote_pregel_2.graph_id == "test_graph_id_3" - - def test_get_state(): # set up test mock_sync_client = MagicMock() @@ -238,7 +158,10 @@ def test_get_state(): } # call method / assertions - remote_pregel = RemoteGraph(sync_client=mock_sync_client, graph_id="test_graph_id") + remote_pregel = RemoteGraph( + "test_graph_id", + sync_client=mock_sync_client, + ) config = {"configurable": {"thread_id": "thread1"}} state_snapshot = remote_pregel.get_state(config) @@ -285,7 +208,10 @@ async def test_aget_state(): } # call method / assertions - remote_pregel = RemoteGraph(client=mock_async_client, graph_id="test_graph_id") + remote_pregel = RemoteGraph( + "test_graph_id", + client=mock_async_client, + ) config = {"configurable": {"thread_id": "thread1"}} state_snapshot = await remote_pregel.aget_state(config) @@ -336,7 +262,10 @@ def test_get_state_history(): ] # call method / assertions - remote_pregel = RemoteGraph(sync_client=mock_sync_client, graph_id="test_graph_id") + remote_pregel = RemoteGraph( + "test_graph_id", + sync_client=mock_sync_client, + ) config = {"configurable": {"thread_id": "thread1"}} state_history_snapshot = list( @@ -384,7 +313,10 @@ async def test_aget_state_history(): ] # call method / assertions - remote_pregel = RemoteGraph(client=mock_async_client, graph_id="test_graph_id") + remote_pregel = RemoteGraph( + "test_graph_id", + client=mock_async_client, + ) config = {"configurable": {"thread_id": "thread1"}} state_history_snapshot = [] @@ -425,7 +357,10 @@ def test_update_state(): } # call method / assertions - remote_pregel = RemoteGraph(sync_client=mock_sync_client, graph_id="test_graph_id") + remote_pregel = RemoteGraph( + "test_graph_id", + sync_client=mock_sync_client, + ) config = {"configurable": {"thread_id": "thread1"}} response = remote_pregel.update_state(config, {"key": "value"}) @@ -454,7 +389,10 @@ async def test_aupdate_state(): } # call method / assertions - remote_pregel = RemoteGraph(client=mock_async_client, graph_id="test_graph_id") + remote_pregel = RemoteGraph( + "test_graph_id", + client=mock_async_client, + ) config = {"configurable": {"thread_id": "thread1"}} response = await remote_pregel.aupdate_state(config, {"key": "value"}) @@ -473,17 +411,100 @@ def test_stream(): # set up test mock_sync_client = MagicMock() mock_sync_client.runs.stream.return_value = [ + StreamPart(event="values", data={"chunk": "data1"}), + StreamPart(event="values", data={"chunk": "data2"}), + StreamPart(event="values", data={"chunk": "data3"}), + StreamPart(event="updates", data={"chunk": "data4"}), + StreamPart(event="updates", data={"__interrupt__": ()}), + ] + + # call method / assertions + remote_pregel = RemoteGraph( + "test_graph_id", + sync_client=mock_sync_client, + ) + + # stream modes doesn't include 'updates' + stream_parts = [] + with pytest.raises(GraphInterrupt): + for stream_part in remote_pregel.stream( + {"input": "data"}, + config={"configurable": {"thread_id": "thread_1"}}, + stream_mode="values", + ): + stream_parts.append(stream_part) + + assert stream_parts == [ {"chunk": "data1"}, {"chunk": "data2"}, {"chunk": "data3"}, ] - # call method / assertions - remote_pregel = RemoteGraph(sync_client=mock_sync_client, graph_id="test_graph_id") + mock_sync_client.runs.stream.return_value = [ + StreamPart(event="updates", data={"chunk": "data3"}), + StreamPart(event="updates", data={"chunk": "data4"}), + StreamPart(event="updates", data={"__interrupt__": ()}), + ] - config = {"configurable": {"thread_id": "thread_1"}} - result = list(remote_pregel.stream({"input": "data"}, config)) - assert result == [{"chunk": "data1"}, {"chunk": "data2"}, {"chunk": "data3"}] + # default stream_mode is updates + stream_parts = [] + with pytest.raises(GraphInterrupt): + for stream_part in remote_pregel.stream( + {"input": "data"}, + config={"configurable": {"thread_id": "thread_1"}}, + ): + stream_parts.append(stream_part) + + assert stream_parts == [ + {"chunk": "data3"}, + {"chunk": "data4"}, + ] + + # list stream_mode includes mode names + stream_parts = [] + with pytest.raises(GraphInterrupt): + for stream_part in remote_pregel.stream( + {"input": "data"}, + config={"configurable": {"thread_id": "thread_1"}}, + stream_mode=["updates"], + ): + stream_parts.append(stream_part) + + assert stream_parts == [ + ("updates", {"chunk": "data3"}), + ("updates", {"chunk": "data4"}), + ] + + # subgraphs + list modes + stream_parts = [] + with pytest.raises(GraphInterrupt): + for stream_part in remote_pregel.stream( + {"input": "data"}, + config={"configurable": {"thread_id": "thread_1"}}, + stream_mode=["updates"], + subgraphs=True, + ): + stream_parts.append(stream_part) + + assert stream_parts == [ + ((), "updates", {"chunk": "data3"}), + ((), "updates", {"chunk": "data4"}), + ] + + # subgraphs + single mode + stream_parts = [] + with pytest.raises(GraphInterrupt): + for stream_part in remote_pregel.stream( + {"input": "data"}, + config={"configurable": {"thread_id": "thread_1"}}, + subgraphs=True, + ): + stream_parts.append(stream_part) + + assert stream_parts == [ + ((), {"chunk": "data3"}), + ((), {"chunk": "data4"}), + ] @pytest.mark.anyio @@ -492,57 +513,195 @@ async def test_astream(): mock_async_client = MagicMock() async_iter = MagicMock() async_iter.__aiter__.return_value = [ - {"chunk": "data1"}, - {"chunk": "data2"}, - {"chunk": "data3"}, + StreamPart(event="values", data={"chunk": "data1"}), + StreamPart(event="values", data={"chunk": "data2"}), + StreamPart(event="values", data={"chunk": "data3"}), + StreamPart(event="updates", data={"chunk": "data4"}), + StreamPart(event="updates", data={"__interrupt__": ()}), ] mock_async_client.runs.stream.return_value = async_iter # call method / assertions - remote_pregel = RemoteGraph(client=mock_async_client, graph_id="test_graph_id") + remote_pregel = RemoteGraph( + "test_graph_id", + client=mock_async_client, + ) - config = {"configurable": {"thread_id": "thread_1"}} - chunks = [] - async for chunk in remote_pregel.astream({"input": "data"}, config): - chunks.append(chunk) - assert chunks == [{"chunk": "data1"}, {"chunk": "data2"}, {"chunk": "data3"}] + # stream modes doesn't include 'updates' + stream_parts = [] + with pytest.raises(GraphInterrupt): + async for stream_part in remote_pregel.astream( + {"input": "data"}, + config={"configurable": {"thread_id": "thread_1"}}, + stream_mode="values", + ): + stream_parts.append(stream_part) + + assert stream_parts == [ + {"chunk": "data1"}, + {"chunk": "data2"}, + {"chunk": "data3"}, + ] + + async_iter = MagicMock() + async_iter.__aiter__.return_value = [ + StreamPart(event="updates", data={"chunk": "data3"}), + StreamPart(event="updates", data={"chunk": "data4"}), + StreamPart(event="updates", data={"__interrupt__": ()}), + ] + mock_async_client.runs.stream.return_value = async_iter + + # default stream_mode is updates + stream_parts = [] + with pytest.raises(GraphInterrupt): + async for stream_part in remote_pregel.astream( + {"input": "data"}, + config={"configurable": {"thread_id": "thread_1"}}, + ): + stream_parts.append(stream_part) + + assert stream_parts == [ + {"chunk": "data3"}, + {"chunk": "data4"}, + ] + + # list stream_mode includes mode names + stream_parts = [] + with pytest.raises(GraphInterrupt): + async for stream_part in remote_pregel.astream( + {"input": "data"}, + config={"configurable": {"thread_id": "thread_1"}}, + stream_mode=["updates"], + ): + stream_parts.append(stream_part) + + assert stream_parts == [ + ("updates", {"chunk": "data3"}), + ("updates", {"chunk": "data4"}), + ] + + # subgraphs + list modes + stream_parts = [] + with pytest.raises(GraphInterrupt): + async for stream_part in remote_pregel.astream( + {"input": "data"}, + config={"configurable": {"thread_id": "thread_1"}}, + stream_mode=["updates"], + subgraphs=True, + ): + stream_parts.append(stream_part) + + assert stream_parts == [ + ((), "updates", {"chunk": "data3"}), + ((), "updates", {"chunk": "data4"}), + ] + + # subgraphs + single mode + stream_parts = [] + with pytest.raises(GraphInterrupt): + async for stream_part in remote_pregel.astream( + {"input": "data"}, + config={"configurable": {"thread_id": "thread_1"}}, + subgraphs=True, + ): + stream_parts.append(stream_part) + + assert stream_parts == [ + ((), {"chunk": "data3"}), + ((), {"chunk": "data4"}), + ] + + async_iter = MagicMock() + async_iter.__aiter__.return_value = [ + StreamPart(event="updates|my|subgraph", data={"chunk": "data3"}), + StreamPart(event="updates|hello|subgraph", data={"chunk": "data4"}), + StreamPart(event="updates|bye|subgraph", data={"__interrupt__": ()}), + ] + mock_async_client.runs.stream.return_value = async_iter + + # subgraphs + list modes + stream_parts = [] + with pytest.raises(GraphInterrupt): + async for stream_part in remote_pregel.astream( + {"input": "data"}, + config={"configurable": {"thread_id": "thread_1"}}, + stream_mode=["updates"], + subgraphs=True, + ): + stream_parts.append(stream_part) + + assert stream_parts == [ + (("my", "subgraph"), "updates", {"chunk": "data3"}), + (("hello", "subgraph"), "updates", {"chunk": "data4"}), + ] + + # subgraphs + single mode + stream_parts = [] + with pytest.raises(GraphInterrupt): + async for stream_part in remote_pregel.astream( + {"input": "data"}, + config={"configurable": {"thread_id": "thread_1"}}, + subgraphs=True, + ): + stream_parts.append(stream_part) + + assert stream_parts == [ + (("my", "subgraph"), {"chunk": "data3"}), + (("hello", "subgraph"), {"chunk": "data4"}), + ] def test_invoke(): # set up test mock_sync_client = MagicMock() - mock_sync_client.runs.wait.return_value = { - "values": {"messages": [{"type": "human", "content": "world"}]} - } + mock_sync_client.runs.stream.return_value = [ + StreamPart(event="values", data={"chunk": "data1"}), + StreamPart(event="values", data={"chunk": "data2"}), + StreamPart( + event="values", data={"messages": [{"type": "human", "content": "world"}]} + ), + ] # call method / assertions - remote_pregel = RemoteGraph(sync_client=mock_sync_client, graph_id="test_graph_id") + remote_pregel = RemoteGraph( + "test_graph_id", + sync_client=mock_sync_client, + ) config = {"configurable": {"thread_id": "thread_1"}} result = remote_pregel.invoke( {"input": {"messages": [{"type": "human", "content": "hello"}]}}, config ) - assert result == {"values": {"messages": [{"type": "human", "content": "world"}]}} + assert result == {"messages": [{"type": "human", "content": "world"}]} @pytest.mark.anyio async def test_ainvoke(): # set up test - mock_async_client = AsyncMock() - mock_async_client.runs.wait.return_value = { - "values": {"messages": [{"type": "human", "content": "world"}]} - } + mock_async_client = MagicMock() + async_iter = MagicMock() + async_iter.__aiter__.return_value = [ + StreamPart(event="values", data={"chunk": "data1"}), + StreamPart(event="values", data={"chunk": "data2"}), + StreamPart( + event="values", data={"messages": [{"type": "human", "content": "world"}]} + ), + ] + mock_async_client.runs.stream.return_value = async_iter # call method / assertions - remote_pregel = RemoteGraph(client=mock_async_client, graph_id="test_graph_id") + remote_pregel = RemoteGraph( + "test_graph_id", + client=mock_async_client, + ) config = {"configurable": {"thread_id": "thread_1"}} result = await remote_pregel.ainvoke( {"input": {"messages": [{"type": "human", "content": "hello"}]}}, config ) - assert result == {"values": {"messages": [{"type": "human", "content": "world"}]}} + assert result == {"messages": [{"type": "human", "content": "world"}]} @pytest.mark.skip("Unskip this test to manually test the LangGraph Cloud integration") @@ -557,7 +716,9 @@ async def test_langgraph_cloud_integration(): client = get_client() sync_client = get_sync_client() remote_pregel = RemoteGraph( - client=client, sync_client=sync_client, graph_id="agent" + "agent", + client=client, + sync_client=sync_client, ) # define graph @@ -572,7 +733,7 @@ async def test_langgraph_cloud_integration(): "messages": [ { "role": "human", - "content": "Hello world!", + "content": "What's the weather in SF?", } ] } @@ -580,7 +741,8 @@ async def test_langgraph_cloud_integration(): # test invoke response = app.invoke( input, - config={"configurable": {"thread_id": "2dc3e3e7-39ac-4597-aa57-4404b944e82a"}}, + config={"configurable": {"thread_id": "39a6104a-34e7-4f83-929c-d9eb163003c9"}}, + interrupt_before=["agent"], ) print("response:", response["messages"][-1].content) @@ -634,9 +796,3 @@ async def test_langgraph_cloud_integration(): remote_pregel.graph_id = "fe096781-5601-53d2-b2f6-0d3403f7e9ca" # must be UUID graph = await remote_pregel.aget_graph(xray=True) print("graph:", graph) - - # test get subgraphs - remote_pregel.graph_id = "fe096781-5601-53d2-b2f6-0d3403f7e9ca" # must be UUID - async for name, pregel in remote_pregel.aget_subgraphs(): - print("name:", name) - print("pregel:", pregel) diff --git a/libs/langgraph/tests/test_state.py b/libs/langgraph/tests/test_state.py index 73546b6a1..0a4a8725a 100644 --- a/libs/langgraph/tests/test_state.py +++ b/libs/langgraph/tests/test_state.py @@ -5,11 +5,11 @@ from typing import Annotated as Annotated2 from typing import Any, Optional import pytest -from langchain_core.runnables import RunnableConfig +from langchain_core.runnables import RunnableConfig, RunnableLambda from pydantic.v1 import BaseModel from typing_extensions import Annotated, NotRequired, Required, TypedDict -from langgraph.graph.state import StateGraph, _warn_invalid_state_schema +from langgraph.graph.state import StateGraph, _get_node_name, _warn_invalid_state_schema from langgraph.managed.shared_value import SharedValue @@ -61,6 +61,9 @@ def test_state_schema_with_type_hint(): class OutputState(TypedDict): input_state: InputState + class FooState(InputState): + foo: str + def complete_hint(state: InputState) -> OutputState: return {"input_state": state} @@ -73,24 +76,46 @@ def test_state_schema_with_type_hint(): def miss_all_hint(state, config): return {"input_state": state} + def pre_foo(_) -> FooState: + return {"foo": "bar"} + + class Foo: + def __call__(self, state: FooState) -> OutputState: + assert state.pop("foo") == "bar" + return {"input_state": state} + graph = StateGraph(InputState, output=OutputState) - actions = [complete_hint, miss_first_hint, only_return_hint, miss_all_hint] + actions = [ + complete_hint, + miss_first_hint, + only_return_hint, + miss_all_hint, + pre_foo, + Foo(), + ] for action in actions: graph.add_node(action) - graph.set_entry_point(actions[0].__name__) + def get_name(action) -> str: + return getattr(action, "__name__", action.__class__.__name__) + + graph.set_entry_point(get_name(actions[0])) for i in range(len(actions) - 1): - graph.add_edge(actions[i].__name__, actions[i + 1].__name__) - graph.set_finish_point(actions[-1].__name__) + graph.add_edge(get_name(actions[i]), get_name(actions[i + 1])) + graph.set_finish_point(get_name(actions[-1])) graph = graph.compile() input_state = InputState(question="Hello World!") output_state = OutputState(input_state=input_state) + foo_state = FooState(foo="bar") for i, c in enumerate(graph.stream(input_state, stream_mode="updates")): - node_name = actions[i].__name__ - assert c[node_name] == output_state + node_name = get_name(actions[i]) + if node_name == get_name(pre_foo): + assert c[node_name] == foo_state + else: + assert c[node_name] == output_state @pytest.mark.parametrize("total_", [True, False]) @@ -261,3 +286,35 @@ def test_raises_invalid_managed(): match="Invalid managed channels detected in BadOutputState: some_output_channel. Managed channels are not permitted in Input/Output schema.", ): StateGraph(_state, input=_inp, output=_outp) + + +def test__get_node_name() -> None: + # default runnable name + assert _get_node_name(RunnableLambda(func=lambda x: x)) == "RunnableLambda" + # custom runnable name + assert ( + _get_node_name(RunnableLambda(name="my_runnable", func=lambda x: x)) + == "my_runnable" + ) + + # lambda + assert _get_node_name(lambda x: x) == "" + + # regular function + def func(state): + return + + assert _get_node_name(func) == "func" + + class MyClass: + def __call__(self, state): + return + + def class_method(self, state): + return + + # callable class + assert _get_node_name(MyClass()) == "MyClass" + + # class method + assert _get_node_name(MyClass().class_method) == "class_method" diff --git a/libs/scheduler-kafka/Makefile b/libs/scheduler-kafka/Makefile index 5d899a3ea..8d62c9df2 100644 --- a/libs/scheduler-kafka/Makefile +++ b/libs/scheduler-kafka/Makefile @@ -19,7 +19,7 @@ test: exit $$EXIT_CODE test_watch: - make start-services && poetry run ptw . -- $(TEST_PATH); \ + make start-services && poetry run ptw . -- -x $(TEST_PATH); \ EXIT_CODE=$$?; \ make stop-services; \ exit $$EXIT_CODE diff --git a/libs/scheduler-kafka/langgraph/scheduler/kafka/executor.py b/libs/scheduler-kafka/langgraph/scheduler/kafka/executor.py index a7c99900d..970d55be8 100644 --- a/libs/scheduler-kafka/langgraph/scheduler/kafka/executor.py +++ b/libs/scheduler-kafka/langgraph/scheduler/kafka/executor.py @@ -8,6 +8,7 @@ from contextlib import ( ) from functools import partial from typing import Any, Optional, Sequence +from uuid import UUID import orjson from langchain_core.runnables import RunnableConfig @@ -37,7 +38,7 @@ from langgraph.scheduler.kafka.types import ( Sendable, Topics, ) -from langgraph.types import LoopProtocol, RetryPolicy +from langgraph.types import LoopProtocol, PregelExecutableTask, RetryPolicy from langgraph.utils.config import patch_configurable @@ -191,12 +192,13 @@ class AsyncKafkaExecutor(AbstractAsyncContextManager): step=saved.metadata["step"] + 1, stop=saved.metadata["step"] + 2, ), - ) as (channels, managed), AsyncBackgroundExecutor() as submit: + ) as (channels, managed), AsyncBackgroundExecutor(msg["config"]) as submit: if task := await asyncio.to_thread( prepare_single_task, msg["task"]["path"], msg["task"]["id"], checkpoint=saved.checkpoint, + pending_writes=saved.pending_writes or [], processes=graph.nodes, channels=channels, managed=managed, @@ -210,13 +212,14 @@ class AsyncKafkaExecutor(AbstractAsyncContextManager): runner = PregelRunner( submit=submit, put_writes=partial(self._put_writes, submit, msg["config"]), + schedule_task=self._schedule_task, ) async for _ in runner.atick([task], reraise=False): pass else: # task was not found await self.graph.checkpointer.aput_writes( - msg["config"], [(ERROR, TaskNotFound())] + msg["config"], [(ERROR, TaskNotFound())], str(UUID(int=0)) ) # notify orchestrator fut = await self.producer.send( @@ -238,6 +241,14 @@ class AsyncKafkaExecutor(AbstractAsyncContextManager): ) await fut + def _schedule_task( + self, + task: PregelExecutableTask, + idx: int, + ) -> None: + # will be scheduled by orchestrator when executor finishes + pass + def _put_writes( self, submit: Submit, @@ -399,6 +410,7 @@ class KafkaExecutor(AbstractContextManager): msg["task"]["path"], msg["task"]["id"], checkpoint=saved.checkpoint, + pending_writes=saved.pending_writes or [], processes=graph.nodes, channels=channels, managed=managed, @@ -411,13 +423,14 @@ class KafkaExecutor(AbstractContextManager): runner = PregelRunner( submit=submit, put_writes=partial(self._put_writes, submit, msg["config"]), + schedule_task=self._schedule_task, ) for _ in runner.tick([task], reraise=False): pass else: # task was not found self.graph.checkpointer.put_writes( - msg["config"], [(ERROR, TaskNotFound())] + msg["config"], [(ERROR, TaskNotFound())], str(UUID(int=0)) ) # notify orchestrator fut = self.producer.send( @@ -439,6 +452,14 @@ class KafkaExecutor(AbstractContextManager): ) fut.result() + def _schedule_task( + self, + task: PregelExecutableTask, + idx: int, + ) -> None: + # will be scheduled by orchestrator when executor finishes + pass + def _put_writes( self, submit: Submit, diff --git a/libs/scheduler-kafka/langgraph/scheduler/kafka/orchestrator.py b/libs/scheduler-kafka/langgraph/scheduler/kafka/orchestrator.py index 493b02d42..4e5be8470 100644 --- a/libs/scheduler-kafka/langgraph/scheduler/kafka/orchestrator.py +++ b/libs/scheduler-kafka/langgraph/scheduler/kafka/orchestrator.py @@ -161,18 +161,18 @@ class AsyncKafkaOrchestrator(AbstractAsyncContextManager): specs=graph.channels, output_keys=graph.output_channels, stream_keys=graph.stream_channels, + interrupt_after=graph.interrupt_after_nodes, + interrupt_before=graph.interrupt_before_nodes, check_subgraphs=False, ) as loop: - if loop.tick( - input_keys=graph.input_channels, - interrupt_after=graph.interrupt_after_nodes, - interrupt_before=graph.interrupt_before_nodes, - ): + if loop.tick(input_keys=graph.input_channels): # wait for checkpoint to be saved if hasattr(loop, "_put_checkpoint_fut"): await loop._put_checkpoint_fut # schedule any new tasks - if new_tasks := [t for t in loop.tasks.values() if not t.scheduled]: + if new_tasks := [ + t for t in loop.tasks.values() if not t.scheduled and not t.writes + ]: # send messages to executor futures = await asyncio.gather( *( @@ -351,18 +351,18 @@ class KafkaOrchestrator(AbstractContextManager): specs=graph.channels, output_keys=graph.output_channels, stream_keys=graph.stream_channels, + interrupt_after=graph.interrupt_after_nodes, + interrupt_before=graph.interrupt_before_nodes, check_subgraphs=False, ) as loop: - if loop.tick( - input_keys=graph.input_channels, - interrupt_after=graph.interrupt_after_nodes, - interrupt_before=graph.interrupt_before_nodes, - ): + if loop.tick(input_keys=graph.input_channels): # wait for checkpoint to be saved if hasattr(loop, "_put_checkpoint_fut"): loop._put_checkpoint_fut.result() # schedule any new tasks - if new_tasks := [t for t in loop.tasks.values() if not t.scheduled]: + if new_tasks := [ + t for t in loop.tasks.values() if not t.scheduled and not t.writes + ]: # send messages to executor futures = [ self.producer.send( diff --git a/libs/scheduler-kafka/langgraph/scheduler/kafka/py.typed b/libs/scheduler-kafka/langgraph/scheduler/kafka/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/libs/scheduler-kafka/langgraph/scheduler/kafka/types.py b/libs/scheduler-kafka/langgraph/scheduler/kafka/types.py index 8230960b4..8a109631b 100644 --- a/libs/scheduler-kafka/langgraph/scheduler/kafka/types.py +++ b/libs/scheduler-kafka/langgraph/scheduler/kafka/types.py @@ -24,8 +24,8 @@ class MessageToOrchestrator(TypedDict): class ExecutorTask(TypedDict): - id: str - path: tuple[str, ...] + id: Optional[str] + path: tuple[Union[str, int], ...] class MessageToExecutor(TypedDict): diff --git a/libs/scheduler-kafka/tests/test_push.py b/libs/scheduler-kafka/tests/test_push.py new file mode 100644 index 000000000..15e9211a2 --- /dev/null +++ b/libs/scheduler-kafka/tests/test_push.py @@ -0,0 +1,208 @@ +import operator +from typing import ( + Annotated, + Literal, + Union, +) + +import pytest +from aiokafka import AIOKafkaProducer + +from langgraph.checkpoint.base import BaseCheckpointSaver +from langgraph.constants import FF_SEND_V2, START +from langgraph.errors import NodeInterrupt +from langgraph.graph.state import CompiledStateGraph, GraphCommand, StateGraph +from langgraph.scheduler.kafka import serde +from langgraph.scheduler.kafka.types import MessageToOrchestrator, Topics +from langgraph.types import Send +from tests.any import AnyDict +from tests.drain import drain_topics_async + +pytestmark = pytest.mark.anyio + + +def mk_push_graph( + checkpointer: BaseCheckpointSaver, +) -> CompiledStateGraph: + # copied from test_send_dedupe_on_resume + + class InterruptOnce: + ticks: int = 0 + + def __call__(self, state): + self.ticks += 1 + if self.ticks == 1: + raise NodeInterrupt("Bahh") + return ["|".join(("flaky", str(state)))] + + class Node: + def __init__(self, name: str): + self.name = name + self.ticks = 0 + self.__name__ = name + + def __call__(self, state): + self.ticks += 1 + update = ( + [self.name] + if isinstance(state, list) + else ["|".join((self.name, str(state)))] + ) + if isinstance(state, GraphCommand): + return state.copy(update=update) + else: + return update + + def send_for_fun(state): + return [ + Send("2", GraphCommand(send=Send("2", 3))), + Send("2", GraphCommand(send=Send("flaky", 4))), + "3.1", + ] + + def route_to_three(state) -> Literal["3"]: + return "3" + + builder = StateGraph(Annotated[list, operator.add]) + builder.add_node(Node("1")) + builder.add_node(Node("2")) + builder.add_node(Node("3")) + builder.add_node(Node("3.1")) + builder.add_node("flaky", InterruptOnce()) + builder.add_edge(START, "1") + builder.add_conditional_edges("1", send_for_fun) + builder.add_conditional_edges("2", route_to_three) + + return builder.compile(checkpointer=checkpointer) + + +async def test_push_graph(topics: Topics, acheckpointer: BaseCheckpointSaver) -> None: + if not FF_SEND_V2: + pytest.skip("Test requires FF_SEND_V2") + + input = ["0"] + config = {"configurable": {"thread_id": "1"}} + graph = mk_push_graph(acheckpointer) + graph_compare = mk_push_graph(acheckpointer) + + # start a new run + async with AIOKafkaProducer(value_serializer=serde.dumps) as producer: + await producer.send_and_wait( + topics.orchestrator, + MessageToOrchestrator(input=input, config=config), + ) + + # drain topics + orch_msgs, exec_msgs = await drain_topics_async(topics, graph) + + # check state + state = await graph.aget_state(config) + assert all(not t.error for t in state.tasks) + assert state.next == ("flaky",) + assert ( + state.values + == await graph_compare.ainvoke(input, {"configurable": {"thread_id": "2"}}) + == [ + "0", + "1", + "2|Control(send=Send(node='2', arg=3))", + "2|Control(send=Send(node='flaky', arg=4))", + "2|3", + ] + ) + + # check history + history = [c async for c in graph.aget_state_history(config)] + assert len(history) == 2 + + # check messages + assert orch_msgs == [MessageToOrchestrator(input=input, config=config)] + [ + { + "config": { + "callbacks": None, + "configurable": { + "__pregel_ensure_latest": True, + "__pregel_dedupe_tasks": True, + "__pregel_resuming": False, + "checkpoint_id": c.config["configurable"]["checkpoint_id"], + "checkpoint_ns": "", + "thread_id": "1", + }, + "metadata": AnyDict(), + "recursion_limit": 25, + "tags": [], + }, + "input": None, + "finally_send": None, + } + for c in reversed(history) + for _ in c.tasks + ] + assert exec_msgs == [ + { + "config": { + "callbacks": None, + "configurable": { + "__pregel_ensure_latest": True, + "__pregel_dedupe_tasks": True, + "__pregel_resuming": False, + "checkpoint_id": c.config["configurable"]["checkpoint_id"], + "checkpoint_ns": "", + "thread_id": "1", + }, + "metadata": AnyDict(), + "recursion_limit": 25, + "tags": [], + }, + "task": { + "id": t.id, + "path": _convert_path(t.path), + }, + "finally_send": None, + } + for c in reversed(history) + for t in c.tasks + ] + + # resume the thread + async with AIOKafkaProducer(value_serializer=serde.dumps) as producer: + await producer.send_and_wait( + topics.orchestrator, + MessageToOrchestrator(input=None, config=config), + ) + + orch_msgs, exec_msgs = await drain_topics_async(topics, graph) + + # check final state + state = await graph.aget_state(config) + assert state.next == () + assert ( + state.values + == await graph_compare.ainvoke(None, {"configurable": {"thread_id": "2"}}) + == [ + "0", + "1", + "2|Control(send=Send(node='2', arg=3))", + "2|Control(send=Send(node='flaky', arg=4))", + "2|3", + "flaky|4", + "3", + "3.1", + ] + ) + + # check history + history = [c async for c in graph.aget_state_history(config)] + assert len(history) == 4 + + # check executions + # node "2" doesn't get called again, as we recover writes saved before + assert graph.builder.nodes["2"].runnable.func.ticks == 3 + # node "flaky" gets called again, as it was interrupted + assert graph.builder.nodes["flaky"].runnable.func.ticks == 2 + + +def _convert_path( + path: tuple[Union[str, int, tuple], ...], +) -> list[Union[str, int, list]]: + return list(_convert_path(p) if isinstance(p, tuple) else p for p in path) diff --git a/libs/scheduler-kafka/tests/test_push_sync.py b/libs/scheduler-kafka/tests/test_push_sync.py new file mode 100644 index 000000000..27cd96cb7 --- /dev/null +++ b/libs/scheduler-kafka/tests/test_push_sync.py @@ -0,0 +1,210 @@ +import operator +from typing import ( + Annotated, + Literal, + Union, +) + +import pytest + +from langgraph.checkpoint.base import BaseCheckpointSaver +from langgraph.constants import FF_SEND_V2, START +from langgraph.errors import NodeInterrupt +from langgraph.graph.state import CompiledStateGraph, GraphCommand, StateGraph +from langgraph.scheduler.kafka import serde +from langgraph.scheduler.kafka.default_sync import DefaultProducer +from langgraph.scheduler.kafka.types import MessageToOrchestrator, Topics +from langgraph.types import Send +from tests.any import AnyDict +from tests.drain import drain_topics + +pytestmark = pytest.mark.anyio + + +def mk_push_graph( + checkpointer: BaseCheckpointSaver, +) -> CompiledStateGraph: + # copied from test_send_dedupe_on_resume + + class InterruptOnce: + ticks: int = 0 + + def __call__(self, state): + self.ticks += 1 + if self.ticks == 1: + raise NodeInterrupt("Bahh") + return ["|".join(("flaky", str(state)))] + + class Node: + def __init__(self, name: str): + self.name = name + self.ticks = 0 + self.__name__ = name + + def __call__(self, state): + self.ticks += 1 + update = ( + [self.name] + if isinstance(state, list) + else ["|".join((self.name, str(state)))] + ) + if isinstance(state, GraphCommand): + return state.copy(update=update) + else: + return update + + def send_for_fun(state): + return [ + Send("2", GraphCommand(send=Send("2", 3))), + Send("2", GraphCommand(send=Send("flaky", 4))), + "3.1", + ] + + def route_to_three(state) -> Literal["3"]: + return "3" + + builder = StateGraph(Annotated[list, operator.add]) + builder.add_node(Node("1")) + builder.add_node(Node("2")) + builder.add_node(Node("3")) + builder.add_node(Node("3.1")) + builder.add_node("flaky", InterruptOnce()) + builder.add_edge(START, "1") + builder.add_conditional_edges("1", send_for_fun) + builder.add_conditional_edges("2", route_to_three) + + return builder.compile(checkpointer=checkpointer) + + +def test_push_graph(topics: Topics, acheckpointer: BaseCheckpointSaver) -> None: + if not FF_SEND_V2: + pytest.skip("Test requires FF_SEND_V2") + + input = ["0"] + config = {"configurable": {"thread_id": "1"}} + graph = mk_push_graph(acheckpointer) + graph_compare = mk_push_graph(acheckpointer) + + # start a new run + with DefaultProducer() as producer: + producer.send( + topics.orchestrator, + value=serde.dumps(MessageToOrchestrator(input=input, config=config)), + ) + producer.flush() + + # drain topics + orch_msgs, exec_msgs = drain_topics(topics, graph) + + # check state + state = graph.get_state(config) + assert all(not t.error for t in state.tasks) + assert state.next == ("flaky",) + assert ( + state.values + == graph_compare.invoke(input, {"configurable": {"thread_id": "2"}}) + == [ + "0", + "1", + "2|Control(send=Send(node='2', arg=3))", + "2|Control(send=Send(node='flaky', arg=4))", + "2|3", + ] + ) + + # check history + history = [c for c in graph.get_state_history(config)] + assert len(history) == 2 + + # check messages + assert orch_msgs == [MessageToOrchestrator(input=input, config=config)] + [ + { + "config": { + "callbacks": None, + "configurable": { + "__pregel_ensure_latest": True, + "__pregel_dedupe_tasks": True, + "__pregel_resuming": False, + "checkpoint_id": c.config["configurable"]["checkpoint_id"], + "checkpoint_ns": "", + "thread_id": "1", + }, + "metadata": AnyDict(), + "recursion_limit": 25, + "tags": [], + }, + "input": None, + "finally_send": None, + } + for c in reversed(history) + for _ in c.tasks + ] + assert exec_msgs == [ + { + "config": { + "callbacks": None, + "configurable": { + "__pregel_ensure_latest": True, + "__pregel_dedupe_tasks": True, + "__pregel_resuming": False, + "checkpoint_id": c.config["configurable"]["checkpoint_id"], + "checkpoint_ns": "", + "thread_id": "1", + }, + "metadata": AnyDict(), + "recursion_limit": 25, + "tags": [], + }, + "task": { + "id": t.id, + "path": _convert_path(t.path), + }, + "finally_send": None, + } + for c in reversed(history) + for t in c.tasks + ] + + # resume the thread + with DefaultProducer() as producer: + producer.send( + topics.orchestrator, + value=serde.dumps(MessageToOrchestrator(input=None, config=config)), + ) + producer.flush() + + orch_msgs, exec_msgs = drain_topics(topics, graph) + + # check final state + state = graph.get_state(config) + assert state.next == () + assert ( + state.values + == graph_compare.invoke(None, {"configurable": {"thread_id": "2"}}) + == [ + "0", + "1", + "2|Control(send=Send(node='2', arg=3))", + "2|Control(send=Send(node='flaky', arg=4))", + "2|3", + "flaky|4", + "3", + "3.1", + ] + ) + + # check history + history = [c for c in graph.get_state_history(config)] + assert len(history) == 4 + + # check executions + # node "2" doesn't get called again, as we recover writes saved before + assert graph.builder.nodes["2"].runnable.func.ticks == 3 + # node "flaky" gets called again, as it was interrupted + assert graph.builder.nodes["flaky"].runnable.func.ticks == 2 + + +def _convert_path( + path: tuple[Union[str, int, tuple], ...], +) -> list[Union[str, int, list]]: + return list(_convert_path(p) if isinstance(p, tuple) else p for p in path) diff --git a/libs/scheduler-kafka/tests/test_subgraph.py b/libs/scheduler-kafka/tests/test_subgraph.py index fd2530843..ebaaea580 100644 --- a/libs/scheduler-kafka/tests/test_subgraph.py +++ b/libs/scheduler-kafka/tests/test_subgraph.py @@ -194,8 +194,9 @@ async def test_subgraph_w_interrupt( "__pregel_ensure_latest": True, "__pregel_dedupe_tasks": True, "__pregel_resuming": False, - '__pregel_store': None, + "__pregel_store": None, "__pregel_task_id": history[0].tasks[0].id, + "__pregel_resume_value": None, "checkpoint_id": None, "checkpoint_map": { "": history[0].config["configurable"]["checkpoint_id"] @@ -258,8 +259,9 @@ async def test_subgraph_w_interrupt( "__pregel_ensure_latest": True, "__pregel_dedupe_tasks": True, "__pregel_resuming": False, - '__pregel_store': None, + "__pregel_store": None, "__pregel_task_id": history[0].tasks[0].id, + "__pregel_resume_value": None, "checkpoint_id": c.config["configurable"]["checkpoint_id"], "checkpoint_map": { "": history[0].config["configurable"]["checkpoint_id"] @@ -352,8 +354,9 @@ async def test_subgraph_w_interrupt( "__pregel_ensure_latest": True, "__pregel_dedupe_tasks": True, "__pregel_resuming": False, - '__pregel_store': None, + "__pregel_store": None, "__pregel_task_id": history[0].tasks[0].id, + "__pregel_resume_value": None, "checkpoint_id": c.config["configurable"]["checkpoint_id"], "checkpoint_map": { "": history[0].config["configurable"]["checkpoint_id"] @@ -456,8 +459,9 @@ async def test_subgraph_w_interrupt( "__pregel_ensure_latest": True, "__pregel_dedupe_tasks": True, "__pregel_resuming": True, - '__pregel_store': None, + "__pregel_store": None, "__pregel_task_id": history[1].tasks[0].id, + "__pregel_resume_value": None, "checkpoint_id": None, "checkpoint_map": { "": history[1].config["configurable"]["checkpoint_id"] @@ -515,8 +519,9 @@ async def test_subgraph_w_interrupt( "__pregel_ensure_latest": True, "__pregel_dedupe_tasks": True, "__pregel_resuming": True, - '__pregel_store': None, + "__pregel_store": None, "__pregel_task_id": history[1].tasks[0].id, + "__pregel_resume_value": None, "checkpoint_id": c.config["configurable"]["checkpoint_id"], "checkpoint_map": { "": history[1].config["configurable"]["checkpoint_id"] @@ -630,8 +635,9 @@ async def test_subgraph_w_interrupt( "__pregel_ensure_latest": True, "__pregel_dedupe_tasks": True, "__pregel_resuming": True, - '__pregel_store': None, + "__pregel_store": None, "__pregel_task_id": history[1].tasks[0].id, + "__pregel_resume_value": None, "checkpoint_id": c.config["configurable"]["checkpoint_id"], "checkpoint_map": { "": history[1].config["configurable"]["checkpoint_id"] diff --git a/libs/scheduler-kafka/tests/test_subgraph_sync.py b/libs/scheduler-kafka/tests/test_subgraph_sync.py index 32d0ceea0..75b9d6e73 100644 --- a/libs/scheduler-kafka/tests/test_subgraph_sync.py +++ b/libs/scheduler-kafka/tests/test_subgraph_sync.py @@ -193,8 +193,9 @@ def test_subgraph_w_interrupt( "__pregel_ensure_latest": True, "__pregel_dedupe_tasks": True, "__pregel_resuming": False, - '__pregel_store': None, + "__pregel_store": None, "__pregel_task_id": history[0].tasks[0].id, + "__pregel_resume_value": None, "checkpoint_id": None, "checkpoint_map": { "": history[0].config["configurable"]["checkpoint_id"] @@ -255,10 +256,11 @@ def test_subgraph_w_interrupt( "__pregel_read": None, "__pregel_send": None, "__pregel_ensure_latest": True, - '__pregel_store': None, + "__pregel_store": None, "__pregel_dedupe_tasks": True, "__pregel_resuming": False, "__pregel_task_id": history[0].tasks[0].id, + "__pregel_resume_value": None, "checkpoint_id": c.config["configurable"]["checkpoint_id"], "checkpoint_map": { "": history[0].config["configurable"]["checkpoint_id"] @@ -350,9 +352,10 @@ def test_subgraph_w_interrupt( "__pregel_send": None, "__pregel_ensure_latest": True, "__pregel_dedupe_tasks": True, - '__pregel_store': None, + "__pregel_store": None, "__pregel_resuming": False, "__pregel_task_id": history[0].tasks[0].id, + "__pregel_resume_value": None, "checkpoint_id": c.config["configurable"]["checkpoint_id"], "checkpoint_map": { "": history[0].config["configurable"]["checkpoint_id"] @@ -453,9 +456,10 @@ def test_subgraph_w_interrupt( "__pregel_send": None, "__pregel_ensure_latest": True, "__pregel_dedupe_tasks": True, - '__pregel_store': None, + "__pregel_store": None, "__pregel_resuming": True, "__pregel_task_id": history[1].tasks[0].id, + "__pregel_resume_value": None, "checkpoint_id": None, "checkpoint_map": { "": history[1].config["configurable"]["checkpoint_id"] @@ -512,9 +516,10 @@ def test_subgraph_w_interrupt( "__pregel_send": None, "__pregel_ensure_latest": True, "__pregel_dedupe_tasks": True, - '__pregel_store': None, + "__pregel_store": None, "__pregel_resuming": True, "__pregel_task_id": history[1].tasks[0].id, + "__pregel_resume_value": None, "checkpoint_id": c.config["configurable"]["checkpoint_id"], "checkpoint_map": { "": history[1].config["configurable"]["checkpoint_id"] @@ -628,8 +633,9 @@ def test_subgraph_w_interrupt( "__pregel_ensure_latest": True, "__pregel_dedupe_tasks": True, "__pregel_resuming": True, - '__pregel_store': None, + "__pregel_store": None, "__pregel_task_id": history[1].tasks[0].id, + "__pregel_resume_value": None, "checkpoint_id": c.config["configurable"]["checkpoint_id"], "checkpoint_map": { "": history[1].config["configurable"]["checkpoint_id"] diff --git a/libs/sdk-js/.gitignore b/libs/sdk-js/.gitignore index af019c1c6..fd0d01027 100644 --- a/libs/sdk-js/.gitignore +++ b/libs/sdk-js/.gitignore @@ -1,13 +1,11 @@ -/docs -/*.tgz -/*.tar -## GENERATED create-entrypoints.js -/client.cjs -/client.js -/client.d.ts -/client.d.cts -/index.cjs -/index.js -/index.d.ts -/index.d.cts -## END GENERATED create-entrypoints.js +index.cjs +index.js +index.d.ts +index.d.cts +client.cjs +client.js +client.d.ts +client.d.cts +node_modules +dist +.yarn diff --git a/libs/sdk-js/langchain.config.js b/libs/sdk-js/langchain.config.js new file mode 100644 index 000000000..eeb79ab47 --- /dev/null +++ b/libs/sdk-js/langchain.config.js @@ -0,0 +1,19 @@ +import { resolve, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +/** + * @param {string} relativePath + * @returns {string} + */ +function abs(relativePath) { + return resolve(dirname(fileURLToPath(import.meta.url)), relativePath); +} + +export const config = { + internals: [], + entrypoints: { index: "index", client: "client" }, + tsConfigPath: resolve("./tsconfig.json"), + cjsSource: "./dist-cjs", + cjsDestination: "./dist", + abs, +}; diff --git a/libs/sdk-js/package.json b/libs/sdk-js/package.json index f65874f46..bc1744108 100644 --- a/libs/sdk-js/package.json +++ b/libs/sdk-js/package.json @@ -1,14 +1,12 @@ { "name": "@langchain/langgraph-sdk", - "version": "0.0.16", + "version": "0.0.25", "description": "Client library for interacting with the LangGraph API", "type": "module", "packageManager": "yarn@1.22.19", "scripts": { - "clean": "rm -rf dist/ && node scripts/create-entrypoints.js clean", - "build": "yarn clean && yarn build:esm && yarn build:cjs && node scripts/create-entrypoints.js", - "build:esm": "rm -f src/package.json && tsc --outDir dist/ && rm -rf dist/tests dist/**/tests", - "build:cjs": "echo '{}' > src/package.json && tsc --outDir dist-cjs/ -p tsconfig.cjs.json && node scripts/move-cjs-to-dist.js && rm -r dist-cjs src/package.json", + "clean": "rm -rf dist/ dist-cjs/", + "build": "yarn clean && yarn lc_build --create-entrypoints --pre --tree-shaking", "prepublish": "yarn run build", "format": "prettier --write src", "lint": "prettier --check src && tsc --noEmit" @@ -22,6 +20,7 @@ "uuid": "^9.0.0" }, "devDependencies": { + "@langchain/scripts": "^0.1.4", "@tsconfig/recommended": "^1.0.2", "@types/node": "^20.12.12", "@types/uuid": "^9.0.1", @@ -54,13 +53,13 @@ }, "files": [ "dist/", - "client.cjs", - "client.js", - "client.d.ts", - "client.d.cts", "index.cjs", "index.js", "index.d.ts", - "index.d.cts" + "index.d.cts", + "client.cjs", + "client.js", + "client.d.ts", + "client.d.cts" ] } diff --git a/libs/sdk-js/scripts/create-entrypoints.js b/libs/sdk-js/scripts/create-entrypoints.js deleted file mode 100644 index bde694f58..000000000 --- a/libs/sdk-js/scripts/create-entrypoints.js +++ /dev/null @@ -1,129 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; - -// This lists all the entrypoints for the library. Each key corresponds to an -// importable path, eg. `import { Foo } from "langgraph-sdk/client"`. -// The value is the path to the file in `src/` that exports the entrypoint. -// This is used to generate the `exports` field in package.json. -// Order is not important. -const entrypoints = { client: "client" }; - -const updateJsonFile = (relativePath, updateFunction) => { - const contents = fs.readFileSync(relativePath).toString(); - const res = updateFunction(JSON.parse(contents)); - fs.writeFileSync(relativePath, JSON.stringify(res, null, 2) + "\n"); -}; - -const generateFiles = () => { - const files = [...Object.entries(entrypoints), ["index", "index"]].flatMap( - ([key, value]) => { - const nrOfDots = key.split("/").length - 1; - const relativePath = "../".repeat(nrOfDots) || "./"; - const compiledPath = `${relativePath}dist/${value}`; - return [ - [`${key}.cjs`, `module.exports = require('${compiledPath}.cjs');`], - [`${key}.js`, `export * from '${compiledPath}.js'`], - [`${key}.d.ts`, `export * from '${compiledPath}.js'`], - [`${key}.d.cts`, `export * from '${compiledPath}.cjs'`], - ]; - }, - ); - - return Object.fromEntries(files); -}; - -const updateConfig = () => { - // Update tsconfig.json `typedocOptions.entryPoints` field - updateJsonFile("./tsconfig.json", (json) => ({ - ...json, - typedocOptions: { - ...json.typedocOptions, - entryPoints: [...Object.keys(entrypoints)].map((key) => { - const basePath = `src/${entrypoints[key]}`; - if (fs.existsSync(`${basePath}.mts`)) { - return `${basePath}.mts`; - } else if (fs.existsSync(`${basePath}.ts`)) { - return `${basePath}.ts`; - } else { - console.warn( - `Warning: Neither ${basePath}.mts nor ${basePath}.ts found for entrypoint ${key}`, - ); - return `${basePath}.ts`; // Default to .ts if neither exists - } - }), - }, - })); - - const generatedFiles = generateFiles(); - const filenames = Object.keys(generatedFiles); - - // Update package.json `exports` and `files` fields - updateJsonFile("./package.json", (json) => ({ - ...json, - exports: Object.assign( - Object.fromEntries( - ["index", ...Object.keys(entrypoints)].map((key) => { - let entryPoint = { - types: { - import: `./${key}.d.ts`, - require: `./${key}.d.cts`, - default: `./${key}.d.ts`, - }, - import: `./${key}.js`, - require: `./${key}.cjs`, - }; - - return [key === "index" ? "." : `./${key}`, entryPoint]; - }), - ), - { - "./package.json": "./package.json", - }, - ), - files: ["dist/", ...filenames], - })); - - // Write generated files - Object.entries(generatedFiles).forEach(([filename, content]) => { - fs.mkdirSync(path.dirname(filename), { - recursive: true, - }); - fs.writeFileSync(filename, content); - }); - - const gitignore = fs.readFileSync("./.gitignore").toString(); - const lines = gitignore.split("\n"); - const startMarker = "## GENERATED create-entrypoints.js"; - const endMarker = "## END GENERATED create-entrypoints.js"; - const startIdx = lines.findIndex((line) => line.includes(startMarker)); - const endIdx = lines.findIndex((line) => line.includes(endMarker)); - const newLines = lines.slice(0, startIdx + 1); - if (startIdx === -1) { - newLines.push(startMarker); - } - newLines.push(...filenames.map((fname) => `/${fname}`)); - if (endIdx === -1) { - newLines.push(endMarker); - } - newLines.push(...lines.slice(endIdx)); - fs.writeFileSync("./.gitignore", newLines.join("\n")); -}; - -const cleanGenerated = () => { - const filenames = Object.keys(generateFiles()); - filenames.forEach((fname) => { - try { - fs.unlinkSync(fname); - } catch { - // ignore error - } - }); -}; - -const command = process.argv[2]; - -if (command === "clean") { - cleanGenerated(); -} else { - updateConfig(); -} diff --git a/libs/sdk-js/scripts/move-cjs-to-dist.js b/libs/sdk-js/scripts/move-cjs-to-dist.js deleted file mode 100644 index 1e89ccca8..000000000 --- a/libs/sdk-js/scripts/move-cjs-to-dist.js +++ /dev/null @@ -1,38 +0,0 @@ -import { resolve, dirname, parse, format } from "node:path"; -import { readdir, readFile, writeFile } from "node:fs/promises"; -import { fileURLToPath } from "node:url"; - -function abs(relativePath) { - return resolve(dirname(fileURLToPath(import.meta.url)), relativePath); -} - -async function moveAndRename(source, dest) { - for (const file of await readdir(abs(source), { withFileTypes: true })) { - if (file.isDirectory()) { - await moveAndRename(`${source}/${file.name}`, `${dest}/${file.name}`); - } else if (file.isFile()) { - const parsed = parse(file.name); - - // Ignore anything that's not a .js file - if (parsed.ext !== ".js") { - continue; - } - - // Rewrite any require statements to use .cjs - const content = await readFile(abs(`${source}/${file.name}`), "utf8"); - const rewritten = content.replace(/require\("(\..+?).js"\)/g, (_, p1) => { - return `require("${p1}.cjs")`; - }); - - // Rename the file to .cjs - const renamed = format({ name: parsed.name, ext: ".cjs" }); - - await writeFile(abs(`${dest}/${renamed}`), rewritten, "utf8"); - } - } -} - -moveAndRename("../dist-cjs", "../dist").catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/libs/sdk-js/src/client.ts b/libs/sdk-js/src/client.ts index 36f1176eb..4829a831f 100644 --- a/libs/sdk-js/src/client.ts +++ b/libs/sdk-js/src/client.ts @@ -1,6 +1,7 @@ import { Assistant, AssistantGraph, + CancelAction, Config, DefaultValues, GraphSchema, @@ -15,6 +16,7 @@ import { SearchItemsResponse, ListNamespaceResponse, Item, + ThreadStatus, } from "./schema.js"; import { AsyncCaller, AsyncCallerParams } from "./utils/async_caller.js"; import { @@ -30,6 +32,7 @@ import { CronsCreatePayload, OnConflictBehavior, } from "./types.js"; +import { mergeSignals } from "./utils/signals.js"; /** * Get the API key from the environment. @@ -85,7 +88,11 @@ class BaseClient { }); this.timeoutMs = config?.timeoutMs || 12_000; - this.apiUrl = config?.apiUrl || "http://localhost:8123"; + + // default limit being capped by Chrome + // https://github.com/nodejs/undici/issues/1373 + // Regex to remove trailing slash, if present + this.apiUrl = config?.apiUrl?.replace(/\/$/, "") || "http://localhost:8123"; this.defaultHeaders = config?.defaultHeaders || {}; const apiKey = getApiKey(config?.apiKey); if (apiKey) { @@ -98,6 +105,7 @@ class BaseClient { options?: RequestInit & { json?: unknown; params?: Record; + timeoutMs?: number | null; }, ): [url: URL, init: RequestInit] { const mutatedOptions = { @@ -114,6 +122,16 @@ class BaseClient { delete mutatedOptions.json; } + let timeoutSignal: AbortSignal | null = null; + if (typeof options?.timeoutMs !== "undefined") { + if (options.timeoutMs != null) { + timeoutSignal = AbortSignal.timeout(options.timeoutMs); + } + } else { + timeoutSignal = AbortSignal.timeout(this.timeoutMs); + } + + mutatedOptions.signal = mergeSignals(timeoutSignal, mutatedOptions.signal); const targetUrl = new URL(`${this.apiUrl}${path}`); if (mutatedOptions.params) { @@ -138,6 +156,8 @@ class BaseClient { options?: RequestInit & { json?: unknown; params?: Record; + timeoutMs?: number | null; + signal?: AbortSignal; }, ): Promise { const response = await this.asyncCaller.fetch( @@ -173,6 +193,7 @@ export class CronsClient extends BaseClient { interrupt_after: payload?.interruptAfter, webhook: payload?.webhook, multitask_strategy: payload?.multitaskStrategy, + if_not_exists: payload?.ifNotExists, }; return this.fetch(`/threads/${threadId}/runs/crons`, { method: "POST", @@ -200,6 +221,7 @@ export class CronsClient extends BaseClient { interrupt_after: payload?.interruptAfter, webhook: payload?.webhook, multitask_strategy: payload?.multitaskStrategy, + if_not_exists: payload?.ifNotExists, }; return this.fetch(`/runs/crons`, { method: "POST", @@ -526,6 +548,11 @@ export class ThreadsClient extends BaseClient { * Offset to start from. */ offset?: number; + /** + * Thread status to filter on. + * Must be one of 'idle', 'busy', 'interrupted' or 'error'. + */ + status?: ThreadStatus; }): Promise { return this.fetch("/threads/search", { method: "POST", @@ -533,6 +560,7 @@ export class ThreadsClient extends BaseClient { metadata: query?.metadata ?? undefined, limit: query?.limit ?? 10, offset: query?.offset ?? 0, + status: query?.status, }, }); } @@ -696,6 +724,7 @@ export class RunsClient extends BaseClient { }> { const json: Record = { input: payload?.input, + command: payload?.command, config: payload?.config, metadata: payload?.metadata, stream_mode: payload?.streamMode, @@ -711,6 +740,7 @@ export class RunsClient extends BaseClient { on_completion: payload?.onCompletion, on_disconnect: payload?.onDisconnect, after_seconds: payload?.afterSeconds, + if_not_exists: payload?.ifNotExists, }; const endpoint = @@ -719,6 +749,7 @@ export class RunsClient extends BaseClient { ...this.prepareFetchOptions(endpoint, { method: "POST", json, + timeoutMs: null, signal: payload?.signal, }), ); @@ -781,6 +812,7 @@ export class RunsClient extends BaseClient { ): Promise { const json: Record = { input: payload?.input, + command: payload?.command, config: payload?.config, metadata: payload?.metadata, assistant_id: assistantId, @@ -791,6 +823,7 @@ export class RunsClient extends BaseClient { checkpoint_id: payload?.checkpointId, multitask_strategy: payload?.multitaskStrategy, after_seconds: payload?.afterSeconds, + if_not_exists: payload?.ifNotExists, }; return this.fetch(`/threads/${threadId}/runs`, { method: "POST", @@ -849,6 +882,7 @@ export class RunsClient extends BaseClient { ): Promise { const json: Record = { input: payload?.input, + command: payload?.command, config: payload?.config, metadata: payload?.metadata, assistant_id: assistantId, @@ -861,14 +895,31 @@ export class RunsClient extends BaseClient { on_completion: payload?.onCompletion, on_disconnect: payload?.onDisconnect, after_seconds: payload?.afterSeconds, + if_not_exists: payload?.ifNotExists, }; const endpoint = threadId == null ? `/runs/wait` : `/threads/${threadId}/runs/wait`; - return this.fetch(endpoint, { + const response = await this.fetch(endpoint, { method: "POST", json, + timeoutMs: null, signal: payload?.signal, }); + const raiseError = + payload?.raiseError !== undefined ? payload.raiseError : true; + if ( + raiseError && + "__error__" in response && + typeof response.__error__ === "object" && + response.__error__ && + "error" in response.__error__ && + "message" in response.__error__ + ) { + throw new Error( + `${response.__error__?.error}: ${response.__error__?.message}`, + ); + } + return response; } /** @@ -919,17 +970,20 @@ export class RunsClient extends BaseClient { * @param threadId The ID of the thread. * @param runId The ID of the run. * @param wait Whether to block when canceling + * @param action Action to take when cancelling the run. Possible values are `interrupt` or `rollback`. Default is `interrupt`. * @returns */ async cancel( threadId: string, runId: string, wait: boolean = false, + action: CancelAction = "interrupt", ): Promise { return this.fetch(`/threads/${threadId}/runs/${runId}/cancel`, { method: "POST", params: { wait: wait ? "1" : "0", + action: action, }, }); } @@ -941,8 +995,15 @@ export class RunsClient extends BaseClient { * @param runId The ID of the run. * @returns */ - async join(threadId: string, runId: string): Promise { - return this.fetch(`/threads/${threadId}/runs/${runId}/join`); + async join( + threadId: string, + runId: string, + options?: { signal?: AbortSignal }, + ): Promise { + return this.fetch(`/threads/${threadId}/runs/${runId}/join`, { + timeoutMs: null, + signal: options?.signal, + }); } /** @@ -963,6 +1024,7 @@ export class RunsClient extends BaseClient { const response = await this.asyncCaller.fetch( ...this.prepareFetchOptions(`/threads/${threadId}/runs/${runId}/stream`, { method: "GET", + timeoutMs: null, signal, }), ); diff --git a/libs/sdk-js/src/index.ts b/libs/sdk-js/src/index.ts index 3db0e5869..f86406100 100644 --- a/libs/sdk-js/src/index.ts +++ b/libs/sdk-js/src/index.ts @@ -10,9 +10,11 @@ export type { Metadata, Run, Thread, + ThreadTask, ThreadState, + ThreadStatus, Cron, Checkpoint, } from "./schema.js"; -export type { OnConflictBehavior } from "./types.js"; +export type { OnConflictBehavior, Command } from "./types.js"; diff --git a/libs/sdk-js/src/schema.ts b/libs/sdk-js/src/schema.ts index 979c198ef..1b9dae1fe 100644 --- a/libs/sdk-js/src/schema.ts +++ b/libs/sdk-js/src/schema.ts @@ -10,10 +10,12 @@ type RunStatus = | "timeout" | "interrupted"; -type ThreadStatus = "idle" | "busy" | "interrupted"; +export type ThreadStatus = "idle" | "busy" | "interrupted" | "error"; type MultitaskStrategy = "reject" | "interrupt" | "rollback" | "enqueue"; +export type CancelAction = "interrupt" | "rollback"; + export interface Config { /** * Tags for this call and any sub-calls (eg. a Chain calling an LLM). @@ -77,7 +79,17 @@ export interface GraphSchema { export type Subgraphs = Record; -export type Metadata = Optional>; +export type Metadata = Optional<{ + source?: "input" | "loop" | "update" | (string & {}); + + step?: number; + + writes?: Record | null; + + parents?: Record; + + [key: string]: unknown; +}>; export interface AssistantBase { /** The ID of the assistant. */ @@ -108,7 +120,22 @@ export interface Assistant extends AssistantBase { /** The name of the assistant */ name: string; } -export type AssistantGraph = Record>>; + +export interface AssistantGraph { + nodes: Array<{ + id: string | number; + name?: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data?: Record | string; + metadata?: unknown; + }>; + edges: Array<{ + source: string; + target: string; + data?: string; + conditional?: boolean; + }>; +} export interface Thread { /** The ID of the thread. */ @@ -181,8 +208,14 @@ export interface ThreadState { export interface ThreadTask { id: string; name: string; + result?: unknown; error: Optional; - interrupts: Array>; + interrupts: Array<{ + value: unknown; + when: "during"; + resumable: boolean; + ns?: string[]; + }>; checkpoint: Optional; state: Optional; } diff --git a/libs/sdk-js/src/types.ts b/libs/sdk-js/src/types.ts index 044dd229c..0073c5962 100644 --- a/libs/sdk-js/src/types.ts +++ b/libs/sdk-js/src/types.ts @@ -1,6 +1,13 @@ import { Checkpoint, Config, Metadata } from "./schema.js"; -export type StreamMode = "values" | "messages" | "updates" | "events" | "debug"; +export type StreamMode = + | "values" + | "messages" + | "updates" + | "events" + | "debug" + | "custom" + | "messages-tuple"; export type MultitaskStrategy = "reject" | "interrupt" | "rollback" | "enqueue"; export type OnConflictBehavior = "raise" | "do_nothing"; export type OnCompletionBehavior = "complete" | "continue"; @@ -16,6 +23,28 @@ export type StreamEvent = | "messages/complete" | (string & {}); +export interface Send { + node: string; + input: Record | null; +} + +export interface Command { + /** + * An object to update the thread state with. + */ + update?: Record; + + /** + * The value to return from an `interrupt` function call. + */ + resume?: unknown; + + /** + * A single, or array of `Send` commands to trigger nodes. + */ + send?: Send | Send[]; +} + interface RunsInvokePayload { /** * Input to the run. Pass `null` to resume from the current state of the thread. @@ -45,12 +74,12 @@ interface RunsInvokePayload { /** * Interrupt execution before entering these nodes. */ - interruptBefore?: string[]; + interruptBefore?: "*" | string[]; /** * Interrupt execution after leaving these nodes. */ - interruptAfter?: string[]; + interruptAfter?: "*" | string[]; /** * Strategy to handle concurrent runs on the same thread. Only relevant if @@ -95,6 +124,16 @@ interface RunsInvokePayload { * Use to schedule future runs. */ afterSeconds?: number; + + /** + * Behavior if the specified run doesn't exist. Defaults to "reject". + */ + ifNotExists?: "create" | "reject"; + + /** + * One or more commands to invoke the graph with. + */ + command?: Command; } export interface RunsStreamPayload extends RunsInvokePayload { @@ -130,4 +169,9 @@ export interface CronsCreatePayload extends RunsCreatePayload { schedule: string; } -export type RunsWaitPayload = RunsStreamPayload; +export interface RunsWaitPayload extends RunsStreamPayload { + /** + * Raise errors returned by the run. Default is `true`. + */ + raiseError?: boolean; +} diff --git a/libs/sdk-js/src/utils/signals.ts b/libs/sdk-js/src/utils/signals.ts new file mode 100644 index 000000000..915753325 --- /dev/null +++ b/libs/sdk-js/src/utils/signals.ts @@ -0,0 +1,22 @@ +export function mergeSignals(...signals: (AbortSignal | null | undefined)[]) { + const nonZeroSignals = signals.filter( + (signal): signal is AbortSignal => signal != null, + ); + + if (nonZeroSignals.length === 0) return undefined; + if (nonZeroSignals.length === 1) return nonZeroSignals[0]; + + const controller = new AbortController(); + for (const signal of signals) { + if (signal?.aborted) { + controller.abort(signal.reason); + return controller.signal; + } + + signal?.addEventListener("abort", () => controller.abort(signal.reason), { + once: true, + }); + } + + return controller.signal; +} diff --git a/libs/sdk-js/tsconfig.cjs.json b/libs/sdk-js/tsconfig.cjs.json index 7e091b100..6f1705b8b 100644 --- a/libs/sdk-js/tsconfig.cjs.json +++ b/libs/sdk-js/tsconfig.cjs.json @@ -1,6 +1,8 @@ { "extends": "./tsconfig.json", "compilerOptions": { + "module": "CommonJS", + "moduleResolution": "Node", "declaration": false }, "exclude": ["node_modules", "dist", "**/tests"] diff --git a/libs/sdk-js/yarn.lock b/libs/sdk-js/yarn.lock index 2937faf64..9dd70b93a 100644 --- a/libs/sdk-js/yarn.lock +++ b/libs/sdk-js/yarn.lock @@ -25,6 +25,35 @@ js-tokens "^4.0.0" picocolors "^1.0.0" +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@langchain/scripts@^0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@langchain/scripts/-/scripts-0.1.4.tgz#8c5d03d627686f20b9522213c12e2b329e73e381" + integrity sha512-O+mv2aqUIm3XWxYBrwFWMeTI3aHWeFR8OYjGFXKc1MGV/3LLao3PciyQvKUg1SL7FemHJ1ltDx74rKuEv8xxPA== + dependencies: + "@octokit/rest" "^21.0.2" + "@rollup/wasm-node" "^4.19.0" + axios "^1.6.7" + commander "^11.1.0" + glob "^10.3.10" + lodash "^4.17.21" + readline "^1.3.0" + rimraf "^5.0.1" + rollup "^4.5.2" + ts-morph "^21.0.1" + typescript "^5.4.5" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -46,6 +75,203 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@octokit/auth-token@^5.0.0": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-5.1.1.tgz#3bbfe905111332a17f72d80bd0b51a3e2fa2cf07" + integrity sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA== + +"@octokit/core@^6.1.2": + version "6.1.2" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-6.1.2.tgz#20442d0a97c411612da206411e356014d1d1bd17" + integrity sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg== + dependencies: + "@octokit/auth-token" "^5.0.0" + "@octokit/graphql" "^8.0.0" + "@octokit/request" "^9.0.0" + "@octokit/request-error" "^6.0.1" + "@octokit/types" "^13.0.0" + before-after-hook "^3.0.2" + universal-user-agent "^7.0.0" + +"@octokit/endpoint@^10.0.0": + version "10.1.1" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-10.1.1.tgz#1a9694e7aef6aa9d854dc78dd062945945869bcc" + integrity sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q== + dependencies: + "@octokit/types" "^13.0.0" + universal-user-agent "^7.0.2" + +"@octokit/graphql@^8.0.0": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-8.1.1.tgz#3cacab5f2e55d91c733e3bf481d3a3f8a5f639c4" + integrity sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg== + dependencies: + "@octokit/request" "^9.0.0" + "@octokit/types" "^13.0.0" + universal-user-agent "^7.0.0" + +"@octokit/openapi-types@^22.2.0": + version "22.2.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-22.2.0.tgz#75aa7dcd440821d99def6a60b5f014207ae4968e" + integrity sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg== + +"@octokit/plugin-paginate-rest@^11.0.0": + version "11.3.5" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.5.tgz#a1929b3ba3dc7b63bc73bb6d3c7a3faf2a9c7649" + integrity sha512-cgwIRtKrpwhLoBi0CUNuY83DPGRMaWVjqVI/bGKsLJ4PzyWZNaEmhHroI2xlrVXkk6nFv0IsZpOp+ZWSWUS2AQ== + dependencies: + "@octokit/types" "^13.6.0" + +"@octokit/plugin-request-log@^5.3.1": + version "5.3.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz#ccb75d9705de769b2aa82bcd105cc96eb0c00f69" + integrity sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw== + +"@octokit/plugin-rest-endpoint-methods@^13.0.0": + version "13.2.6" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.6.tgz#b9d343dbe88a6cb70cc7fa16faa98f0a29ffe654" + integrity sha512-wMsdyHMjSfKjGINkdGKki06VEkgdEldIGstIEyGX0wbYHGByOwN/KiM+hAAlUwAtPkP3gvXtVQA9L3ITdV2tVw== + dependencies: + "@octokit/types" "^13.6.1" + +"@octokit/request-error@^6.0.1": + version "6.1.5" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-6.1.5.tgz#907099e341c4e6179db623a0328d678024f54653" + integrity sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ== + dependencies: + "@octokit/types" "^13.0.0" + +"@octokit/request@^9.0.0": + version "9.1.3" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-9.1.3.tgz#42b693bc06238f43af3c037ebfd35621c6457838" + integrity sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA== + dependencies: + "@octokit/endpoint" "^10.0.0" + "@octokit/request-error" "^6.0.1" + "@octokit/types" "^13.1.0" + universal-user-agent "^7.0.2" + +"@octokit/rest@^21.0.2": + version "21.0.2" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-21.0.2.tgz#9b767dbc1098daea8310fd8b76bf7a97215d5972" + integrity sha512-+CiLisCoyWmYicH25y1cDfCrv41kRSvTq6pPWtRroRJzhsCZWZyCqGyI8foJT5LmScADSwRAnr/xo+eewL04wQ== + dependencies: + "@octokit/core" "^6.1.2" + "@octokit/plugin-paginate-rest" "^11.0.0" + "@octokit/plugin-request-log" "^5.3.1" + "@octokit/plugin-rest-endpoint-methods" "^13.0.0" + +"@octokit/types@^13.0.0", "@octokit/types@^13.1.0", "@octokit/types@^13.6.0", "@octokit/types@^13.6.1": + version "13.6.1" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.6.1.tgz#432fc6c0aaae54318e5b2d3e15c22ac97fc9b15f" + integrity sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g== + dependencies: + "@octokit/openapi-types" "^22.2.0" + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@rollup/rollup-android-arm-eabi@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.4.tgz#c460b54c50d42f27f8254c435a4f3b3e01910bc8" + integrity sha512-jfUJrFct/hTA0XDM5p/htWKoNNTbDLY0KRwEt6pyOA6k2fmk0WVwl65PdUdJZgzGEHWx+49LilkcSaumQRyNQw== + +"@rollup/rollup-android-arm64@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.4.tgz#96e01f3a04675d8d5973ab8d3fd6bc3be21fa5e1" + integrity sha512-j4nrEO6nHU1nZUuCfRKoCcvh7PIywQPUCBa2UsootTHvTHIoIu2BzueInGJhhvQO/2FTRdNYpf63xsgEqH9IhA== + +"@rollup/rollup-darwin-arm64@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.4.tgz#9b2ec23b17b47cbb2f771b81f86ede3ac6730bce" + integrity sha512-GmU/QgGtBTeraKyldC7cDVVvAJEOr3dFLKneez/n7BvX57UdhOqDsVwzU7UOnYA7AAOt+Xb26lk79PldDHgMIQ== + +"@rollup/rollup-darwin-x64@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.4.tgz#f30e4ee6929e048190cf10e0daa8e8ae035b6e46" + integrity sha512-N6oDBiZCBKlwYcsEPXGDE4g9RoxZLK6vT98M8111cW7VsVJFpNEqvJeIPfsCzbf0XEakPslh72X0gnlMi4Ddgg== + +"@rollup/rollup-freebsd-arm64@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.4.tgz#c54b2373ec5bcf71f08c4519c7ae80a0b6c8e03b" + integrity sha512-py5oNShCCjCyjWXCZNrRGRpjWsF0ic8f4ieBNra5buQz0O/U6mMXCpC1LvrHuhJsNPgRt36tSYMidGzZiJF6mw== + +"@rollup/rollup-freebsd-x64@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.4.tgz#3bc53aa29d5a34c28ba8e00def76aa612368458e" + integrity sha512-L7VVVW9FCnTTp4i7KrmHeDsDvjB4++KOBENYtNYAiYl96jeBThFfhP6HVxL74v4SiZEVDH/1ILscR5U9S4ms4g== + +"@rollup/rollup-linux-arm-gnueabihf@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.4.tgz#c85aedd1710c9e267ee86b6d1ce355ecf7d9e8d9" + integrity sha512-10ICosOwYChROdQoQo589N5idQIisxjaFE/PAnX2i0Zr84mY0k9zul1ArH0rnJ/fpgiqfu13TFZR5A5YJLOYZA== + +"@rollup/rollup-linux-arm-musleabihf@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.4.tgz#e77313408bf13995aecde281aec0cceb08747e42" + integrity sha512-ySAfWs69LYC7QhRDZNKqNhz2UKN8LDfbKSMAEtoEI0jitwfAG2iZwVqGACJT+kfYvvz3/JgsLlcBP+WWoKCLcw== + +"@rollup/rollup-linux-arm64-gnu@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.4.tgz#633f632397b3662108cfaa1abca2a80b85f51102" + integrity sha512-uHYJ0HNOI6pGEeZ/5mgm5arNVTI0nLlmrbdph+pGXpC9tFHFDQmDMOEqkmUObRfosJqpU8RliYoGz06qSdtcjg== + +"@rollup/rollup-linux-arm64-musl@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.4.tgz#63edd72b29c4cced93e16113a68e1be9fef88907" + integrity sha512-38yiWLemQf7aLHDgTg85fh3hW9stJ0Muk7+s6tIkSUOMmi4Xbv5pH/5Bofnsb6spIwD5FJiR+jg71f0CH5OzoA== + +"@rollup/rollup-linux-powerpc64le-gnu@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.4.tgz#a9418a4173df80848c0d47df0426a0bf183c4e75" + integrity sha512-q73XUPnkwt9ZNF2xRS4fvneSuaHw2BXuV5rI4cw0fWYVIWIBeDZX7c7FWhFQPNTnE24172K30I+dViWRVD9TwA== + +"@rollup/rollup-linux-riscv64-gnu@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.4.tgz#bc9c195db036a27e5e3339b02f51526b4ce1e988" + integrity sha512-Aie/TbmQi6UXokJqDZdmTJuZBCU3QBDA8oTKRGtd4ABi/nHgXICulfg1KI6n9/koDsiDbvHAiQO3YAUNa/7BCw== + +"@rollup/rollup-linux-s390x-gnu@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.4.tgz#1651fdf8144ae89326c01da5d52c60be63e71a82" + integrity sha512-P8MPErVO/y8ohWSP9JY7lLQ8+YMHfTI4bAdtCi3pC2hTeqFJco2jYspzOzTUB8hwUWIIu1xwOrJE11nP+0JFAQ== + +"@rollup/rollup-linux-x64-gnu@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.4.tgz#e473de5e4acb95fcf930a35cbb7d3e8080e57a6f" + integrity sha512-K03TljaaoPK5FOyNMZAAEmhlyO49LaE4qCsr0lYHUKyb6QacTNF9pnfPpXnFlFD3TXuFbFbz7tJ51FujUXkXYA== + +"@rollup/rollup-linux-x64-musl@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.4.tgz#0af12dd2578c29af4037f0c834b4321429dd5b01" + integrity sha512-VJYl4xSl/wqG2D5xTYncVWW+26ICV4wubwN9Gs5NrqhJtayikwCXzPL8GDsLnaLU3WwhQ8W02IinYSFJfyo34Q== + +"@rollup/rollup-win32-arm64-msvc@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.4.tgz#e48e78cdd45313b977c1390f4bfde7ab79be8871" + integrity sha512-ku2GvtPwQfCqoPFIJCqZ8o7bJcj+Y54cZSr43hHca6jLwAiCbZdBUOrqE6y29QFajNAzzpIOwsckaTFmN6/8TA== + +"@rollup/rollup-win32-ia32-msvc@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.4.tgz#a3fc8536d243fe161c796acb93eba43c250f311c" + integrity sha512-V3nCe+eTt/W6UYNr/wGvO1fLpHUrnlirlypZfKCT1fG6hWfqhPgQV/K/mRBXBpxc0eKLIF18pIOFVPh0mqHjlg== + +"@rollup/rollup-win32-x64-msvc@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.4.tgz#e2a9d1fd56524103a6cc8a54404d9d3ebc73c454" + integrity sha512-LTw1Dfd0mBIEqUVCxbvTE/LLo+9ZxVC9k99v1v4ahg9Aak6FpqOfNu5kRkeTAn0wphoC4JU7No1/rL+bBCEwhg== + +"@rollup/wasm-node@^4.19.0": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@rollup/wasm-node/-/wasm-node-4.24.4.tgz#11d78f5cc85b04e81468f245c2dc0885d06d663d" + integrity sha512-WKJUdPcM8YAYujafY95+2EapqU3F/nwfBkXh9AfkBvWBwFhsvNJABA86Br6graRH2vRE4FBsiqjFvFWOtEO6wg== + dependencies: + "@types/estree" "1.0.6" + optionalDependencies: + fsevents "~2.3.2" + "@shikijs/core@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-1.9.0.tgz#ff717fef5e0e9882f0848272699fd8f04d6f9a07" @@ -71,11 +297,26 @@ traverse "^0.6.7" unified "^9.2.2" +"@ts-morph/common@~0.22.0": + version "0.22.0" + resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.22.0.tgz#8951d451622a26472fbc3a227d6c3a90e687a683" + integrity sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw== + dependencies: + fast-glob "^3.3.2" + minimatch "^9.0.3" + mkdirp "^3.0.1" + path-browserify "^1.0.1" + "@tsconfig/recommended@^1.0.2": version "1.0.6" resolved "https://registry.yarnpkg.com/@tsconfig/recommended/-/recommended-1.0.6.tgz#217b78f9601215939d566a79d202a760ae185114" integrity sha512-0IKu9GHYF1NGTJiYgfWwqnOQSlnE9V9R7YohHNNf0/fj/SyOZWzdd06JFr0fLpg1Mqw0kGbYg8w5xdkSqLKM9g== +"@types/estree@1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + "@types/json-schema@^7.0.15": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -127,6 +368,16 @@ anchor-markdown-header@^0.6.0: dependencies: emoji-regex "~10.1.0" +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -134,6 +385,18 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -178,6 +441,11 @@ arrify@^1.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + available-typed-arrays@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" @@ -185,6 +453,15 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" +axios@^1.6.7: + version "1.7.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + bail@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" @@ -195,6 +472,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +before-after-hook@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-3.0.2.tgz#d5665a5fa8b62294a5aa0a499f933f4a1016195d" + integrity sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A== + brace-expansion@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" @@ -263,6 +545,11 @@ character-reference-invalid@^1.0.0: resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== +code-block-writer@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-12.0.0.tgz#4dd58946eb4234105aff7f0035977b2afdc2a770" + integrity sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -270,11 +557,35 @@ color-convert@^1.9.0: dependencies: color-name "1.1.3" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906" + integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ== + concat-md@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/concat-md/-/concat-md-0.5.1.tgz#03c72343a5d81306aa5ae1040d6368ffbc444781" @@ -287,6 +598,15 @@ concat-md@^0.5.1: meow "^9.0.0" transform-markdown-links "^2.0.0" +cross-spawn@^7.0.0: + version "7.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.5.tgz#910aac880ff5243da96b728bc6521a5f6c2f2f82" + integrity sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + data-view-buffer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" @@ -352,6 +672,11 @@ define-properties@^1.2.0, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -401,6 +726,21 @@ domutils@^2.8.0: domelementtype "^2.2.0" domhandler "^4.2.0" +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + emoji-regex@~10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.1.0.tgz#d50e383743c0f7a5945c47087295afc112e3cf66" @@ -542,7 +882,7 @@ extend@^3.0.0: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -fast-glob@^3.2.9: +fast-glob@^3.2.9, fast-glob@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -582,6 +922,11 @@ find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -589,6 +934,23 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +foreground-child@^3.1.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" + integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + +form-data@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" + integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + format@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" @@ -601,6 +963,11 @@ front-matter@^4.0.2: dependencies: js-yaml "^3.13.1" +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" @@ -648,6 +1015,18 @@ glob-parent@^5.1.2: dependencies: is-glob "^4.0.1" +glob@^10.3.10, glob@^10.3.7: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + globalthis@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" @@ -844,6 +1223,11 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + is-glob@^4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -931,6 +1315,20 @@ isarray@^2.0.5: resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -978,11 +1376,21 @@ lodash.startcase@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.startcase/-/lodash.startcase-4.4.0.tgz#9436e34ed26093ed7ffae1936144350915d9add8" integrity sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg== +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + longest-streak@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg== +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -1221,11 +1629,30 @@ micromatch@^4.0.4: braces "^3.0.3" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== +minimatch@^9.0.3: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + minimatch@^9.0.4: version "9.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" @@ -1247,6 +1674,16 @@ minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + +mkdirp@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" + integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -1339,6 +1776,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + parse-entities@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" @@ -1361,16 +1803,34 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -1396,6 +1856,11 @@ prettier@^3.2.5: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + punycode.js@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7" @@ -1430,6 +1895,11 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" +readline@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/readline/-/readline-1.3.0.tgz#c580d77ef2cfc8752b132498060dc9793a7ac01c" + integrity sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg== + redent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" @@ -1503,6 +1973,40 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rimraf@^5.0.1: + version "5.0.10" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c" + integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ== + dependencies: + glob "^10.3.7" + +rollup@^4.5.2: + version "4.24.4" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.24.4.tgz#fdc76918de02213c95447c9ffff5e35dddb1d058" + integrity sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA== + dependencies: + "@types/estree" "1.0.6" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.24.4" + "@rollup/rollup-android-arm64" "4.24.4" + "@rollup/rollup-darwin-arm64" "4.24.4" + "@rollup/rollup-darwin-x64" "4.24.4" + "@rollup/rollup-freebsd-arm64" "4.24.4" + "@rollup/rollup-freebsd-x64" "4.24.4" + "@rollup/rollup-linux-arm-gnueabihf" "4.24.4" + "@rollup/rollup-linux-arm-musleabihf" "4.24.4" + "@rollup/rollup-linux-arm64-gnu" "4.24.4" + "@rollup/rollup-linux-arm64-musl" "4.24.4" + "@rollup/rollup-linux-powerpc64le-gnu" "4.24.4" + "@rollup/rollup-linux-riscv64-gnu" "4.24.4" + "@rollup/rollup-linux-s390x-gnu" "4.24.4" + "@rollup/rollup-linux-x64-gnu" "4.24.4" + "@rollup/rollup-linux-x64-musl" "4.24.4" + "@rollup/rollup-win32-arm64-msvc" "4.24.4" + "@rollup/rollup-win32-ia32-msvc" "4.24.4" + "@rollup/rollup-win32-x64-msvc" "4.24.4" + fsevents "~2.3.2" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -1561,6 +2065,18 @@ set-function-name@^2.0.1: functions-have-names "^1.2.3" has-property-descriptors "^1.0.2" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + shiki@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/shiki/-/shiki-1.9.0.tgz#e4d3a044d9c746aefbea47615e83323fdc3dc361" @@ -1578,6 +2094,11 @@ side-channel@^1.0.4: get-intrinsic "^1.2.4" object-inspect "^1.13.1" +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -1614,6 +2135,33 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + string.prototype.trim@^1.2.9: version "1.2.9" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" @@ -1642,6 +2190,27 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + strip-indent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" @@ -1692,6 +2261,14 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== +ts-morph@^21.0.1: + version "21.0.1" + resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-21.0.1.tgz#712302a0f6e9dbf1aa8d9cf33a4386c4b18c2006" + integrity sha512-dbDtVdEAncKctzrVZ+Nr7kHpHkv+0JDJb2MjjpBaj8bFeCkePU9rHfMklmhuLFnpeq/EJZk2IhStY6NzqgjOkg== + dependencies: + "@ts-morph/common" "~0.22.0" + code-block-writer "^12.0.0" + type-fest@^0.18.0: version "0.18.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" @@ -1841,6 +2418,11 @@ unist-util-visit-parents@^3.0.0: "@types/unist" "^2.0.0" unist-util-is "^4.0.0" +universal-user-agent@^7.0.0, universal-user-agent@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-7.0.2.tgz#52e7d0e9b3dc4df06cc33cb2b9fd79041a54827e" + integrity sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q== + update-section@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/update-section/-/update-section-0.3.3.tgz#458f17820d37820dc60e20b86d94391b00123158" @@ -1899,6 +2481,31 @@ which-typed-array@^1.1.14, which-typed-array@^1.1.15: gopd "^1.0.1" has-tostringtag "^1.0.2" +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" diff --git a/libs/sdk-py/langgraph_sdk/client.py b/libs/sdk-py/langgraph_sdk/client.py index f89a0f6a5..6a3bb6c9c 100644 --- a/libs/sdk-py/langgraph_sdk/client.py +++ b/libs/sdk-py/langgraph_sdk/client.py @@ -34,11 +34,14 @@ from langgraph_sdk.schema import ( All, Assistant, AssistantVersion, + CancelAction, Checkpoint, + Command, Config, Cron, DisconnectMode, GraphSchema, + IfNotExists, Item, Json, ListNamespaceResponse, @@ -158,7 +161,7 @@ def get_client( client = httpx.AsyncClient( base_url=url, transport=transport, - timeout=httpx.Timeout(connect=5, read=60, write=60, pool=5), + timeout=httpx.Timeout(connect=5, read=300, write=300, pool=5), headers=get_headers(api_key, headers), ) return LangGraphClient(client) @@ -602,7 +605,7 @@ class AssistantsClient: graph_id: The ID of the graph the assistant should use. The graph ID is normally set in your langgraph.json configuration. If None, assistant will keep pointing to same graph. config: Configuration to use for the graph. - metadata: Metadata to add to assistant. + metadata: Metadata to merge with existing assistant metadata. name: The new name for the assistant. Returns: @@ -705,7 +708,10 @@ class AssistantsClient: """List all versions of an assistant. Args: - assistant_id: The assistant ID to delete. + assistant_id: The assistant ID to get versions for. + metadata: Metadata to filter versions by. Exact match filter for each KV pair. + limit: The maximum number of versions to return. + offset: The number of versions to skip. Returns: list[Assistant]: A list of assistants. @@ -838,7 +844,7 @@ class ThreadsClient: Args: thread_id: ID of thread to update. - metadata: Metadata to add/update to thread. + metadata: Metadata to merge with existing thread metadata. Returns: Thread: The created thread. @@ -884,10 +890,10 @@ class ThreadsClient: """Search for threads. Args: - metadata: Thread metadata to search for. - values: Thread values to search for. - status: Status to search for. - Must be one of 'idle', 'busy', or 'interrupted'. + metadata: Thread metadata to filter on. + values: State values to filter on. + status: Thread status to filter on. + Must be one of 'idle', 'busy', 'interrupted' or 'error'. limit: Limit on number of threads to return. offset: Offset in threads table to start search from. @@ -950,7 +956,7 @@ class ThreadsClient: Args: thread_id: The ID of the thread to get the state of. checkpoint: The checkpoint to get the state of. - subgraphs: Include subgraphs in the state. + subgraphs: Include subgraphs states. Returns: ThreadState: the thread of the state. @@ -1067,7 +1073,7 @@ class ThreadsClient: Args: thread_id: The ID of the thread to update. - values: The values to update to the state. + values: The values to update the state with. as_node: Update the state as if this node had just executed. checkpoint: The checkpoint to update the state of. @@ -1118,11 +1124,11 @@ class ThreadsClient: """Get the state history of a thread. Args: - thread_id: The ID of the thread to get the state of. - checkpoint: Get history for this subgraph. If empty defaults to root. - limit: The maximum number of results to return. - before: Get history before this checkpoint. - metadata: Filter checkpoints by metadata. + thread_id: The ID of the thread to get the state history for. + checkpoint: Return states for this subgraph. If empty defaults to root. + limit: The maximum number of states to return. + before: Return states before this checkpoint. + metadata: Filter states by metadata key-value pairs. Returns: list[ThreadState]: the state history of the thread. @@ -1169,18 +1175,20 @@ class RunsClient: assistant_id: str, *, input: Optional[dict] = None, - stream_mode: Union[StreamMode, list[StreamMode]] = "values", + command: Optional[Command] = None, + stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values", stream_subgraphs: bool = False, metadata: Optional[dict] = None, config: Optional[Config] = None, checkpoint: Optional[Checkpoint] = None, checkpoint_id: Optional[str] = None, - interrupt_before: Optional[Union[All, list[str]]] = None, - interrupt_after: Optional[Union[All, list[str]]] = None, - feedback_keys: Optional[list[str]] = None, + interrupt_before: Optional[Union[All, Sequence[str]]] = None, + interrupt_after: Optional[Union[All, Sequence[str]]] = None, + feedback_keys: Optional[Sequence[str]] = None, on_disconnect: Optional[DisconnectMode] = None, webhook: Optional[str] = None, multitask_strategy: Optional[MultitaskStrategy] = None, + if_not_exists: Optional[IfNotExists] = None, after_seconds: Optional[int] = None, ) -> AsyncIterator[StreamPart]: ... @@ -1191,15 +1199,17 @@ class RunsClient: assistant_id: str, *, input: Optional[dict] = None, - stream_mode: Union[StreamMode, list[StreamMode]] = "values", + command: Optional[Command] = None, + stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values", stream_subgraphs: bool = False, metadata: Optional[dict] = None, config: Optional[Config] = None, - interrupt_before: Optional[Union[All, list[str]]] = None, - interrupt_after: Optional[Union[All, list[str]]] = None, - feedback_keys: Optional[list[str]] = None, + interrupt_before: Optional[Union[All, Sequence[str]]] = None, + interrupt_after: Optional[Union[All, Sequence[str]]] = None, + feedback_keys: Optional[Sequence[str]] = None, on_disconnect: Optional[DisconnectMode] = None, on_completion: Optional[OnCompletionBehavior] = None, + if_not_exists: Optional[IfNotExists] = None, webhook: Optional[str] = None, after_seconds: Optional[int] = None, ) -> AsyncIterator[StreamPart]: ... @@ -1210,19 +1220,21 @@ class RunsClient: assistant_id: str, *, input: Optional[dict] = None, - stream_mode: Union[StreamMode, list[StreamMode]] = "values", + command: Optional[Command] = None, + stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values", stream_subgraphs: bool = False, metadata: Optional[dict] = None, config: Optional[Config] = None, checkpoint: Optional[Checkpoint] = None, checkpoint_id: Optional[str] = None, - interrupt_before: Optional[Union[All, list[str]]] = None, - interrupt_after: Optional[Union[All, list[str]]] = None, - feedback_keys: Optional[list[str]] = None, + interrupt_before: Optional[Union[All, Sequence[str]]] = None, + interrupt_after: Optional[Union[All, Sequence[str]]] = None, + feedback_keys: Optional[Sequence[str]] = None, on_disconnect: Optional[DisconnectMode] = None, on_completion: Optional[OnCompletionBehavior] = None, webhook: Optional[str] = None, multitask_strategy: Optional[MultitaskStrategy] = None, + if_not_exists: Optional[IfNotExists] = None, after_seconds: Optional[int] = None, ) -> AsyncIterator[StreamPart]: """Create a run and stream the results. @@ -1233,6 +1245,7 @@ class RunsClient: assistant_id: The assistant ID or graph name to stream from. If using graph name, will default to first assistant created from that graph. input: The input to the graph. + command: A command to execute. Cannot be combined with input. stream_mode: The stream mode(s) to use. stream_subgraphs: Whether to stream output from subgraphs. metadata: Metadata to assign to the run. @@ -1248,6 +1261,8 @@ class RunsClient: webhook: Webhook to call after LangGraph API call is done. multitask_strategy: Multitask strategy to use. Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'. + if_not_exists: How to handle missing thread. Defaults to 'reject'. + Must be either 'reject' (raise error if missing), or 'create' (create new thread). after_seconds: The number of seconds to wait before starting the run. Use to schedule future runs. @@ -1281,6 +1296,7 @@ class RunsClient: """ # noqa: E501 payload = { "input": input, + "command": command, "config": config, "metadata": metadata, "stream_mode": stream_mode, @@ -1293,6 +1309,7 @@ class RunsClient: "checkpoint": checkpoint, "checkpoint_id": checkpoint_id, "multitask_strategy": multitask_strategy, + "if_not_exists": if_not_exists, "on_disconnect": on_disconnect, "on_completion": on_completion, "after_seconds": after_seconds, @@ -1313,14 +1330,16 @@ class RunsClient: assistant_id: str, *, input: Optional[dict] = None, - stream_mode: Union[StreamMode, list[StreamMode]] = "values", + command: Optional[Command] = None, + stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values", stream_subgraphs: bool = False, metadata: Optional[dict] = None, config: Optional[Config] = None, - interrupt_before: Optional[Union[All, list[str]]] = None, - interrupt_after: Optional[Union[All, list[str]]] = None, + interrupt_before: Optional[Union[All, Sequence[str]]] = None, + interrupt_after: Optional[Union[All, Sequence[str]]] = None, webhook: Optional[str] = None, on_completion: Optional[OnCompletionBehavior] = None, + if_not_exists: Optional[IfNotExists] = None, after_seconds: Optional[int] = None, ) -> Run: ... @@ -1331,16 +1350,18 @@ class RunsClient: assistant_id: str, *, input: Optional[dict] = None, - stream_mode: Union[StreamMode, list[StreamMode]] = "values", + command: Optional[Command] = None, + stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values", stream_subgraphs: bool = False, metadata: Optional[dict] = None, config: Optional[Config] = None, checkpoint: Optional[Checkpoint] = None, checkpoint_id: Optional[str] = None, - interrupt_before: Optional[Union[All, list[str]]] = None, - interrupt_after: Optional[Union[All, list[str]]] = None, + interrupt_before: Optional[Union[All, Sequence[str]]] = None, + interrupt_after: Optional[Union[All, Sequence[str]]] = None, webhook: Optional[str] = None, multitask_strategy: Optional[MultitaskStrategy] = None, + if_not_exists: Optional[IfNotExists] = None, after_seconds: Optional[int] = None, ) -> Run: ... @@ -1350,16 +1371,18 @@ class RunsClient: assistant_id: str, *, input: Optional[dict] = None, - stream_mode: Union[StreamMode, list[StreamMode]] = "values", + command: Optional[Command] = None, + stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values", stream_subgraphs: bool = False, metadata: Optional[dict] = None, config: Optional[Config] = None, checkpoint: Optional[Checkpoint] = None, checkpoint_id: Optional[str] = None, - interrupt_before: Optional[Union[All, list[str]]] = None, - interrupt_after: Optional[Union[All, list[str]]] = None, + interrupt_before: Optional[Union[All, Sequence[str]]] = None, + interrupt_after: Optional[Union[All, Sequence[str]]] = None, webhook: Optional[str] = None, multitask_strategy: Optional[MultitaskStrategy] = None, + if_not_exists: Optional[IfNotExists] = None, on_completion: Optional[OnCompletionBehavior] = None, after_seconds: Optional[int] = None, ) -> Run: @@ -1371,6 +1394,7 @@ class RunsClient: assistant_id: The assistant ID or graph name to stream from. If using graph name, will default to first assistant created from that graph. input: The input to the graph. + command: A command to execute. Cannot be combined with input. stream_mode: The stream mode(s) to use. stream_subgraphs: Whether to stream output from subgraphs. metadata: Metadata to assign to the run. @@ -1383,6 +1407,8 @@ class RunsClient: Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'. on_completion: Whether to delete or keep the thread created for a stateless run. Must be one of 'delete' or 'keep'. + if_not_exists: How to handle missing thread. Defaults to 'reject'. + Must be either 'reject' (raise error if missing), or 'create' (create new thread). after_seconds: The number of seconds to wait before starting the run. Use to schedule future runs. @@ -1455,6 +1481,7 @@ class RunsClient: """ # noqa: E501 payload = { "input": input, + "command": command, "stream_mode": stream_mode, "stream_subgraphs": stream_subgraphs, "config": config, @@ -1466,6 +1493,7 @@ class RunsClient: "checkpoint": checkpoint, "checkpoint_id": checkpoint_id, "multitask_strategy": multitask_strategy, + "if_not_exists": if_not_exists, "on_completion": on_completion, "after_seconds": after_seconds, } @@ -1491,16 +1519,19 @@ class RunsClient: assistant_id: str, *, input: Optional[dict] = None, + command: Optional[Command] = None, metadata: Optional[dict] = None, config: Optional[Config] = None, checkpoint: Optional[Checkpoint] = None, checkpoint_id: Optional[str] = None, - interrupt_before: Optional[Union[All, list[str]]] = None, - interrupt_after: Optional[Union[All, list[str]]] = None, + interrupt_before: Optional[Union[All, Sequence[str]]] = None, + interrupt_after: Optional[Union[All, Sequence[str]]] = None, webhook: Optional[str] = None, on_disconnect: Optional[DisconnectMode] = None, multitask_strategy: Optional[MultitaskStrategy] = None, + if_not_exists: Optional[IfNotExists] = None, after_seconds: Optional[int] = None, + raise_error: bool = True, ) -> Union[list[dict], dict[str, Any]]: ... @overload @@ -1510,14 +1541,17 @@ class RunsClient: assistant_id: str, *, input: Optional[dict] = None, + command: Optional[Command] = None, metadata: Optional[dict] = None, config: Optional[Config] = None, - interrupt_before: Optional[Union[All, list[str]]] = None, - interrupt_after: Optional[Union[All, list[str]]] = None, + interrupt_before: Optional[Union[All, Sequence[str]]] = None, + interrupt_after: Optional[Union[All, Sequence[str]]] = None, webhook: Optional[str] = None, on_disconnect: Optional[DisconnectMode] = None, on_completion: Optional[OnCompletionBehavior] = None, + if_not_exists: Optional[IfNotExists] = None, after_seconds: Optional[int] = None, + raise_error: bool = True, ) -> Union[list[dict], dict[str, Any]]: ... async def wait( @@ -1526,17 +1560,20 @@ class RunsClient: assistant_id: str, *, input: Optional[dict] = None, + command: Optional[Command] = None, metadata: Optional[dict] = None, config: Optional[Config] = None, checkpoint: Optional[Checkpoint] = None, checkpoint_id: Optional[str] = None, - interrupt_before: Optional[Union[All, list[str]]] = None, - interrupt_after: Optional[Union[All, list[str]]] = None, + interrupt_before: Optional[Union[All, Sequence[str]]] = None, + interrupt_after: Optional[Union[All, Sequence[str]]] = None, webhook: Optional[str] = None, on_disconnect: Optional[DisconnectMode] = None, on_completion: Optional[OnCompletionBehavior] = None, multitask_strategy: Optional[MultitaskStrategy] = None, + if_not_exists: Optional[IfNotExists] = None, after_seconds: Optional[int] = None, + raise_error: bool = True, ) -> Union[list[dict], dict[str, Any]]: """Create a run, wait until it finishes and return the final state. @@ -1546,6 +1583,7 @@ class RunsClient: assistant_id: The assistant ID or graph name to run. If using graph name, will default to first assistant created from that graph. input: The input to the graph. + command: A command to execute. Cannot be combined with input. metadata: Metadata to assign to the run. config: The configuration for the assistant. checkpoint: The checkpoint to resume from. @@ -1558,6 +1596,8 @@ class RunsClient: Must be one of 'delete' or 'keep'. multitask_strategy: Multitask strategy to use. Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'. + if_not_exists: How to handle missing thread. Defaults to 'reject'. + Must be either 'reject' (raise error if missing), or 'create' (create new thread). after_seconds: The number of seconds to wait before starting the run. Use to schedule future runs. @@ -1610,6 +1650,7 @@ class RunsClient: """ # noqa: E501 payload = { "input": input, + "command": command, "config": config, "metadata": metadata, "assistant_id": assistant_id, @@ -1619,6 +1660,7 @@ class RunsClient: "checkpoint": checkpoint, "checkpoint_id": checkpoint_id, "multitask_strategy": multitask_strategy, + "if_not_exists": if_not_exists, "on_disconnect": on_disconnect, "on_completion": on_completion, "after_seconds": after_seconds, @@ -1626,9 +1668,19 @@ class RunsClient: endpoint = ( f"/threads/{thread_id}/runs/wait" if thread_id is not None else "/runs/wait" ) - return await self.http.post( + response = await self.http.post( endpoint, json={k: v for k, v in payload.items() if v is not None} ) + if ( + raise_error + and isinstance(response, dict) + and "__error__" in response + and isinstance(response["__error__"], dict) + ): + raise Exception( + f"{response['__error__'].get('error')}: {response['__error__'].get('message')}" + ) + return response async def list( self, thread_id: str, *, limit: int = 10, offset: int = 0 @@ -1677,13 +1729,22 @@ class RunsClient: return await self.http.get(f"/threads/{thread_id}/runs/{run_id}") - async def cancel(self, thread_id: str, run_id: str, *, wait: bool = False) -> None: + async def cancel( + self, + thread_id: str, + run_id: str, + *, + wait: bool = False, + action: CancelAction = "interrupt", + ) -> None: """Get a run. Args: thread_id: The thread ID to cancel. run_id: The run ID to cancek. wait: Whether to wait until run has completed. + action: Action to take when cancelling the run. Possible values + are `interrupt` or `rollback`. Default is `interrupt`. Returns: None @@ -1693,12 +1754,13 @@ class RunsClient: await client.runs.cancel( thread_id="thread_id_to_cancel", run_id="run_id_to_cancel", - wait=True + wait=True, + action="interrupt" ) """ # noqa: E501 return await self.http.post( - f"/threads/{thread_id}/runs/{run_id}/cancel?wait={1 if wait else 0}", + f"/threads/{thread_id}/runs/{run_id}/cancel?wait={1 if wait else 0}&action={action}", json=None, ) @@ -2241,7 +2303,7 @@ def get_sync_client( client = httpx.Client( base_url=url, transport=transport, - timeout=httpx.Timeout(connect=5, read=60, write=60, pool=5), + timeout=httpx.Timeout(connect=5, read=300, write=300, pool=5), headers=get_headers(api_key, headers), ) return SyncLangGraphClient(client) @@ -2668,7 +2730,7 @@ class SyncAssistantsClient: graph_id: The ID of the graph the assistant should use. The graph ID is normally set in your langgraph.json configuration. If None, assistant will keep pointing to same graph. config: Configuration to use for the graph. - metadata: Metadata to add to assistant. + metadata: Metadata to merge with existing assistant metadata. name: The new name for the assistant. Returns: @@ -2771,7 +2833,10 @@ class SyncAssistantsClient: """List all versions of an assistant. Args: - assistant_id: The assistant ID to delete. + assistant_id: The assistant ID to get versions for. + metadata: Metadata to filter versions by. Exact match filter for each KV pair. + limit: The maximum number of versions to return. + offset: The number of versions to skip. Returns: list[Assistant]: A list of assistants. @@ -2901,7 +2966,7 @@ class SyncThreadsClient: Args: thread_id: ID of thread to update. - metadata: Metadata to add/update to thread. + metadata: Metadata to merge with existing thread metadata. Returns: Thread: The created thread. @@ -2945,10 +3010,10 @@ class SyncThreadsClient: """Search for threads. Args: - metadata: Thread metadata to search for. - values: Thread values to search for. - status: Status to search for. - Must be one of 'idle', 'busy', or 'interrupted'. + metadata: Thread metadata to filter on. + values: State values to filter on. + status: Thread status to filter on. + Must be one of 'idle', 'busy', 'interrupted' or 'error'. limit: Limit on number of threads to return. offset: Offset in threads table to start search from. @@ -3011,7 +3076,7 @@ class SyncThreadsClient: Args: thread_id: The ID of the thread to get the state of. checkpoint: The checkpoint to get the state of. - subgraphs: Include subgraphs in the state. + subgraphs: Include subgraphs states. Returns: ThreadState: the thread of the state. @@ -3128,7 +3193,7 @@ class SyncThreadsClient: Args: thread_id: The ID of the thread to update. - values: The values to update to the state. + values: The values to update the state with. as_node: Update the state as if this node had just executed. checkpoint: The checkpoint to update the state of. @@ -3179,11 +3244,11 @@ class SyncThreadsClient: """Get the state history of a thread. Args: - thread_id: The ID of the thread to get the state of. - checkpoint: Get history for this subgraph. If empty defaults to root. - limit: The maximum number of results to return. - before: Get history before this checkpoint. - metadata: Filter checkpoints by metadata. + thread_id: The ID of the thread to get the state history for. + checkpoint: Return states for this subgraph. If empty defaults to root. + limit: The maximum number of states to return. + before: Return states before this checkpoint. + metadata: Filter states by metadata key-value pairs. Returns: list[ThreadState]: the state history of the thread. @@ -3232,18 +3297,19 @@ class SyncRunsClient: assistant_id: str, *, input: Optional[dict] = None, - stream_mode: Union[StreamMode, list[StreamMode]] = "values", + stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values", stream_subgraphs: bool = False, metadata: Optional[dict] = None, config: Optional[Config] = None, checkpoint: Optional[Checkpoint] = None, checkpoint_id: Optional[str] = None, - interrupt_before: Optional[Union[All, list[str]]] = None, - interrupt_after: Optional[Union[All, list[str]]] = None, - feedback_keys: Optional[list[str]] = None, + interrupt_before: Optional[Union[All, Sequence[str]]] = None, + interrupt_after: Optional[Union[All, Sequence[str]]] = None, + feedback_keys: Optional[Sequence[str]] = None, on_disconnect: Optional[DisconnectMode] = None, webhook: Optional[str] = None, multitask_strategy: Optional[MultitaskStrategy] = None, + if_not_exists: Optional[IfNotExists] = None, after_seconds: Optional[int] = None, ) -> Iterator[StreamPart]: ... @@ -3254,15 +3320,16 @@ class SyncRunsClient: assistant_id: str, *, input: Optional[dict] = None, - stream_mode: Union[StreamMode, list[StreamMode]] = "values", + stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values", stream_subgraphs: bool = False, metadata: Optional[dict] = None, config: Optional[Config] = None, - interrupt_before: Optional[Union[All, list[str]]] = None, - interrupt_after: Optional[Union[All, list[str]]] = None, - feedback_keys: Optional[list[str]] = None, + interrupt_before: Optional[Union[All, Sequence[str]]] = None, + interrupt_after: Optional[Union[All, Sequence[str]]] = None, + feedback_keys: Optional[Sequence[str]] = None, on_disconnect: Optional[DisconnectMode] = None, on_completion: Optional[OnCompletionBehavior] = None, + if_not_exists: Optional[IfNotExists] = None, webhook: Optional[str] = None, after_seconds: Optional[int] = None, ) -> Iterator[StreamPart]: ... @@ -3273,19 +3340,20 @@ class SyncRunsClient: assistant_id: str, *, input: Optional[dict] = None, - stream_mode: Union[StreamMode, list[StreamMode]] = "values", + stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values", stream_subgraphs: bool = False, metadata: Optional[dict] = None, config: Optional[Config] = None, checkpoint: Optional[Checkpoint] = None, checkpoint_id: Optional[str] = None, - interrupt_before: Optional[Union[All, list[str]]] = None, - interrupt_after: Optional[Union[All, list[str]]] = None, - feedback_keys: Optional[list[str]] = None, + interrupt_before: Optional[Union[All, Sequence[str]]] = None, + interrupt_after: Optional[Union[All, Sequence[str]]] = None, + feedback_keys: Optional[Sequence[str]] = None, on_disconnect: Optional[DisconnectMode] = None, on_completion: Optional[OnCompletionBehavior] = None, webhook: Optional[str] = None, multitask_strategy: Optional[MultitaskStrategy] = None, + if_not_exists: Optional[IfNotExists] = None, after_seconds: Optional[int] = None, ) -> Iterator[StreamPart]: """Create a run and stream the results. @@ -3311,6 +3379,8 @@ class SyncRunsClient: webhook: Webhook to call after LangGraph API call is done. multitask_strategy: Multitask strategy to use. Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'. + if_not_exists: How to handle missing thread. Defaults to 'reject'. + Must be either 'reject' (raise error if missing), or 'create' (create new thread). after_seconds: The number of seconds to wait before starting the run. Use to schedule future runs. @@ -3356,6 +3426,7 @@ class SyncRunsClient: "checkpoint": checkpoint, "checkpoint_id": checkpoint_id, "multitask_strategy": multitask_strategy, + "if_not_exists": if_not_exists, "on_disconnect": on_disconnect, "on_completion": on_completion, "after_seconds": after_seconds, @@ -3376,14 +3447,15 @@ class SyncRunsClient: assistant_id: str, *, input: Optional[dict] = None, - stream_mode: Union[StreamMode, list[StreamMode]] = "values", + stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values", stream_subgraphs: bool = False, metadata: Optional[dict] = None, config: Optional[Config] = None, - interrupt_before: Optional[Union[All, list[str]]] = None, - interrupt_after: Optional[Union[All, list[str]]] = None, + interrupt_before: Optional[Union[All, Sequence[str]]] = None, + interrupt_after: Optional[Union[All, Sequence[str]]] = None, webhook: Optional[str] = None, on_completion: Optional[OnCompletionBehavior] = None, + if_not_exists: Optional[IfNotExists] = None, after_seconds: Optional[int] = None, ) -> Run: ... @@ -3394,16 +3466,17 @@ class SyncRunsClient: assistant_id: str, *, input: Optional[dict] = None, - stream_mode: Union[StreamMode, list[StreamMode]] = "values", + stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values", stream_subgraphs: bool = False, metadata: Optional[dict] = None, config: Optional[Config] = None, checkpoint: Optional[Checkpoint] = None, checkpoint_id: Optional[str] = None, - interrupt_before: Optional[Union[All, list[str]]] = None, - interrupt_after: Optional[Union[All, list[str]]] = None, + interrupt_before: Optional[Union[All, Sequence[str]]] = None, + interrupt_after: Optional[Union[All, Sequence[str]]] = None, webhook: Optional[str] = None, multitask_strategy: Optional[MultitaskStrategy] = None, + if_not_exists: Optional[IfNotExists] = None, after_seconds: Optional[int] = None, ) -> Run: ... @@ -3413,17 +3486,18 @@ class SyncRunsClient: assistant_id: str, *, input: Optional[dict] = None, - stream_mode: Union[StreamMode, list[StreamMode]] = "values", + stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values", stream_subgraphs: bool = False, metadata: Optional[dict] = None, config: Optional[Config] = None, checkpoint: Optional[Checkpoint] = None, checkpoint_id: Optional[str] = None, - interrupt_before: Optional[Union[All, list[str]]] = None, - interrupt_after: Optional[Union[All, list[str]]] = None, + interrupt_before: Optional[Union[All, Sequence[str]]] = None, + interrupt_after: Optional[Union[All, Sequence[str]]] = None, webhook: Optional[str] = None, multitask_strategy: Optional[MultitaskStrategy] = None, on_completion: Optional[OnCompletionBehavior] = None, + if_not_exists: Optional[IfNotExists] = None, after_seconds: Optional[int] = None, ) -> Run: """Create a background run. @@ -3446,6 +3520,8 @@ class SyncRunsClient: Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'. on_completion: Whether to delete or keep the thread created for a stateless run. Must be one of 'delete' or 'keep'. + if_not_exists: How to handle missing thread. Defaults to 'reject'. + Must be either 'reject' (raise error if missing), or 'create' (create new thread). after_seconds: The number of seconds to wait before starting the run. Use to schedule future runs. @@ -3529,6 +3605,7 @@ class SyncRunsClient: "checkpoint": checkpoint, "checkpoint_id": checkpoint_id, "multitask_strategy": multitask_strategy, + "if_not_exists": if_not_exists, "on_completion": on_completion, "after_seconds": after_seconds, } @@ -3558,11 +3635,12 @@ class SyncRunsClient: config: Optional[Config] = None, checkpoint: Optional[Checkpoint] = None, checkpoint_id: Optional[str] = None, - interrupt_before: Optional[Union[All, list[str]]] = None, - interrupt_after: Optional[Union[All, list[str]]] = None, + interrupt_before: Optional[Union[All, Sequence[str]]] = None, + interrupt_after: Optional[Union[All, Sequence[str]]] = None, webhook: Optional[str] = None, on_disconnect: Optional[DisconnectMode] = None, multitask_strategy: Optional[MultitaskStrategy] = None, + if_not_exists: Optional[IfNotExists] = None, after_seconds: Optional[int] = None, ) -> Union[list[dict], dict[str, Any]]: ... @@ -3575,11 +3653,12 @@ class SyncRunsClient: input: Optional[dict] = None, metadata: Optional[dict] = None, config: Optional[Config] = None, - interrupt_before: Optional[Union[All, list[str]]] = None, - interrupt_after: Optional[Union[All, list[str]]] = None, + interrupt_before: Optional[Union[All, Sequence[str]]] = None, + interrupt_after: Optional[Union[All, Sequence[str]]] = None, webhook: Optional[str] = None, on_disconnect: Optional[DisconnectMode] = None, on_completion: Optional[OnCompletionBehavior] = None, + if_not_exists: Optional[IfNotExists] = None, after_seconds: Optional[int] = None, ) -> Union[list[dict], dict[str, Any]]: ... @@ -3593,12 +3672,13 @@ class SyncRunsClient: config: Optional[Config] = None, checkpoint: Optional[Checkpoint] = None, checkpoint_id: Optional[str] = None, - interrupt_before: Optional[Union[All, list[str]]] = None, - interrupt_after: Optional[Union[All, list[str]]] = None, + interrupt_before: Optional[Union[All, Sequence[str]]] = None, + interrupt_after: Optional[Union[All, Sequence[str]]] = None, webhook: Optional[str] = None, on_disconnect: Optional[DisconnectMode] = None, on_completion: Optional[OnCompletionBehavior] = None, multitask_strategy: Optional[MultitaskStrategy] = None, + if_not_exists: Optional[IfNotExists] = None, after_seconds: Optional[int] = None, ) -> Union[list[dict], dict[str, Any]]: """Create a run, wait until it finishes and return the final state. @@ -3621,6 +3701,8 @@ class SyncRunsClient: Must be one of 'delete' or 'keep'. multitask_strategy: Multitask strategy to use. Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'. + if_not_exists: How to handle missing thread. Defaults to 'reject'. + Must be either 'reject' (raise error if missing), or 'create' (create new thread). after_seconds: The number of seconds to wait before starting the run. Use to schedule future runs. @@ -3682,6 +3764,7 @@ class SyncRunsClient: "checkpoint": checkpoint, "checkpoint_id": checkpoint_id, "multitask_strategy": multitask_strategy, + "if_not_exists": if_not_exists, "on_disconnect": on_disconnect, "on_completion": on_completion, "after_seconds": after_seconds, @@ -3736,13 +3819,22 @@ class SyncRunsClient: return self.http.get(f"/threads/{thread_id}/runs/{run_id}") - def cancel(self, thread_id: str, run_id: str, *, wait: bool = False) -> None: + def cancel( + self, + thread_id: str, + run_id: str, + *, + wait: bool = False, + action: CancelAction = "interrupt", + ) -> None: """Get a run. Args: thread_id: The thread ID to cancel. run_id: The run ID to cancek. wait: Whether to wait until run has completed. + action: Action to take when cancelling the run. Possible values + are `interrupt` or `rollback`. Default is `interrupt`. Returns: None @@ -3752,12 +3844,13 @@ class SyncRunsClient: client.runs.cancel( thread_id="thread_id_to_cancel", run_id="run_id_to_cancel", - wait=True + wait=True, + action="interrupt" ) """ # noqa: E501 return self.http.post( - f"/threads/{thread_id}/runs/{run_id}/cancel?wait={1 if wait else 0}", + f"/threads/{thread_id}/runs/{run_id}/cancel?wait={1 if wait else 0}&action={action}", json=None, ) diff --git a/libs/sdk-py/langgraph_sdk/schema.py b/libs/sdk-py/langgraph_sdk/schema.py index 4e5314692..5264ce709 100644 --- a/libs/sdk-py/langgraph_sdk/schema.py +++ b/libs/sdk-py/langgraph_sdk/schema.py @@ -6,26 +6,28 @@ from typing import Any, Literal, NamedTuple, Optional, Sequence, TypedDict, Unio Json = Optional[dict[str, Any]] """Represents a JSON-like structure, which can be None or a dictionary with string keys and any values.""" -RunStatus = Literal["pending", "running", "error", "success", "timeout", "interrupted"] +RunStatus = Literal["pending", "error", "success", "timeout", "interrupted"] """ Represents the status of a run: - "pending": The run is waiting to start. -- "running": The run is currently in progress. - "error": The run encountered an error and stopped. - "success": The run completed successfully. - "timeout": The run exceeded its time limit. - "interrupted": The run was manually stopped or interrupted. """ -ThreadStatus = Literal["idle", "busy", "interrupted"] +ThreadStatus = Literal["idle", "busy", "interrupted", "error"] """ Represents the status of a thread: - "idle": The thread is not currently processing any task. - "busy": The thread is actively processing a task. - "interrupted": The thread's execution was interrupted. +- "error": An exception occurred during task processing. """ -StreamMode = Literal["values", "messages", "updates", "events", "debug", "custom"] +StreamMode = Literal[ + "values", "messages", "updates", "events", "debug", "custom", "messages-tuple" +] """ Defines the mode of streaming: - "values": Stream only the values. @@ -69,6 +71,20 @@ Defines action after completion: All = Literal["*"] """Represents a wildcard or 'all' selector.""" +IfNotExists = Literal["create", "reject"] +""" +Specifies behavior if the thread doesn't exist: +- "create": Create a new thread if it doesn't exist. +- "reject": Reject the operation if the thread doesn't exist. +""" + +CancelAction = Literal["interrupt", "rollback"] +""" +Action to take when cancelling the run. +- "interrupt": Simply cancel the run. +- "rollback": Cancel the run. Then delete the run and associated checkpoints. +""" + class Config(TypedDict, total=False): """Configuration options for a call.""" @@ -112,7 +128,7 @@ class GraphSchema(TypedDict): graph_id: str """The ID of the graph.""" input_schema: Optional[dict] - """The schema for the graph state. + """The schema for the graph input. Missing if unable to generate JSON schema from graph.""" output_schema: Optional[dict] """The schema for the graph output. @@ -323,3 +339,14 @@ class StreamPart(NamedTuple): """The type of event for this stream part.""" data: dict """The data payload associated with the event.""" + + +class Send(TypedDict): + node: str + input: Optional[dict[str, Any]] + + +class Command(TypedDict, total=False): + send: Union[Send, Sequence[Send]] + update: dict[str, Any] + resume: Any diff --git a/libs/sdk-py/pyproject.toml b/libs/sdk-py/pyproject.toml index 0748c4114..393750ba6 100644 --- a/libs/sdk-py/pyproject.toml +++ b/libs/sdk-py/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langgraph-sdk" -version = "0.1.33" +version = "0.1.36" description = "SDK for interacting with LangGraph API" authors = [] license = "MIT" diff --git a/poetry.lock b/poetry.lock index aa041a3f7..7d0162abb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,14 +1,14 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" -version = "2.4.2" +version = "2.4.3" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "aiohappyeyeballs-2.4.2-py3-none-any.whl", hash = "sha256:8522691d9a154ba1145b157d6d5c15e5c692527ce6a53c5e5f9876977f6dab2f"}, - {file = "aiohappyeyeballs-2.4.2.tar.gz", hash = "sha256:4ca893e6c5c1f5bf3888b04cb5a3bee24995398efef6e0b9f747b5e89d84fd74"}, + {file = "aiohappyeyeballs-2.4.3-py3-none-any.whl", hash = "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572"}, + {file = "aiohappyeyeballs-2.4.3.tar.gz", hash = "sha256:75cf88a15106a5002a8eb1dab212525c00d1f4c0fa96e551c9fbe6f09a621586"}, ] [[package]] @@ -390,6 +390,59 @@ docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphi tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +[[package]] +name = "autogen" +version = "0.3.2" +description = "A programming framework for agentic AI" +optional = false +python-versions = "<3.13,>=3.8" +files = [ + {file = "autogen-0.3.2-py3-none-any.whl", hash = "sha256:e37a9df0ad84cde3429ec63298b8e9eb4e6306a28eec2627171e14b9a61ea64d"}, + {file = "autogen-0.3.2.tar.gz", hash = "sha256:9f8a1170ac2e5a1fc9efc3cfa6e23261dd014db97b17c8c416f97ee14951bc7b"}, +] + +[package.dependencies] +diskcache = "*" +docker = "*" +flaml = "*" +numpy = ">=1.17.0,<2" +openai = ">=1.3" +packaging = "*" +pydantic = ">=1.10,<2.6.0 || >2.6.0,<3" +python-dotenv = "*" +termcolor = "*" +tiktoken = "*" + +[package.extras] +anthropic = ["anthropic (>=0.23.1)"] +autobuild = ["chromadb", "huggingface-hub", "pysqlite3", "sentence-transformers"] +bedrock = ["boto3 (>=1.34.149)"] +blendsearch = ["flaml[blendsearch]"] +cerebras = ["cerebras-cloud-sdk (>=1.0.0)"] +cohere = ["cohere (>=5.5.8)"] +cosmosdb = ["azure-cosmos (>=4.2.0)"] +gemini = ["google-auth", "google-cloud-aiplatform", "google-generativeai (>=0.5,<1)", "pillow", "pydantic"] +graph = ["matplotlib", "networkx"] +graph-rag-falkor-db = ["graphrag-sdk"] +groq = ["groq (>=0.9.0)"] +jupyter-executor = ["ipykernel (>=6.29.0)", "jupyter-client (>=8.6.0)", "jupyter-kernel-gateway", "requests", "websocket-client"] +lmm = ["pillow", "replicate"] +long-context = ["llmlingua (<0.3)"] +mathchat = ["pydantic (==1.10.9)", "sympy", "wolframalpha"] +mistral = ["mistralai (>=1.0.1)"] +ollama = ["fix-busted-json (>=0.0.18)", "ollama (>=0.3.3)"] +redis = ["redis"] +retrievechat = ["beautifulsoup4", "chromadb (==0.5.3)", "ipython", "markdownify", "protobuf (==4.25.3)", "pypdf", "sentence-transformers"] +retrievechat-mongodb = ["beautifulsoup4", "chromadb (==0.5.3)", "ipython", "markdownify", "protobuf (==4.25.3)", "pymongo (>=4.0.0)", "pypdf", "sentence-transformers"] +retrievechat-pgvector = ["beautifulsoup4", "chromadb (==0.5.3)", "ipython", "markdownify", "pgvector (>=0.2.5)", "protobuf (==4.25.3)", "psycopg (>=3.1.18)", "pypdf", "sentence-transformers"] +retrievechat-qdrant = ["beautifulsoup4", "chromadb (==0.5.3)", "fastembed (>=0.3.1)", "ipython", "markdownify", "protobuf (==4.25.3)", "pypdf", "qdrant-client", "sentence-transformers"] +teachable = ["chromadb"] +test = ["ipykernel", "nbconvert", "nbformat", "pandas", "pre-commit", "pytest (>=6.1.1,<8)", "pytest-asyncio", "pytest-cov (>=5)"] +together = ["together (>=1.2)"] +types = ["ipykernel (>=6.29.0)", "jupyter-client (>=8.6.0)", "jupyter-kernel-gateway", "mypy (==1.9.0)", "pytest (>=6.1.1,<8)", "requests", "websocket-client"] +websockets = ["websockets (>=12.0,<13)"] +websurfer = ["beautifulsoup4", "markdownify", "pathvalidate", "pdfminer.six"] + [[package]] name = "babel" version = "2.16.0" @@ -1135,6 +1188,17 @@ wrapt = ">=1.10,<2" [package.extras] dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] +[[package]] +name = "diskcache" +version = "5.6.3" +description = "Disk Cache -- Disk and file backed persistent cache." +optional = false +python-versions = ">=3" +files = [ + {file = "diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19"}, + {file = "diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc"}, +] + [[package]] name = "distro" version = "1.9.0" @@ -1166,6 +1230,28 @@ idna = ["idna (>=3.6)"] trio = ["trio (>=0.23)"] wmi = ["wmi (>=1.5.1)"] +[[package]] +name = "docker" +version = "7.1.0" +description = "A Python library for the Docker Engine API." +optional = false +python-versions = ">=3.8" +files = [ + {file = "docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0"}, + {file = "docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c"}, +] + +[package.dependencies] +pywin32 = {version = ">=304", markers = "sys_platform == \"win32\""} +requests = ">=2.26.0" +urllib3 = ">=1.26.0" + +[package.extras] +dev = ["coverage (==7.2.7)", "pytest (==7.4.2)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.1.0)", "ruff (==0.1.8)"] +docs = ["myst-parser (==0.18.0)", "sphinx (==5.1.1)"] +ssh = ["paramiko (>=2.4.3)"] +websockets = ["websocket-client (>=1.3.0)"] + [[package]] name = "durationpy" version = "0.7" @@ -1271,6 +1357,43 @@ httpx-sse = "*" Pillow = "*" pydantic = "*" +[[package]] +name = "flaml" +version = "2.3.2" +description = "A fast library for automated machine learning and tuning" +optional = false +python-versions = ">=3.8" +files = [ + {file = "FLAML-2.3.2-py3-none-any.whl", hash = "sha256:1ee6e8e76bf1d741b4da41e2a2a8c0638b36d90b0f60aac323b5568f54dcb9e7"}, + {file = "flaml-2.3.2.tar.gz", hash = "sha256:4a1ec289ddaec36850cfc66f6fb335b8521df49ea31f6adb54ea63a5cebb6865"}, +] + +[package.dependencies] +NumPy = ">=1.17" + +[package.extras] +autogen = ["diskcache", "openai (==0.27.8)", "termcolor"] +automl = ["lightgbm (>=2.3.1)", "pandas (>=1.1.4)", "scikit-learn (>=1.0.0)", "scipy (>=1.4.1)", "xgboost (>=0.90,<3.0.0)"] +autozero = ["packaging", "pandas", "scikit-learn"] +azureml = ["azureml-mlflow"] +benchmark = ["catboost (>=0.26)", "pandas (==1.1.4)", "psutil (==5.8.0)", "xgboost (==1.3.3)"] +blendsearch = ["optuna (>=2.8.0,<=3.6.1)", "packaging"] +catboost = ["catboost (>=0.26,<1.2)", "catboost (>=0.26,<=1.2.5)"] +forecast = ["hcrystalball (==0.1.10)", "holidays (<0.14)", "prophet (>=1.0.1)", "pytorch-forecasting (>=0.9.0)", "pytorch-lightning (==1.9.0)", "statsmodels (>=0.12.2)", "tensorboardX (==2.6)"] +hf = ["datasets", "nltk (<=3.8.1)", "rouge-score", "seqeval", "transformers[torch] (==4.26)"] +mathchat = ["diskcache", "openai (==0.27.8)", "pydantic (==1.10.9)", "sympy", "termcolor", "wolframalpha"] +nlp = ["datasets", "nltk (<=3.8.1)", "rouge-score", "seqeval", "transformers[torch] (==4.26)"] +nni = ["nni"] +notebook = ["jupyter"] +openai = ["diskcache", "openai (==0.27.8)"] +ray = ["ray[tune] (>=1.13,<2.0)"] +retrievechat = ["chromadb", "diskcache", "openai (==0.27.8)", "sentence-transformers", "termcolor", "tiktoken"] +spark = ["joblib (<=1.3.2)", "joblibspark (>=0.5.0)", "pyspark (>=3.2.0)"] +synapse = ["joblibspark (>=0.5.0)", "optuna (>=2.8.0,<=3.6.1)", "pyspark (>=3.2.0)"] +test = ["catboost (>=0.26)", "catboost (>=0.26,<1.2)", "coverage (>=5.3)", "dataclasses", "datasets", "dill", "hcrystalball (==0.1.10)", "ipykernel", "joblib (<=1.3.2)", "joblibspark (>=0.5.0)", "jupyter", "lightgbm (>=2.3.1)", "mlflow (==2.15.1)", "nbconvert", "nbformat", "nltk (<=3.8.1)", "openml", "optuna (>=2.8.0,<=3.6.1)", "packaging", "pandas (>=1.1.4)", "pandas (>=1.1.4,<2.0.0)", "pre-commit", "psutil (==5.8.0)", "pydantic (==1.10.9)", "pytest (>=6.1.1)", "pytorch-forecasting (>=0.9.0,<=0.10.1)", "pytorch-lightning (<1.9.1)", "requests (<2.29.0)", "rgf-python", "rouge-score", "scikit-learn (>=1.0.0)", "scipy (>=1.4.1)", "seqeval", "statsmodels (>=0.12.2)", "sympy", "tensorboardX (==2.6)", "thop", "torch", "torchvision", "transformers[torch] (==4.26)", "wolframalpha", "xgboost (>=0.90,<2.0.0)"] +ts-forecast = ["hcrystalball (==0.1.10)", "holidays (<0.14)", "prophet (>=1.0.1)", "statsmodels (>=0.12.2)"] +vw = ["scikit-learn", "vowpalwabbit (>=8.10.0,<9.0.0)"] + [[package]] name = "flatbuffers" version = "24.3.25" @@ -2810,13 +2933,13 @@ tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0" [[package]] name = "langchain-core" -version = "0.3.8" +version = "0.3.15" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "langchain_core-0.3.8-py3-none-any.whl", hash = "sha256:07015f7b1d9f52eefe05130e8cafe4dcbdbbf72a8411c9edafe38422e4d11b5c"}, - {file = "langchain_core-0.3.8.tar.gz", hash = "sha256:7485904f7082f1df880d5ae470a488161616132f30d99f556a1877901fffd1cb"}, + {file = "langchain_core-0.3.15-py3-none-any.whl", hash = "sha256:3d4ca6dbb8ed396a6ee061063832a2451b0ce8c345570f7b086ffa7288e4fa29"}, + {file = "langchain_core-0.3.15.tar.gz", hash = "sha256:b1a29787a4ffb7ec2103b4e97d435287201da7809b369740dd1e32f176325aba"}, ] [package.dependencies] @@ -2828,7 +2951,7 @@ pydantic = [ {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, ] PyYAML = ">=5.3" -tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0" +tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" typing-extensions = ">=4.7" [[package]] @@ -2912,7 +3035,7 @@ langchain-core = ">=0.3.0,<0.4.0" [[package]] name = "langgraph" -version = "0.2.34" +version = "0.2.52" description = "Building stateful, multi-actor applications with LLMs" optional = false python-versions = ">=3.9.0,<4.0" @@ -2920,8 +3043,9 @@ files = [] develop = true [package.dependencies] -langchain-core = ">=0.2.39,<0.4" -langgraph-checkpoint = "^2.0.0" +langchain-core = ">=0.2.43,<0.4.0,!=0.3.0,!=0.3.1,!=0.3.2,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14" +langgraph-checkpoint = "^2.0.4" +langgraph-sdk = "^0.1.32" [package.source] type = "directory" @@ -2929,7 +3053,7 @@ url = "libs/langgraph" [[package]] name = "langgraph-checkpoint" -version = "2.0.1" +version = "2.0.5" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = "^3.9.0,<4.0" @@ -2946,7 +3070,7 @@ url = "libs/checkpoint" [[package]] name = "langgraph-checkpoint-postgres" -version = "2.0.1" +version = "2.0.3" description = "Library with a Postgres implementation of LangGraph checkpoint saver." optional = false python-versions = "^3.9.0,<4.0" @@ -2954,7 +3078,7 @@ files = [] develop = true [package.dependencies] -langgraph-checkpoint = "^2.0.0" +langgraph-checkpoint = "^2.0.2" orjson = ">=3.10.1" psycopg = "^3.0.0" psycopg-pool = "^3.0.0" @@ -2965,7 +3089,7 @@ url = "libs/checkpoint-postgres" [[package]] name = "langgraph-checkpoint-sqlite" -version = "2.0.0" +version = "2.0.1" description = "Library with a SQLite implementation of LangGraph checkpoint saver." optional = false python-versions = "^3.9.0" @@ -2974,7 +3098,7 @@ develop = true [package.dependencies] aiosqlite = "^0.20.0" -langgraph-checkpoint = "^2.0.0" +langgraph-checkpoint = "^2.0.2" [package.source] type = "directory" @@ -2982,7 +3106,7 @@ url = "libs/checkpoint-sqlite" [[package]] name = "langgraph-sdk" -version = "0.1.32" +version = "0.1.36" description = "SDK for interacting with LangGraph API" optional = false python-versions = "^3.9.0,<4.0" @@ -4974,6 +5098,7 @@ description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs optional = false python-versions = ">=3.8" files = [ + {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, ] @@ -4984,6 +5109,7 @@ description = "A collection of ASN.1-based protocols modules" optional = false python-versions = ">=3.8" files = [ + {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, ] @@ -6043,6 +6169,11 @@ files = [ {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f60021ec1574e56632be2a36b946f8143bf4e5e6af4a06d85281adc22938e0dd"}, {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:394397841449853c2290a32050382edaec3da89e35b3e03d6cc966aebc6a8ae6"}, {file = "scikit_learn-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:57cc1786cfd6bd118220a92ede80270132aa353647684efa385a74244a41e3b1"}, + {file = "scikit_learn-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9a702e2de732bbb20d3bad29ebd77fc05a6b427dc49964300340e4c9328b3f5"}, + {file = "scikit_learn-1.5.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:b0768ad641981f5d3a198430a1d31c3e044ed2e8a6f22166b4d546a5116d7908"}, + {file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:178ddd0a5cb0044464fc1bfc4cca5b1833bfc7bb022d70b05db8530da4bb3dd3"}, + {file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7284ade780084d94505632241bf78c44ab3b6f1e8ccab3d2af58e0e950f9c12"}, + {file = "scikit_learn-1.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:b7b0f9a0b1040830d38c39b91b3a44e1b643f4b36e36567b80b7c6bd2202a27f"}, {file = "scikit_learn-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:757c7d514ddb00ae249832fe87100d9c73c6ea91423802872d9e74970a0e40b9"}, {file = "scikit_learn-1.5.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:52788f48b5d8bca5c0736c175fa6bdaab2ef00a8f536cda698db61bd89c551c1"}, {file = "scikit_learn-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:643964678f4b5fbdc95cbf8aec638acc7aa70f5f79ee2cdad1eec3df4ba6ead8"}, @@ -6362,6 +6493,20 @@ files = [ doc = ["reno", "sphinx"] test = ["pytest", "tornado (>=4.5)", "typeguard"] +[[package]] +name = "termcolor" +version = "2.5.0" +description = "ANSI color formatting for output in terminal" +optional = false +python-versions = ">=3.9" +files = [ + {file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"}, + {file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"}, +] + +[package.extras] +tests = ["pytest", "pytest-cov"] + [[package]] name = "terminado" version = "0.18.1" @@ -7331,4 +7476,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "67675531a6c4cd218c9effe87061234fbc778ff1afbb61327239b20556aa63da" +content-hash = "776ee42630769f08e3896338f18ec81830166695d32d2208dc31dedb22d3b22d" diff --git a/pyproject.toml b/pyproject.toml index 3238a7a10..31fe17172 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.10" +aiohappyeyeballs = "2.4.3" [tool.poetry.group.docs.dependencies] langgraph = { path = "libs/langgraph/", develop = true } @@ -53,6 +54,7 @@ motor = "^3.5.1" grandalf = "^0.8" pyppeteer = "^2.0.0" networkx = "^3.3" +autogen = { version = "^0.3.0", python = "<3.13,>=3.8" } [tool.poetry.group.test] optional = true