From f98149b5c598306534a742081c7da9dd2bb631c3 Mon Sep 17 00:00:00 2001 From: Tyler Hutcherson Date: Sat, 22 Feb 2025 13:51:47 -0500 Subject: [PATCH 1/5] refactor to use redisvl 0.4.1+ --- .github/workflows/test.yml | 38 ++-- Makefile | 5 +- langgraph/checkpoint/redis/__init__.py | 18 +- langgraph/checkpoint/redis/aio.py | 17 +- langgraph/checkpoint/redis/ashallow.py | 17 +- langgraph/checkpoint/redis/shallow.py | 17 +- langgraph/store/redis/__init__.py | 2 +- langgraph/store/redis/aio.py | 39 ++-- langgraph/store/redis/base.py | 10 +- poetry.lock | 252 +++++++++++++++++++------ pyproject.toml | 6 +- scripts.py | 17 +- tests/conftest.py | 56 ++---- tests/docker-compose.yml | 2 +- tests/test_async.py | 18 +- tests/test_async_store.py | 7 +- tests/test_shallow_async.py | 49 +++++ tests/test_shallow_sync.py | 16 +- tests/test_store.py | 8 +- tests/test_sync.py | 24 +-- 20 files changed, 409 insertions(+), 209 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d8c6005..3cc7ef4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,29 +7,26 @@ on: branches: - main + workflow_dispatch: + env: POETRY_VERSION: "1.8.3" jobs: test: - name: Python ${{ matrix.python-version }} - ${{ matrix.connection }} [redis-stack ${{matrix.redis-stack-version}}] + name: Python ${{ matrix.python-version }} - [redis ${{ matrix.redis-version }}] runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: [3.9, '3.10', 3.11, 3.12] - connection: ['hiredis', 'plain'] - redis-stack-version: ['6.2.6-v9', 'latest', 'edge'] - - services: - redis: - image: redis/redis-stack-server:${{matrix.redis-stack-version}} - ports: - - 6379:6379 + python-version: [3.9, 3.11, 3.13] + redis-version: ['6.2.6-v9', 'latest', '8.0-M03'] steps: - - uses: actions/checkout@v2 + - name: Check out repository + uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: @@ -45,15 +42,22 @@ jobs: run: | poetry install --all-extras - - name: Install hiredis if needed - if: matrix.connection == 'hiredis' + - name: Set Redis image name run: | - poetry add hiredis - - - name: Set Redis version + if [[ "${{ matrix.redis-version }}" == "8.0-M03" ]]; then + echo "REDIS_IMAGE=redis:${{ matrix.redis-version }}" >> $GITHUB_ENV + else + echo "REDIS_IMAGE=redis/redis-stack-server:${{ matrix.redis-version }}" >> $GITHUB_ENV + fi + + - name: Run API tests + if: matrix.redis-version == 'latest' + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | - echo "REDIS_VERSION=${{ matrix.redis-stack-version }}" >> $GITHUB_ENV + make test-all - name: Run tests + if: matrix.redis-version != 'latest' run: | make test diff --git a/Makefile b/Makefile index 9308db0..d3fa956 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: install format lint test clean redis-start redis-stop check-types check +.PHONY: install format lint test test-all clean redis-start redis-stop check-types check install: poetry install --all-extras @@ -21,6 +21,9 @@ lint: format check-types test: poetry run test-verbose +test-all: + poetry run test-verbose --run-api-tests + check: lint test clean: diff --git a/langgraph/checkpoint/redis/__init__.py b/langgraph/checkpoint/redis/__init__.py index 07c8c7b..ae9d505 100644 --- a/langgraph/checkpoint/redis/__init__.py +++ b/langgraph/checkpoint/redis/__init__.py @@ -51,20 +51,20 @@ def configure_client( ) -> None: """Configure the Redis client.""" self._owns_its_client = redis_client is None - self._redis = redis_client or RedisConnectionFactory.get_redis_connection( redis_url, **connection_args ) def create_indexes(self) -> None: - self.checkpoints_index = SearchIndex.from_dict(self.SCHEMAS[0]) - self.checkpoint_blobs_index = SearchIndex.from_dict(self.SCHEMAS[1]) - self.checkpoint_writes_index = SearchIndex.from_dict(self.SCHEMAS[2]) - - # Connect Redis client to indices - self.checkpoints_index.set_client(self._redis) - self.checkpoint_blobs_index.set_client(self._redis) - self.checkpoint_writes_index.set_client(self._redis) + self.checkpoints_index = SearchIndex.from_dict( + self.SCHEMAS[0], redis_client=self._redis + ) + self.checkpoint_blobs_index = SearchIndex.from_dict( + self.SCHEMAS[1], redis_client=self._redis + ) + self.checkpoint_writes_index = SearchIndex.from_dict( + self.SCHEMAS[2], redis_client=self._redis + ) def list( self, diff --git a/langgraph/checkpoint/redis/aio.py b/langgraph/checkpoint/redis/aio.py index e7cc426..82c4311 100644 --- a/langgraph/checkpoint/redis/aio.py +++ b/langgraph/checkpoint/redis/aio.py @@ -88,9 +88,15 @@ def configure_client( def create_indexes(self) -> None: """Create indexes without connecting to Redis.""" - self.checkpoints_index = AsyncSearchIndex.from_dict(self.SCHEMAS[0]) - self.checkpoint_blobs_index = AsyncSearchIndex.from_dict(self.SCHEMAS[1]) - self.checkpoint_writes_index = AsyncSearchIndex.from_dict(self.SCHEMAS[2]) + self.checkpoints_index = AsyncSearchIndex.from_dict( + self.SCHEMAS[0], redis_client=self._redis + ) + self.checkpoint_blobs_index = AsyncSearchIndex.from_dict( + self.SCHEMAS[1], redis_client=self._redis + ) + self.checkpoint_writes_index = AsyncSearchIndex.from_dict( + self.SCHEMAS[2], redis_client=self._redis + ) async def __aenter__(self) -> AsyncRedisSaver: """Async context manager enter.""" @@ -116,11 +122,6 @@ async def __aexit__( async def asetup(self) -> None: """Initialize Redis indexes asynchronously.""" - # Connect Redis client to indices asynchronously - await self.checkpoints_index.set_client(self._redis) - await self.checkpoint_blobs_index.set_client(self._redis) - await self.checkpoint_writes_index.set_client(self._redis) - # Create indexes in Redis asynchronously await self.checkpoints_index.create(overwrite=False) await self.checkpoint_blobs_index.create(overwrite=False) diff --git a/langgraph/checkpoint/redis/ashallow.py b/langgraph/checkpoint/redis/ashallow.py index 5233be1..377f43c 100644 --- a/langgraph/checkpoint/redis/ashallow.py +++ b/langgraph/checkpoint/redis/ashallow.py @@ -153,11 +153,6 @@ async def from_conn_string( async def asetup(self) -> None: """Initialize Redis indexes asynchronously.""" - # Connect Redis client to indices asynchronously - await self.checkpoints_index.set_client(self._redis) - await self.checkpoint_blobs_index.set_client(self._redis) - await self.checkpoint_writes_index.set_client(self._redis) - # Create indexes in Redis asynchronously await self.checkpoints_index.create(overwrite=False) await self.checkpoint_blobs_index.create(overwrite=False) @@ -557,9 +552,15 @@ def configure_client( def create_indexes(self) -> None: """Create indexes without connecting to Redis.""" - self.checkpoints_index = AsyncSearchIndex.from_dict(self.SCHEMAS[0]) - self.checkpoint_blobs_index = AsyncSearchIndex.from_dict(self.SCHEMAS[1]) - self.checkpoint_writes_index = AsyncSearchIndex.from_dict(self.SCHEMAS[2]) + self.checkpoints_index = AsyncSearchIndex.from_dict( + self.SCHEMAS[0], redis_client=self._redis + ) + self.checkpoint_blobs_index = AsyncSearchIndex.from_dict( + self.SCHEMAS[1], redis_client=self._redis + ) + self.checkpoint_writes_index = AsyncSearchIndex.from_dict( + self.SCHEMAS[2], redis_client=self._redis + ) def setup(self) -> None: """Initialize the checkpoint_index in Redis.""" diff --git a/langgraph/checkpoint/redis/shallow.py b/langgraph/checkpoint/redis/shallow.py index 47bea59..7345462 100644 --- a/langgraph/checkpoint/redis/shallow.py +++ b/langgraph/checkpoint/redis/shallow.py @@ -388,14 +388,15 @@ def configure_client( ) def create_indexes(self) -> None: - self.checkpoints_index = SearchIndex.from_dict(self.SCHEMAS[0]) - self.checkpoint_blobs_index = SearchIndex.from_dict(self.SCHEMAS[1]) - self.checkpoint_writes_index = SearchIndex.from_dict(self.SCHEMAS[2]) - - # Connect Redis client to indices - self.checkpoints_index.set_client(self._redis) - self.checkpoint_blobs_index.set_client(self._redis) - self.checkpoint_writes_index.set_client(self._redis) + self.checkpoints_index = SearchIndex.from_dict( + self.SCHEMAS[0], redis_client=self._redis + ) + self.checkpoint_blobs_index = SearchIndex.from_dict( + self.SCHEMAS[1], redis_client=self._redis + ) + self.checkpoint_writes_index = SearchIndex.from_dict( + self.SCHEMAS[2], redis_client=self._redis + ) def put_writes( self, diff --git a/langgraph/store/redis/__init__.py b/langgraph/store/redis/__init__.py index 79d3cb1..6faa0d6 100644 --- a/langgraph/store/redis/__init__.py +++ b/langgraph/store/redis/__init__.py @@ -8,7 +8,6 @@ from contextlib import contextmanager from datetime import datetime, timezone from typing import Any, Iterable, Iterator, Optional, Sequence, cast -from ulid import ULID from langgraph.store.base import ( BaseStore, @@ -26,6 +25,7 @@ from redisvl.query import FilterQuery, VectorQuery from redisvl.redis.connection import RedisConnectionFactory from redisvl.utils.token_escaper import TokenEscaper +from ulid import ULID from langgraph.store.redis.aio import AsyncRedisStore from langgraph.store.redis.base import ( diff --git a/langgraph/store/redis/aio.py b/langgraph/store/redis/aio.py index 8625122..407ae91 100644 --- a/langgraph/store/redis/aio.py +++ b/langgraph/store/redis/aio.py @@ -7,7 +7,6 @@ from datetime import datetime, timezone from types import TracebackType from typing import Any, AsyncIterator, Iterable, Optional, Sequence, cast -from ulid import ULID from langgraph.store.base import ( BaseStore, @@ -29,6 +28,7 @@ from redisvl.query import FilterQuery, VectorQuery from redisvl.redis.connection import RedisConnectionFactory from redisvl.utils.token_escaper import TokenEscaper +from ulid import ULID from langgraph.store.redis.base import ( REDIS_KEY_SEPARATOR, @@ -57,7 +57,7 @@ class AsyncRedisStore( store_index: AsyncSearchIndex vector_index: AsyncSearchIndex - _owns_client: bool + _owns_its_client: bool def __init__( self, @@ -65,6 +65,7 @@ def __init__( *, redis_client: Optional[AsyncRedis] = None, index: Optional[IndexConfig] = None, + connection_args: Optional[dict[str, Any]] = None, ) -> None: """Initialize store with Redis connection and optional index config.""" if redis_url is None and redis_client is None: @@ -94,10 +95,16 @@ def __init__( ] # Configure client - self.configure_client(redis_url=redis_url, redis_client=redis_client) + self.configure_client( + redis_url=redis_url, + redis_client=redis_client, + connection_args=connection_args or {}, + ) # Create store index - self.store_index = AsyncSearchIndex.from_dict(self.SCHEMAS[0]) + self.store_index = AsyncSearchIndex.from_dict( + self.SCHEMAS[0], redis_client=self._redis + ) # Configure vector index if needed if self.index_config: @@ -131,7 +138,9 @@ def __init__( vector_field["attrs"].update(self.index_config["ann_index_config"]) try: - self.vector_index = AsyncSearchIndex.from_dict(vector_schema) + self.vector_index = AsyncSearchIndex.from_dict( + vector_schema, redis_client=self._redis + ) except Exception as e: raise ValueError( f"Failed to create vector index with schema: {vector_schema}. Error: {str(e)}" @@ -145,11 +154,12 @@ def configure_client( self, redis_url: Optional[str] = None, redis_client: Optional[AsyncRedis] = None, + connection_args: Optional[dict[str, Any]] = None, ) -> None: """Configure the Redis client.""" - self._owns_client = redis_client is None + self._owns_its_client = redis_client is None self._redis = redis_client or RedisConnectionFactory.get_async_redis_connection( - redis_url + redis_url, **connection_args ) async def setup(self) -> None: @@ -160,11 +170,6 @@ async def setup(self) -> None: self.index_config.get("embed"), ) - # Now connect Redis client to indices - await self.store_index.set_client(self._redis) - if self.index_config: - await self.vector_index.set_client(self._redis) - # Create indices in Redis await self.store_index.create(overwrite=False) if self.index_config: @@ -188,9 +193,13 @@ async def from_conn_string( def create_indexes(self) -> None: """Create async indices.""" - self.store_index = AsyncSearchIndex.from_dict(self.SCHEMAS[0]) + self.store_index = AsyncSearchIndex.from_dict( + self.SCHEMAS[0], redis_client=self._redis + ) if self.index_config: - self.vector_index = AsyncSearchIndex.from_dict(self.SCHEMAS[1]) + self.vector_index = AsyncSearchIndex.from_dict( + self.SCHEMAS[1], redis_client=self._redis + ) async def __aenter__(self) -> AsyncRedisStore: """Async context manager enter.""" @@ -210,7 +219,7 @@ async def __aexit__( except asyncio.CancelledError: pass - if self._owns_client: + if self._owns_its_client: await self._redis.aclose() # type: ignore[attr-defined] await self._redis.connection_pool.disconnect() diff --git a/langgraph/store/redis/base.py b/langgraph/store/redis/base.py index d98af80..cfb8e8f 100644 --- a/langgraph/store/redis/base.py +++ b/langgraph/store/redis/base.py @@ -121,8 +121,9 @@ def __init__( ] # Initialize search indices - self.store_index = SearchIndex.from_dict(self.SCHEMAS[0]) - self.store_index.set_client(self._redis) + self.store_index = SearchIndex.from_dict( + self.SCHEMAS[0], redis_client=self._redis + ) # Configure vector index if needed if self.index_config: @@ -156,8 +157,9 @@ def __init__( if "ann_index_config" in self.index_config: vector_field["attrs"].update(self.index_config["ann_index_config"]) - self.vector_index = SearchIndex.from_dict(vector_schema) - self.vector_index.set_client(self._redis) + self.vector_index = SearchIndex.from_dict( + vector_schema, redis_client=self._redis + ) def _get_batch_GET_ops_queries( self, diff --git a/poetry.lock b/poetry.lock index 266b053..f90d39e 100644 --- a/poetry.lock +++ b/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 2.0.1 and should not be changed by hand. [[package]] name = "aioconsole" @@ -6,6 +6,7 @@ version = "0.8.1" description = "Asynchronous console and interfaces for asyncio" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "aioconsole-0.8.1-py3-none-any.whl", hash = "sha256:e1023685cde35dde909fbf00631ffb2ed1c67fe0b7058ebb0892afbde5f213e5"}, {file = "aioconsole-0.8.1.tar.gz", hash = "sha256:0535ce743ba468fb21a1ba43c9563032c779534d4ecd923a46dbd350ad91d234"}, @@ -20,6 +21,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] 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"}, @@ -31,6 +33,7 @@ version = "0.45.2" description = "The official Python library for the anthropic API" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "anthropic-0.45.2-py3-none-any.whl", hash = "sha256:ecd746f7274451dfcb7e1180571ead624c7e1195d1d46cb7c70143d2aedb4d35"}, {file = "anthropic-0.45.2.tar.gz", hash = "sha256:32a18b9ecd12c91b2be4cae6ca2ab46a06937b5aa01b21308d97a6d29794fb5e"}, @@ -55,6 +58,7 @@ version = "4.8.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, @@ -77,6 +81,8 @@ version = "5.0.1" description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_full_version < \"3.11.3\"" files = [ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, @@ -88,6 +94,7 @@ version = "25.1.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, @@ -134,6 +141,7 @@ version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, @@ -145,6 +153,7 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {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"}, @@ -214,6 +223,7 @@ files = [ {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] +markers = {main = "platform_python_implementation == \"PyPy\""} [package.dependencies] pycparser = "*" @@ -224,6 +234,7 @@ version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -325,6 +336,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -339,6 +351,7 @@ version = "2.4.1" description = "Fix common misspellings in text files" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "codespell-2.4.1-py3-none-any.whl", hash = "sha256:3dadafa67df7e4a3dbf51e0d7315061b80d265f9552ebd699b3dd6834b47e425"}, {file = "codespell-2.4.1.tar.gz", hash = "sha256:299fcdcb09d23e81e35a671bbe746d5ad7e8385972e65dbb833a2eaac33c01e5"}, @@ -356,6 +369,8 @@ 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" +groups = ["dev"] +markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -367,6 +382,7 @@ version = "15.0.1" description = "Colored terminal output for Python's logging module" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] files = [ {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, @@ -380,51 +396,56 @@ cron = ["capturer (>=2.4)"] [[package]] name = "cryptography" -version = "43.0.3" +version = "44.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false -python-versions = ">=3.7" -files = [ - {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, - {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, - {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, - {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, - {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, - {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, - {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, - {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, - {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, - {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, - {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, +python-versions = "!=3.9.0,!=3.9.1,>=3.7" +groups = ["dev"] +files = [ + {file = "cryptography-44.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf688f615c29bfe9dfc44312ca470989279f0e94bb9f631f85e3459af8efc009"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd7c7e2d71d908dc0f8d2027e1604102140d84b155e658c20e8ad1304317691f"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887143b9ff6bad2b7570da75a7fe8bbf5f65276365ac259a5d2d5147a73775f2"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:322eb03ecc62784536bc173f1483e76747aafeb69c8728df48537eb431cd1911"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:21377472ca4ada2906bc313168c9dc7b1d7ca417b63c1c3011d0c74b7de9ae69"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:df978682c1504fc93b3209de21aeabf2375cb1571d4e61907b3e7a2540e83026"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:eb3889330f2a4a148abead555399ec9a32b13b7c8ba969b72d8e500eb7ef84cd"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8e6a85a93d0642bd774460a86513c5d9d80b5c002ca9693e63f6e540f1815ed0"}, + {file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6f76fdd6fd048576a04c5210d53aa04ca34d2ed63336d4abd306d0cbe298fddf"}, + {file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6c8acf6f3d1f47acb2248ec3ea261171a671f3d9428e34ad0357148d492c7864"}, + {file = "cryptography-44.0.1-cp37-abi3-win32.whl", hash = "sha256:24979e9f2040c953a94bf3c6782e67795a4c260734e5264dceea65c8f4bae64a"}, + {file = "cryptography-44.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:fd0ee90072861e276b0ff08bd627abec29e32a53b2be44e41dbcdf87cbee2b00"}, + {file = "cryptography-44.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a2d8a7045e1ab9b9f803f0d9531ead85f90c5f2859e653b61497228b18452008"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8272f257cf1cbd3f2e120f14c68bff2b6bdfcc157fafdee84a1b795efd72862"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e8d181e90a777b63f3f0caa836844a1182f1f265687fac2115fcf245f5fbec3"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:436df4f203482f41aad60ed1813811ac4ab102765ecae7a2bbb1dbb66dcff5a7"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4f422e8c6a28cf8b7f883eb790695d6d45b0c385a2583073f3cec434cc705e1a"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:72198e2b5925155497a5a3e8c216c7fb3e64c16ccee11f0e7da272fa93b35c4c"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a46a89ad3e6176223b632056f321bc7de36b9f9b93b2cc1cccf935a3849dc62"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:53f23339864b617a3dfc2b0ac8d5c432625c80014c25caac9082314e9de56f41"}, + {file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:888fcc3fce0c888785a4876ca55f9f43787f4c5c1cc1e2e0da71ad481ff82c5b"}, + {file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00918d859aa4e57db8299607086f793fa7813ae2ff5a4637e318a25ef82730f7"}, + {file = "cryptography-44.0.1-cp39-abi3-win32.whl", hash = "sha256:9b336599e2cb77b1008cb2ac264b290803ec5e8e89d618a5e978ff5eb6f715d9"}, + {file = "cryptography-44.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:e403f7f766ded778ecdb790da786b418a9f2394f36e8cc8b796cc056ab05f44f"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1f9a92144fa0c877117e9748c74501bea842f93d21ee00b0cf922846d9d0b183"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:610a83540765a8d8ce0f351ce42e26e53e1f774a6efb71eb1b41eb01d01c3d12"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5fed5cd6102bb4eb843e3315d2bf25fede494509bddadb81e03a859c1bc17b83"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f4daefc971c2d1f82f03097dc6f216744a6cd2ac0f04c68fb935ea2ba2a0d420"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94f99f2b943b354a5b6307d7e8d19f5c423a794462bde2bf310c770ba052b1c4"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d9c5b9f698a83c8bd71e0f4d3f9f839ef244798e5ffe96febfa9714717db7af7"}, + {file = "cryptography-44.0.1.tar.gz", hash = "sha256:f51f5705ab27898afda1aaa430f34ad90dc117421057782022edf0600bec5f14"}, ] [package.dependencies] cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] -nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] -sdist = ["build"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi (>=2024)", "cryptography-vectors (==44.0.1)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] [[package]] @@ -433,6 +454,7 @@ version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, @@ -444,6 +466,7 @@ version = "7.1.0" description = "A Python library for the Docker Engine API." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0"}, {file = "docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c"}, @@ -466,6 +489,8 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -480,6 +505,7 @@ version = "2.1.1" description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, @@ -494,6 +520,7 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -505,6 +532,7 @@ version = "1.0.7" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, @@ -526,6 +554,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -550,6 +579,7 @@ version = "10.0" description = "Human friendly output for text interfaces using Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] files = [ {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, @@ -564,6 +594,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -578,6 +609,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -589,6 +621,7 @@ version = "6.0.0" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.9.0" +groups = ["dev"] files = [ {file = "isort-6.0.0-py3-none-any.whl", hash = "sha256:567954102bb47bb12e0fae62606570faacddd441e45683968c8d1734fb1af892"}, {file = "isort-6.0.0.tar.gz", hash = "sha256:75d9d8a1438a9432a7d7b54f2d3b45cad9a4a0fdba43617d9873379704a8bdf1"}, @@ -604,6 +637,7 @@ version = "0.8.2" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "jiter-0.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ca8577f6a413abe29b079bc30f907894d7eb07a865c4df69475e868d73e71c7b"}, {file = "jiter-0.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b25bd626bde7fb51534190c7e3cb97cee89ee76b76d7585580e22f34f5e3f393"}, @@ -689,6 +723,7 @@ 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.*" +groups = ["main", "dev"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, @@ -703,6 +738,7 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, @@ -714,6 +750,7 @@ version = "0.3.7" description = "An integration package connecting AnthropicMessages and LangChain" optional = false python-versions = "<4.0,>=3.9" +groups = ["dev"] files = [ {file = "langchain_anthropic-0.3.7-py3-none-any.whl", hash = "sha256:adec0a1daabd3c25249753c6cd625654917fb9e3feee68e72c7dc3f4449c0f3c"}, {file = "langchain_anthropic-0.3.7.tar.gz", hash = "sha256:534cd1867bc41711cd8c3d0a0bc055e6c5a4215953c87260209a90dc5816f30d"}, @@ -730,6 +767,7 @@ version = "0.3.34" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.9" +groups = ["main", "dev"] files = [ {file = "langchain_core-0.3.34-py3-none-any.whl", hash = "sha256:a057ebeddd2158d3be14bde341b25640ddf958b6989bd6e47160396f5a8202ae"}, {file = "langchain_core-0.3.34.tar.gz", hash = "sha256:26504cf1e8e6c310adad907b890d4e3c147581cfa7434114f6dc1134fe4bc6d3"}, @@ -753,6 +791,7 @@ version = "0.3.4" description = "An integration package connecting OpenAI and LangChain" optional = false python-versions = "<4.0,>=3.9" +groups = ["dev"] files = [ {file = "langchain_openai-0.3.4-py3-none-any.whl", hash = "sha256:58d0c014620eb92f4f46ff9daf584c2a7794896b1379eb85ad7be8d9f3493b61"}, {file = "langchain_openai-0.3.4.tar.gz", hash = "sha256:c6645745a1d1bf19f21ea6fa473a746bd464053ff57ce563215e6165a0c4b9f1"}, @@ -769,6 +808,7 @@ version = "0.2.70" description = "Building stateful, multi-actor applications with LLMs" optional = false python-versions = "<4.0,>=3.9.0" +groups = ["main"] files = [ {file = "langgraph-0.2.70-py3-none-any.whl", hash = "sha256:fe5029830b2332049b8270d8dda446eeb4995dccd5c8c997cc3e538bc9ec81a5"}, {file = "langgraph-0.2.70.tar.gz", hash = "sha256:fc929ce3c96c49cdf62b48d3c8accd71f6a30f2d06bcf4f5946b2b91d1b9ff8e"}, @@ -785,6 +825,7 @@ version = "2.0.12" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = "<4.0.0,>=3.9.0" +groups = ["main"] files = [ {file = "langgraph_checkpoint-2.0.12-py3-none-any.whl", hash = "sha256:37e45a9b06ee37b9fe705c1f96f72a4ca1730195ca9553f1c1f49a152dbf21ff"}, {file = "langgraph_checkpoint-2.0.12.tar.gz", hash = "sha256:1b7e4967b784e2b66dc38ff6840e929c658c47ee00c28cf0e354b95062060e89"}, @@ -800,6 +841,7 @@ version = "0.1.51" description = "SDK for interacting with LangGraph API" optional = false python-versions = "<4.0.0,>=3.9.0" +groups = ["main"] files = [ {file = "langgraph_sdk-0.1.51-py3-none-any.whl", hash = "sha256:ce2b58466d1700d06149782ed113157a8694a6d7932c801f316cd13fab315fe4"}, {file = "langgraph_sdk-0.1.51.tar.gz", hash = "sha256:dea1363e72562cb1e82a2d156be8d5b1a69ff3fe8815eee0e1e7a2f423242ec1"}, @@ -815,6 +857,7 @@ version = "0.3.8" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = "<4.0,>=3.9" +groups = ["main", "dev"] files = [ {file = "langsmith-0.3.8-py3-none-any.whl", hash = "sha256:fbb9dd97b0f090219447fca9362698d07abaeda1da85aa7cc6ec6517b36581b1"}, {file = "langsmith-0.3.8.tar.gz", hash = "sha256:97f9bebe0b7cb0a4f278e6ff30ae7d5ededff3883b014442ec6d7d575b02a0f1"}, @@ -841,6 +884,7 @@ version = "0.4.1" description = "" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "ml_dtypes-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1fe8b5b5e70cd67211db94b05cfd58dace592f24489b038dc6f9fe347d2e07d5"}, {file = "ml_dtypes-0.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c09a6d11d8475c2a9fd2bc0695628aec105f97cab3b3a3fb7c9660348ff7d24"}, @@ -863,10 +907,10 @@ files = [ [package.dependencies] numpy = [ - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, {version = ">=1.23.3", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, {version = ">=1.21.2", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, {version = ">1.20", markers = "python_version < \"3.10\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] [package.extras] @@ -878,6 +922,7 @@ version = "1.1.0" description = "MessagePack serializer" optional = false python-versions = ">=3.8" +groups = ["main"] 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"}, @@ -951,6 +996,7 @@ version = "1.15.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"}, {file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"}, @@ -1004,17 +1050,67 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" +groups = ["dev"] 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 = "numpy" +version = "1.26.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version < \"3.12\"" +files = [ + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, +] + [[package]] name = "numpy" version = "2.0.2" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" +groups = ["main"] +markers = "python_version >= \"3.12\"" files = [ {file = "numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece"}, {file = "numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04"}, @@ -1069,6 +1165,7 @@ version = "1.61.1" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "openai-1.61.1-py3-none-any.whl", hash = "sha256:72b0826240ce26026ac2cd17951691f046e5be82ad122d20a8e1b30ca18bd11e"}, {file = "openai-1.61.1.tar.gz", hash = "sha256:ce1851507218209961f89f3520e06726c0aa7d0512386f0f977e3ac3e4f2472e"}, @@ -1094,6 +1191,7 @@ version = "3.10.15" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "orjson-3.10.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:552c883d03ad185f720d0c09583ebde257e41b9521b74ff40e08b7dec4559c04"}, {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e3e8d438d02e4854f70bfdc03a6bcdb697358dbaa6bcd19cbe24d24ece1f8"}, @@ -1175,6 +1273,7 @@ files = [ {file = "orjson-3.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:efcf6c735c3d22ef60c4aa27a5238f1a477df85e9b15f2142f9d669beb2d13fd"}, {file = "orjson-3.10.15.tar.gz", hash = "sha256:05ca7fe452a2e9d8d9d706a2984c95b9c2ebc5db417ce0b7a49b91d50642a23e"}, ] +markers = {dev = "platform_python_implementation != \"PyPy\""} [[package]] name = "packaging" @@ -1182,6 +1281,7 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -1193,6 +1293,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1204,6 +1305,7 @@ 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" +groups = ["dev"] files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -1220,6 +1322,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -1235,6 +1338,7 @@ version = "6.1.1" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["dev"] files = [ {file = "psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8"}, {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777"}, @@ -1265,10 +1369,12 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] +markers = {main = "platform_python_implementation == \"PyPy\""} [[package]] name = "pydantic" @@ -1276,6 +1382,7 @@ version = "2.10.6" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, @@ -1296,6 +1403,7 @@ version = "2.27.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, @@ -1408,6 +1516,8 @@ version = "3.5.4" description = "A python implementation of GNU readline." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "sys_platform == \"win32\"" files = [ {file = "pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6"}, {file = "pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7"}, @@ -1422,6 +1532,7 @@ version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, @@ -1444,6 +1555,7 @@ version = "0.21.2" description = "Pytest support for asyncio" optional = false python-versions = ">=3.7" +groups = ["dev"] 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"}, @@ -1462,6 +1574,7 @@ version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" +groups = ["dev"] 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"}, @@ -1479,6 +1592,7 @@ version = "3.6.1" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, @@ -1500,6 +1614,7 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -1514,6 +1629,7 @@ version = "3.0.0" description = "Universally unique lexicographically sortable identifier" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "python_ulid-3.0.0-py3-none-any.whl", hash = "sha256:e4c4942ff50dbd79167ad01ac725ec58f924b4018025ce22c858bfcff99a5e31"}, {file = "python_ulid-3.0.0.tar.gz", hash = "sha256:e50296a47dc8209d28629a22fc81ca26c00982c78934bd7766377ba37ea49a9f"}, @@ -1528,6 +1644,8 @@ version = "308" description = "Python for Window Extensions" optional = false python-versions = "*" +groups = ["dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, @@ -1555,6 +1673,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] 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"}, @@ -1617,6 +1736,7 @@ version = "5.2.1" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"}, {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"}, @@ -1631,32 +1751,37 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)" [[package]] name = "redisvl" -version = "0.3.9" +version = "0.4.1" description = "Python client library and CLI for using Redis as a vector database" optional = false -python-versions = "<3.13,>=3.9" +python-versions = "<3.14,>=3.9" +groups = ["main"] files = [ - {file = "redisvl-0.3.9-py3-none-any.whl", hash = "sha256:7607c9d6a449b229b07350e54b22fb1b8de21bdadf440f4c60ad8748229a4a77"}, - {file = "redisvl-0.3.9.tar.gz", hash = "sha256:6508579f887be95555542a0dbf5759a87bfe2cbd57a48ce978e0460f78665302"}, + {file = "redisvl-0.4.1-py3-none-any.whl", hash = "sha256:6db5d5bc95b1fe8032a1cdae74ce1c65bc7fe9054e5429b5d34d5a91d28bae5f"}, + {file = "redisvl-0.4.1.tar.gz", hash = "sha256:fd6a36426ba94792c0efca20915c31232d4ee3cc58eb23794a62c142696401e6"}, ] [package.dependencies] -coloredlogs = "*" +coloredlogs = ">=15.0,<16.0" ml-dtypes = ">=0.4.0,<0.5.0" -numpy = "*" +numpy = [ + {version = ">=1,<2", markers = "python_version < \"3.12\""}, + {version = ">=1.26.0,<3", markers = "python_version >= \"3.12\""}, +] pydantic = ">=2,<3" -pyyaml = "*" -redis = ">=5.0.0" -tabulate = ">=0.9.0,<1" +python-ulid = ">=3.0.0,<4.0.0" +pyyaml = ">=5.4,<7.0" +redis = ">=5.0,<6.0" +tabulate = ">=0.9.0,<0.10.0" tenacity = ">=8.2.2" [package.extras] -bedrock = ["boto3 (>=1.34.0)"] +bedrock = ["boto3[bedrock] (>=1.36.0,<2.0.0)"] cohere = ["cohere (>=4.44)"] mistralai = ["mistralai (>=1.0.0)"] -openai = ["openai (>=1.13.0)"] -sentence-transformers = ["sentence-transformers (>=2.2.2)"] -vertexai = ["google-cloud-aiplatform (>=1.26)", "protobuf (>=5.29.1,<6.0.0.dev0)"] +openai = ["openai (>=1.13.0,<2.0.0)"] +sentence-transformers = ["scipy (<1.15)", "scipy (>=1.15,<2.0)", "sentence-transformers (>=3.4.0,<4.0.0)"] +vertexai = ["google-cloud-aiplatform (>=1.26,<2.0)", "protobuf (>=5.29.1,<6.0.0)"] voyageai = ["voyageai (>=0.2.2)"] [[package]] @@ -1665,6 +1790,7 @@ version = "2024.11.6" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, @@ -1768,6 +1894,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -1789,6 +1916,7 @@ 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.*" +groups = ["main", "dev"] 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"}, @@ -1803,6 +1931,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -1814,6 +1943,7 @@ version = "0.9.0" description = "Pretty-print tabular data" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, @@ -1828,6 +1958,7 @@ version = "9.0.0" description = "Retry code until it succeeds" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, @@ -1843,6 +1974,7 @@ version = "4.9.1" description = "Python library for throwaway instances of anything that can run in a Docker container" optional = false python-versions = "<4.0,>=3.9" +groups = ["dev"] files = [ {file = "testcontainers-4.9.1-py3-none-any.whl", hash = "sha256:315fb94b42a383872df530aa45319745278ef0cc18b9cfcdc231a75d14afa5a0"}, {file = "testcontainers-4.9.1.tar.gz", hash = "sha256:37fe9a222549ddb788463935965b16f91809e9a8d654f437d6a59eac9b77f76f"}, @@ -1896,6 +2028,7 @@ version = "0.8.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "tiktoken-0.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b07e33283463089c81ef1467180e3e00ab00d46c2c4bbcef0acab5f771d6695e"}, {file = "tiktoken-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9269348cb650726f44dd3bbb3f9110ac19a8dcc8f54949ad3ef652ca22a38e21"}, @@ -1943,6 +2076,8 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -1984,6 +2119,7 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, @@ -2005,6 +2141,7 @@ version = "1.16.0.20241221" description = "Typing stubs for cffi" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "types_cffi-1.16.0.20241221-py3-none-any.whl", hash = "sha256:e5b76b4211d7a9185f6ab8d06a106d56c7eb80af7cdb8bfcb4186ade10fb112f"}, {file = "types_cffi-1.16.0.20241221.tar.gz", hash = "sha256:1c96649618f4b6145f58231acb976e0b448be6b847f7ab733dabe62dfbff6591"}, @@ -2019,6 +2156,7 @@ version = "24.1.0.20240722" description = "Typing stubs for pyOpenSSL" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "types-pyOpenSSL-24.1.0.20240722.tar.gz", hash = "sha256:47913b4678a01d879f503a12044468221ed8576263c1540dcb0484ca21b08c39"}, {file = "types_pyOpenSSL-24.1.0.20240722-py3-none-any.whl", hash = "sha256:6a7a5d2ec042537934cfb4c9d4deb0e16c4c6250b09358df1f083682fe6fda54"}, @@ -2034,6 +2172,7 @@ version = "4.6.0.20241004" description = "Typing stubs for redis" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "types-redis-4.6.0.20241004.tar.gz", hash = "sha256:5f17d2b3f9091ab75384153bfa276619ffa1cf6a38da60e10d5e6749cc5b902e"}, {file = "types_redis-4.6.0.20241004-py3-none-any.whl", hash = "sha256:ef5da68cb827e5f606c8f9c0b49eeee4c2669d6d97122f301d3a55dc6a63f6ed"}, @@ -2049,6 +2188,7 @@ version = "75.8.0.20250210" description = "Typing stubs for setuptools" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "types_setuptools-75.8.0.20250210-py3-none-any.whl", hash = "sha256:a217d7b4d59be04c29e23d142c959a0f85e71292fd3fc4313f016ca11f0b56dc"}, {file = "types_setuptools-75.8.0.20250210.tar.gz", hash = "sha256:c1547361b2441f07c94e25dce8a068e18c611593ad4b6fdd727b1a8f5d1fda33"}, @@ -2060,6 +2200,7 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] 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"}, @@ -2071,6 +2212,7 @@ version = "2.3.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, @@ -2088,6 +2230,7 @@ version = "1.17.2" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, @@ -2176,6 +2319,7 @@ version = "0.23.0" description = "Zstandard bindings for Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9"}, {file = "zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880"}, @@ -2283,6 +2427,6 @@ cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\ cffi = ["cffi (>=1.11)"] [metadata] -lock-version = "2.0" -python-versions = ">=3.9,<3.13" -content-hash = "4752616bd33f211c275fd830ceb231466ae138aff257a45136815a9d603919ce" +lock-version = "2.1" +python-versions = ">=3.9,<3.14" +content-hash = "48f023b89d979932c65ac68649cdc575169547158999e7d15679cafdb7f0b1a5" diff --git a/pyproject.toml b/pyproject.toml index 8c5eab1..fb4b2be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,14 +12,15 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "License :: OSI Approved :: MIT License", ] packages = [{ include = "langgraph" }] [tool.poetry.dependencies] -python = ">=3.9,<3.13" +python = ">=3.9,<3.14" langgraph-checkpoint = "^2.0.10" -redisvl = "^0.3.9" +redisvl = "^0.4.1" redis = "^5.2.1" python-ulid = "^3.0.0" langgraph = "^0.2.70" @@ -39,6 +40,7 @@ langchain-openai = "^0.3.2" testcontainers = "^4.9.1" langchain-anthropic = "^0.3.5" isort = "^6.0.0" +cryptography = { version = ">=44.0.1", markers = "python_version > '3.9.1'" } [tool.pytest.ini_options] # --strict-markers will raise errors on unknown marks. diff --git a/scripts.py b/scripts.py index 41bd737..49c90ef 100644 --- a/scripts.py +++ b/scripts.py @@ -1,4 +1,5 @@ import subprocess +import sys def format(): @@ -29,10 +30,18 @@ def check_mypy(): def test(): - subprocess.run(["python", "-m", "pytest", "-n", "auto", "--log-level=CRITICAL"], check=True) + test_cmd = ["python", "-m", "pytest", "-n", "auto", "--log-level=CRITICAL"] + # Get any extra arguments passed to the script + extra_args = sys.argv[1:] + if extra_args: + test_cmd.extend(extra_args) + subprocess.run(test_cmd, check=True) def test_verbose(): - subprocess.run( - ["python", "-m", "pytest", "-n", "auto", "-vv", "-s", "--log-level=CRITICAL"], check=True - ) + test_cmd = ["python", "-m", "pytest", "-n", "auto", "-vv", "-s", "--log-level=CRITICAL"] + # Get any extra arguments passed to the script + extra_args = sys.argv[1:] + if extra_args: + test_cmd.extend(extra_args) + subprocess.run(test_cmd, check=True) \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index 049e0fc..99d8580 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,4 @@ import os -import subprocess import pytest from redis.asyncio import Redis @@ -8,55 +7,28 @@ VECTOR_TYPES = ["vector", "halfvec"] -# try: -# from testcontainers.compose import DockerCompose - -# TESTCONTAINERS_AVAILABLE = True -# except ImportError: -# TESTCONTAINERS_AVAILABLE = False - -# if TESTCONTAINERS_AVAILABLE: - @pytest.fixture(autouse=True) def set_tokenizers_parallelism(): """Disable tokenizers parallelism in tests to avoid deadlocks""" os.environ["TOKENIZERS_PARALLELISM"] = "false" - # @pytest.fixture(scope="session", autouse=True) - # def redis_container() -> DockerCompose: - # # Set the default Redis version if not already set - # os.environ.setdefault("REDIS_VERSION", "edge") - - # try: - # compose = DockerCompose( - # "tests", compose_file_name="docker-compose.yml", pull=True - # ) - # compose.start() - - # redis_host, redis_port = compose.get_service_host_and_port("redis", 6379) - # redis_url = f"redis://{redis_host}:{redis_port}" - # os.environ["DEFAULT_REDIS_URI"] = redis_url - - # yield compose - - # compose.stop() - # except subprocess.CalledProcessError: - # yield None - @pytest.fixture(scope="session", autouse=True) def redis_container(request): """ - Create a unique Compose project for each xdist worker by setting - COMPOSE_PROJECT_NAME. That prevents collisions on container/volume names. + If using xdist, create a unique Compose project for each xdist worker by + setting COMPOSE_PROJECT_NAME. That prevents collisions on container/volume + names. """ # In xdist, the config has "workerid" in workerinput - worker_id = request.config.workerinput.get("workerid", "master") + workerinput = getattr(request.config, "workerinput", {}) + worker_id = workerinput.get("workerid", "master") # Set the Compose project name so containers do not clash across workers os.environ["COMPOSE_PROJECT_NAME"] = f"redis_test_{worker_id}" - os.environ.setdefault("REDIS_VERSION", "edge") + os.environ.setdefault("REDIS_VERSION", "latest") + os.environ.setdefault("REDIS_IMAGE", "redis/redis-stack-server:latest") compose = DockerCompose( context="tests", @@ -70,11 +42,6 @@ def redis_container(request): compose.stop() -# @pytest.fixture(scope="session") -# def redis_url() -> str: -# return os.getenv("DEFAULT_REDIS_URI", "redis://localhost:6379") - - @pytest.fixture(scope="session") def redis_url(redis_container): """ @@ -85,6 +52,15 @@ def redis_url(redis_container): return f"redis://{host}:{port}" +@pytest.fixture +async def async_client(redis_url): + """ + An async Redis client that uses the dynamic `redis_url`. + """ + async with await RedisConnectionFactory._get_aredis_connection(redis_url) as client: + yield client + + @pytest.fixture def client(redis_url): """ diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index e8648df..1441e30 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -1,7 +1,7 @@ version: "3.9" services: redis: - image: "redis/redis-stack:${REDIS_VERSION}" + image: "${REDIS_IMAGE}" ports: - "6379" environment: diff --git a/tests/test_async.py b/tests/test_async.py index 5e94976..9092cdd 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -287,24 +287,26 @@ async def test_from_conn_string_errors() -> None: with pytest.raises( ValueError, match="Either redis_url or redis_client must be provided" ): - async with AsyncRedisSaver.from_conn_string() as _: - pass + async with AsyncRedisSaver.from_conn_string() as saver: + await saver.asetup() # Test with empty URL - should fail with pytest.raises(ValueError, match="REDIS_URL env var not set"): - async with AsyncRedisSaver.from_conn_string("") as _: - pass + async with AsyncRedisSaver.from_conn_string("") as saver: + await saver.asetup() # Test with invalid connection URL with pytest.raises(RedisConnectionError): - async with AsyncRedisSaver.from_conn_string("redis://nonexistent:6379") as _: - await _.asetup() # Force connection attempt + async with AsyncRedisSaver.from_conn_string( + "redis://nonexistent:6379" + ) as saver: + await saver.asetup() # Test with non-functional client client = Redis.from_url("redis://nonexistent:6379") with pytest.raises(RedisConnectionError): - async with AsyncRedisSaver.from_conn_string(redis_client=client) as _: - await _.asetup() # Force connection attempt + async with AsyncRedisSaver.from_conn_string(redis_client=client) as saver: + await saver.asetup() @pytest.mark.asyncio diff --git a/tests/test_async_store.py b/tests/test_async_store.py index b1b98e6..f4bd311 100644 --- a/tests/test_async_store.py +++ b/tests/test_async_store.py @@ -7,10 +7,6 @@ from langchain_core.messages import BaseMessage, HumanMessage from langchain_core.runnables import RunnableConfig from langchain_openai import OpenAIEmbeddings -from redis.asyncio import Redis -from ulid import ULID - -from langgraph.checkpoint.redis import AsyncRedisSaver from langgraph.constants import START from langgraph.graph import MessagesState, StateGraph from langgraph.store.base import ( @@ -26,6 +22,7 @@ SearchOp, ) from redis.asyncio import Redis +from ulid import ULID from langgraph.checkpoint.redis import AsyncRedisSaver from langgraph.store.redis import AsyncRedisStore @@ -58,7 +55,7 @@ async def store(redis_url: str) -> AsyncGenerator[AsyncRedisStore, None]: yield store finally: if store: - if store._owns_client: + if store._owns_its_client: await store._redis.aclose() # type: ignore[attr-defined] await store._redis.connection_pool.disconnect() diff --git a/tests/test_shallow_async.py b/tests/test_shallow_async.py index 9f5626c..7c33dff 100644 --- a/tests/test_shallow_async.py +++ b/tests/test_shallow_async.py @@ -8,6 +8,8 @@ create_checkpoint, empty_checkpoint, ) +from redis.asyncio import Redis +from redis.exceptions import ConnectionError as RedisConnectionError from langgraph.checkpoint.redis.ashallow import AsyncShallowRedisSaver @@ -208,3 +210,50 @@ async def test_sequential_writes( assert result.pending_writes is not None assert len(result.pending_writes) == 1 assert result.pending_writes[0] == ("task1", "channel2", "value2") + + +@pytest.mark.asyncio +async def test_from_conn_string_errors(redis_url: str) -> None: + """Test proper cleanup of Redis connections.""" + async with AsyncShallowRedisSaver.from_conn_string(redis_url) as s: + saver_redis = s._redis + assert await saver_redis.ping() + + client = Redis.from_url(redis_url) + try: + async with AsyncShallowRedisSaver.from_conn_string( + redis_client=client + ) as saver: + assert saver._redis is client + assert await saver._redis.ping() + assert await client.ping() + finally: + await client.close() + + """Test error conditions for from_conn_string.""" + # Test with neither URL nor client provided + with pytest.raises( + ValueError, match="Either redis_url or redis_client must be provided" + ): + async with AsyncShallowRedisSaver.from_conn_string() as saver: + await saver.asetup() + + # Test with invalid connection URL + with pytest.raises(RedisConnectionError): + async with AsyncShallowRedisSaver.from_conn_string( + "redis://nonexistent:6379" + ) as saver: + await saver.asetup() + + # Test with non-responding client + client = Redis(host="nonexistent", port=6379) + with pytest.raises(RedisConnectionError): + async with AsyncShallowRedisSaver.from_conn_string( + redis_client=client + ) as saver: + await saver.asetup() + + # Test with empty URL + with pytest.raises(ValueError, match="REDIS_URL env var not set"): + async with AsyncShallowRedisSaver.from_conn_string("") as saver: + await saver.asetup() diff --git a/tests/test_shallow_sync.py b/tests/test_shallow_sync.py index 048944f..2380831 100644 --- a/tests/test_shallow_sync.py +++ b/tests/test_shallow_sync.py @@ -254,21 +254,21 @@ def test_from_conn_string_errors(redis_url: str) -> None: with pytest.raises( ValueError, match="Either redis_url or redis_client must be provided" ): - with ShallowRedisSaver.from_conn_string() as _: - pass + with ShallowRedisSaver.from_conn_string() as saver: + saver.setup() # Test with invalid connection URL with pytest.raises(RedisConnectionError): - with ShallowRedisSaver.from_conn_string("redis://nonexistent:6379") as _: - pass + with ShallowRedisSaver.from_conn_string("redis://nonexistent:6379") as saver: + saver.setup() # Test with non-responding client client = Redis(host="nonexistent", port=6379) with pytest.raises(RedisConnectionError): - with ShallowRedisSaver.from_conn_string(redis_client=client) as _: - pass + with ShallowRedisSaver.from_conn_string(redis_client=client) as saver: + saver.setup() # Test with empty URL with pytest.raises(ValueError, match="REDIS_URL env var not set"): - with ShallowRedisSaver.from_conn_string("") as _: - pass + with ShallowRedisSaver.from_conn_string("") as saver: + saver.setup() diff --git a/tests/test_store.py b/tests/test_store.py index e47e958..199ad15 100644 --- a/tests/test_store.py +++ b/tests/test_store.py @@ -5,10 +5,6 @@ from langchain_core.messages import BaseMessage, HumanMessage from langchain_core.runnables import RunnableConfig from langchain_openai import OpenAIEmbeddings -from redis import Redis -from ulid import ULID - -from langgraph.checkpoint.redis import RedisSaver from langgraph.graph import START, MessagesState, StateGraph from langgraph.store.base import ( BaseStore, @@ -22,6 +18,10 @@ SearchItem, SearchOp, ) +from redis import Redis +from ulid import ULID + +from langgraph.checkpoint.redis import RedisSaver from langgraph.store.redis import RedisStore from tests.conftest import VECTOR_TYPES from tests.embed_test_utils import CharacterEmbeddings diff --git a/tests/test_sync.py b/tests/test_sync.py index 9e4b0a4..7027a3c 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -8,9 +8,6 @@ from langchain_core.tools import tool from langchain_core.tools.base import BaseTool from langchain_openai import ChatOpenAI -from redis import Redis -from redis.exceptions import ConnectionError as RedisConnectionError - from langgraph.checkpoint.base import ( WRITES_IDX_MAP, Checkpoint, @@ -18,8 +15,11 @@ create_checkpoint, empty_checkpoint, ) -from langgraph.checkpoint.redis import BaseRedisSaver, RedisSaver from langgraph.prebuilt import create_react_agent +from redis import Redis +from redis.exceptions import ConnectionError as RedisConnectionError + +from langgraph.checkpoint.redis import BaseRedisSaver, RedisSaver @pytest.fixture @@ -337,24 +337,24 @@ def test_from_conn_string_errors() -> None: with pytest.raises( ValueError, match="Either redis_url or redis_client must be provided" ): - with RedisSaver.from_conn_string() as _: - pass + with RedisSaver.from_conn_string() as saver: + saver.setup() # Test with empty URL with pytest.raises(ValueError, match="REDIS_URL env var not set"): - with RedisSaver.from_conn_string("") as _: - pass + with RedisSaver.from_conn_string("") as saver: + saver.setup() # Test with invalid connection URL with pytest.raises(RedisConnectionError): - with RedisSaver.from_conn_string("redis://nonexistent:6379") as _: - pass + with RedisSaver.from_conn_string("redis://nonexistent:6379") as saver: + saver.setup() # Test with non-responding client client = Redis(host="nonexistent", port=6379) with pytest.raises(RedisConnectionError): - with RedisSaver.from_conn_string(redis_client=client) as _: - pass + with RedisSaver.from_conn_string(redis_client=client) as saver: + saver.setup() def test_large_batches(test_data: dict[str, Any], redis_url: str) -> None: From 7d48f33d04de9ceb125cb83ae92e1fbd9f2c8060 Mon Sep 17 00:00:00 2001 From: Tyler Hutcherson Date: Thu, 27 Feb 2025 09:26:59 -0500 Subject: [PATCH 2/5] wip on key sep --- langgraph/checkpoint/redis/__init__.py | 2 +- langgraph/checkpoint/redis/ashallow.py | 9 ++++++--- langgraph/checkpoint/redis/base.py | 9 ++++++--- langgraph/checkpoint/redis/shallow.py | 9 ++++++--- langgraph/store/redis/__init__.py | 4 ++-- langgraph/store/redis/base.py | 6 ++++-- 6 files changed, 25 insertions(+), 14 deletions(-) diff --git a/langgraph/checkpoint/redis/__init__.py b/langgraph/checkpoint/redis/__init__.py index ae9d505..daebf3a 100644 --- a/langgraph/checkpoint/redis/__init__.py +++ b/langgraph/checkpoint/redis/__init__.py @@ -303,7 +303,7 @@ def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]: # Fetch pending_sends from parent checkpoint pending_sends = [] - if doc["parent_checkpoint_id"]: + if "parent_checkpoint_id" in doc: pending_sends = self._load_pending_sends( thread_id=thread_id, checkpoint_ns=checkpoint_ns, diff --git a/langgraph/checkpoint/redis/ashallow.py b/langgraph/checkpoint/redis/ashallow.py index 377f43c..86606dd 100644 --- a/langgraph/checkpoint/redis/ashallow.py +++ b/langgraph/checkpoint/redis/ashallow.py @@ -38,7 +38,8 @@ { "index": { "name": "checkpoints", - "prefix": CHECKPOINT_PREFIX + REDIS_KEY_SEPARATOR, + "prefix": CHECKPOINT_PREFIX, + "key_separator": REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ @@ -51,7 +52,8 @@ { "index": { "name": "checkpoints_blobs", - "prefix": CHECKPOINT_BLOB_PREFIX + REDIS_KEY_SEPARATOR, + "prefix": CHECKPOINT_BLOB_PREFIX, + "key_separator": REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ @@ -64,7 +66,8 @@ { "index": { "name": "checkpoint_writes", - "prefix": CHECKPOINT_WRITE_PREFIX + REDIS_KEY_SEPARATOR, + "prefix": CHECKPOINT_WRITE_PREFIX, + "key_separator": REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ diff --git a/langgraph/checkpoint/redis/base.py b/langgraph/checkpoint/redis/base.py index fbc424a..3552c24 100644 --- a/langgraph/checkpoint/redis/base.py +++ b/langgraph/checkpoint/redis/base.py @@ -31,7 +31,8 @@ { "index": { "name": "checkpoints", - "prefix": CHECKPOINT_PREFIX + REDIS_KEY_SEPARATOR, + "prefix": CHECKPOINT_PREFIX, + "key_separator": REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ @@ -46,7 +47,8 @@ { "index": { "name": "checkpoints_blobs", - "prefix": CHECKPOINT_BLOB_PREFIX + REDIS_KEY_SEPARATOR, + "prefix": CHECKPOINT_BLOB_PREFIX, + "key_separator": REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ @@ -60,7 +62,8 @@ { "index": { "name": "checkpoint_writes", - "prefix": CHECKPOINT_WRITE_PREFIX + REDIS_KEY_SEPARATOR, + "prefix": CHECKPOINT_WRITE_PREFIX, + "key_separator": REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ diff --git a/langgraph/checkpoint/redis/shallow.py b/langgraph/checkpoint/redis/shallow.py index 7345462..fe480e2 100644 --- a/langgraph/checkpoint/redis/shallow.py +++ b/langgraph/checkpoint/redis/shallow.py @@ -31,7 +31,8 @@ { "index": { "name": "checkpoints", - "prefix": CHECKPOINT_PREFIX + REDIS_KEY_SEPARATOR, + "prefix": CHECKPOINT_PREFIX, + "key_separator": REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ @@ -44,7 +45,8 @@ { "index": { "name": "checkpoints_blobs", - "prefix": CHECKPOINT_BLOB_PREFIX + REDIS_KEY_SEPARATOR, + "prefix": CHECKPOINT_BLOB_PREFIX, + "key_separator": REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ @@ -57,7 +59,8 @@ { "index": { "name": "checkpoint_writes", - "prefix": CHECKPOINT_WRITE_PREFIX + REDIS_KEY_SEPARATOR, + "prefix": CHECKPOINT_WRITE_PREFIX, + "key_separator": REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ diff --git a/langgraph/store/redis/__init__.py b/langgraph/store/redis/__init__.py index 6faa0d6..f88f42b 100644 --- a/langgraph/store/redis/__init__.py +++ b/langgraph/store/redis/__init__.py @@ -25,7 +25,7 @@ from redisvl.query import FilterQuery, VectorQuery from redisvl.redis.connection import RedisConnectionFactory from redisvl.utils.token_escaper import TokenEscaper -from ulid import ULID +from redisvl.utils.utils import create_ulid from langgraph.store.redis.aio import AsyncRedisStore from langgraph.store.redis.base import ( @@ -223,7 +223,7 @@ def _batch_put_ops( # Generate IDs for PUT operations for _, op in put_ops: if op.value is not None: - generated_doc_id = str(ULID()) + generated_doc_id = create_ulid() namespace = _namespace_to_text(op.namespace) doc_ids[(namespace, op.key)] = generated_doc_id diff --git a/langgraph/store/redis/base.py b/langgraph/store/redis/base.py index cfb8e8f..a3b6115 100644 --- a/langgraph/store/redis/base.py +++ b/langgraph/store/redis/base.py @@ -44,7 +44,8 @@ { "index": { "name": "store", - "prefix": STORE_PREFIX + REDIS_KEY_SEPARATOR, + "prefix": STORE_PREFIX, + "key_separator": REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ @@ -57,7 +58,8 @@ { "index": { "name": "store_vectors", - "prefix": STORE_VECTOR_PREFIX + REDIS_KEY_SEPARATOR, + "prefix": STORE_VECTOR_PREFIX, + "key_separator": REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ From 1bf74149f3e0338c98dd1f6f7c4e05ff21fe184b Mon Sep 17 00:00:00 2001 From: Tyler Hutcherson Date: Thu, 27 Feb 2025 09:44:51 -0500 Subject: [PATCH 3/5] Revert "wip on key sep" This reverts commit 7d48f33d04de9ceb125cb83ae92e1fbd9f2c8060. --- langgraph/checkpoint/redis/__init__.py | 2 +- langgraph/checkpoint/redis/ashallow.py | 9 +++------ langgraph/checkpoint/redis/base.py | 9 +++------ langgraph/checkpoint/redis/shallow.py | 9 +++------ langgraph/store/redis/__init__.py | 4 ++-- langgraph/store/redis/base.py | 6 ++---- 6 files changed, 14 insertions(+), 25 deletions(-) diff --git a/langgraph/checkpoint/redis/__init__.py b/langgraph/checkpoint/redis/__init__.py index daebf3a..ae9d505 100644 --- a/langgraph/checkpoint/redis/__init__.py +++ b/langgraph/checkpoint/redis/__init__.py @@ -303,7 +303,7 @@ def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]: # Fetch pending_sends from parent checkpoint pending_sends = [] - if "parent_checkpoint_id" in doc: + if doc["parent_checkpoint_id"]: pending_sends = self._load_pending_sends( thread_id=thread_id, checkpoint_ns=checkpoint_ns, diff --git a/langgraph/checkpoint/redis/ashallow.py b/langgraph/checkpoint/redis/ashallow.py index 86606dd..377f43c 100644 --- a/langgraph/checkpoint/redis/ashallow.py +++ b/langgraph/checkpoint/redis/ashallow.py @@ -38,8 +38,7 @@ { "index": { "name": "checkpoints", - "prefix": CHECKPOINT_PREFIX, - "key_separator": REDIS_KEY_SEPARATOR, + "prefix": CHECKPOINT_PREFIX + REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ @@ -52,8 +51,7 @@ { "index": { "name": "checkpoints_blobs", - "prefix": CHECKPOINT_BLOB_PREFIX, - "key_separator": REDIS_KEY_SEPARATOR, + "prefix": CHECKPOINT_BLOB_PREFIX + REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ @@ -66,8 +64,7 @@ { "index": { "name": "checkpoint_writes", - "prefix": CHECKPOINT_WRITE_PREFIX, - "key_separator": REDIS_KEY_SEPARATOR, + "prefix": CHECKPOINT_WRITE_PREFIX + REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ diff --git a/langgraph/checkpoint/redis/base.py b/langgraph/checkpoint/redis/base.py index 3552c24..fbc424a 100644 --- a/langgraph/checkpoint/redis/base.py +++ b/langgraph/checkpoint/redis/base.py @@ -31,8 +31,7 @@ { "index": { "name": "checkpoints", - "prefix": CHECKPOINT_PREFIX, - "key_separator": REDIS_KEY_SEPARATOR, + "prefix": CHECKPOINT_PREFIX + REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ @@ -47,8 +46,7 @@ { "index": { "name": "checkpoints_blobs", - "prefix": CHECKPOINT_BLOB_PREFIX, - "key_separator": REDIS_KEY_SEPARATOR, + "prefix": CHECKPOINT_BLOB_PREFIX + REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ @@ -62,8 +60,7 @@ { "index": { "name": "checkpoint_writes", - "prefix": CHECKPOINT_WRITE_PREFIX, - "key_separator": REDIS_KEY_SEPARATOR, + "prefix": CHECKPOINT_WRITE_PREFIX + REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ diff --git a/langgraph/checkpoint/redis/shallow.py b/langgraph/checkpoint/redis/shallow.py index fe480e2..7345462 100644 --- a/langgraph/checkpoint/redis/shallow.py +++ b/langgraph/checkpoint/redis/shallow.py @@ -31,8 +31,7 @@ { "index": { "name": "checkpoints", - "prefix": CHECKPOINT_PREFIX, - "key_separator": REDIS_KEY_SEPARATOR, + "prefix": CHECKPOINT_PREFIX + REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ @@ -45,8 +44,7 @@ { "index": { "name": "checkpoints_blobs", - "prefix": CHECKPOINT_BLOB_PREFIX, - "key_separator": REDIS_KEY_SEPARATOR, + "prefix": CHECKPOINT_BLOB_PREFIX + REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ @@ -59,8 +57,7 @@ { "index": { "name": "checkpoint_writes", - "prefix": CHECKPOINT_WRITE_PREFIX, - "key_separator": REDIS_KEY_SEPARATOR, + "prefix": CHECKPOINT_WRITE_PREFIX + REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ diff --git a/langgraph/store/redis/__init__.py b/langgraph/store/redis/__init__.py index f88f42b..6faa0d6 100644 --- a/langgraph/store/redis/__init__.py +++ b/langgraph/store/redis/__init__.py @@ -25,7 +25,7 @@ from redisvl.query import FilterQuery, VectorQuery from redisvl.redis.connection import RedisConnectionFactory from redisvl.utils.token_escaper import TokenEscaper -from redisvl.utils.utils import create_ulid +from ulid import ULID from langgraph.store.redis.aio import AsyncRedisStore from langgraph.store.redis.base import ( @@ -223,7 +223,7 @@ def _batch_put_ops( # Generate IDs for PUT operations for _, op in put_ops: if op.value is not None: - generated_doc_id = create_ulid() + generated_doc_id = str(ULID()) namespace = _namespace_to_text(op.namespace) doc_ids[(namespace, op.key)] = generated_doc_id diff --git a/langgraph/store/redis/base.py b/langgraph/store/redis/base.py index a3b6115..cfb8e8f 100644 --- a/langgraph/store/redis/base.py +++ b/langgraph/store/redis/base.py @@ -44,8 +44,7 @@ { "index": { "name": "store", - "prefix": STORE_PREFIX, - "key_separator": REDIS_KEY_SEPARATOR, + "prefix": STORE_PREFIX + REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ @@ -58,8 +57,7 @@ { "index": { "name": "store_vectors", - "prefix": STORE_VECTOR_PREFIX, - "key_separator": REDIS_KEY_SEPARATOR, + "prefix": STORE_VECTOR_PREFIX + REDIS_KEY_SEPARATOR, "storage_type": "json", }, "fields": [ From 46c3025e44c9e85b06f96470d2f45eebeabd7f31 Mon Sep 17 00:00:00 2001 From: Tyler Hutcherson Date: Thu, 27 Feb 2025 16:03:54 -0500 Subject: [PATCH 4/5] remove anthropic from package contents and tests --- poetry.lock | 44 +-------------------------------------- pyproject.toml | 1 - tests/test_async_store.py | 7 +++---- tests/test_store.py | 8 +++---- 4 files changed, 8 insertions(+), 52 deletions(-) diff --git a/poetry.lock b/poetry.lock index f90d39e..7ffc4cb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -27,31 +27,6 @@ files = [ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] -[[package]] -name = "anthropic" -version = "0.45.2" -description = "The official Python library for the anthropic API" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "anthropic-0.45.2-py3-none-any.whl", hash = "sha256:ecd746f7274451dfcb7e1180571ead624c7e1195d1d46cb7c70143d2aedb4d35"}, - {file = "anthropic-0.45.2.tar.gz", hash = "sha256:32a18b9ecd12c91b2be4cae6ca2ab46a06937b5aa01b21308d97a6d29794fb5e"}, -] - -[package.dependencies] -anyio = ">=3.5.0,<5" -distro = ">=1.7.0,<2" -httpx = ">=0.23.0,<1" -jiter = ">=0.4.0,<1" -pydantic = ">=1.9.0,<3" -sniffio = "*" -typing-extensions = ">=4.10,<5" - -[package.extras] -bedrock = ["boto3 (>=1.28.57)", "botocore (>=1.31.57)"] -vertex = ["google-auth (>=2,<3)"] - [[package]] name = "anyio" version = "4.8.0" @@ -744,23 +719,6 @@ files = [ {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, ] -[[package]] -name = "langchain-anthropic" -version = "0.3.7" -description = "An integration package connecting AnthropicMessages and LangChain" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["dev"] -files = [ - {file = "langchain_anthropic-0.3.7-py3-none-any.whl", hash = "sha256:adec0a1daabd3c25249753c6cd625654917fb9e3feee68e72c7dc3f4449c0f3c"}, - {file = "langchain_anthropic-0.3.7.tar.gz", hash = "sha256:534cd1867bc41711cd8c3d0a0bc055e6c5a4215953c87260209a90dc5816f30d"}, -] - -[package.dependencies] -anthropic = ">=0.45.0,<1" -langchain-core = ">=0.3.34,<1.0.0" -pydantic = ">=2.7.4,<3.0.0" - [[package]] name = "langchain-core" version = "0.3.34" @@ -2429,4 +2387,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<3.14" -content-hash = "48f023b89d979932c65ac68649cdc575169547158999e7d15679cafdb7f0b1a5" +content-hash = "3e9cf7a6636e1184e8200b2ea748e45410e616e162c755a295867cc4136581f1" diff --git a/pyproject.toml b/pyproject.toml index fb4b2be..57c5b7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,6 @@ types-redis = "^4.6.0.20241004" aioconsole = "^0.8.1" langchain-openai = "^0.3.2" testcontainers = "^4.9.1" -langchain-anthropic = "^0.3.5" isort = "^6.0.0" cryptography = { version = ">=44.0.1", markers = "python_version > '3.9.1'" } diff --git a/tests/test_async_store.py b/tests/test_async_store.py index f4bd311..043cc42 100644 --- a/tests/test_async_store.py +++ b/tests/test_async_store.py @@ -3,10 +3,9 @@ from typing import Any, AsyncGenerator, Dict, Sequence, cast import pytest -from langchain_anthropic import ChatAnthropic from langchain_core.messages import BaseMessage, HumanMessage from langchain_core.runnables import RunnableConfig -from langchain_openai import OpenAIEmbeddings +from langchain_openai import ChatOpenAI, OpenAIEmbeddings from langgraph.constants import START from langgraph.graph import MessagesState, StateGraph from langgraph.store.base import ( @@ -511,7 +510,7 @@ async def test_async_store_with_memory_persistence( await store.setup() await checkpointer.asetup() - model = ChatAnthropic(model="claude-3-5-sonnet-20240620") # type: ignore[call-arg] + model = ChatOpenAI(model="gpt-4o-2024-08-06", temperature=0) # type: ignore[call-arg] def call_model( state: MessagesState, config: RunnableConfig, *, store: BaseStore @@ -549,7 +548,7 @@ def call_model( input_message = HumanMessage(content="Hi! Remember: my name is Bob") response = await graph.ainvoke({"messages": [input_message]}, config) - assert "I'll remember that your name is Bob" in response["messages"][1].content + assert "Hi Bob" in response["messages"][1].content # Test 2: inspect the Redis store and verify that we have in fact saved the memories for the user memories = await store.asearch(("memories", "1")) diff --git a/tests/test_store.py b/tests/test_store.py index 199ad15..aa0e9f6 100644 --- a/tests/test_store.py +++ b/tests/test_store.py @@ -1,10 +1,10 @@ from typing import Any, Dict, Sequence, cast import pytest -from langchain_anthropic import ChatAnthropic from langchain_core.messages import BaseMessage, HumanMessage from langchain_core.runnables import RunnableConfig -from langchain_openai import OpenAIEmbeddings +from langchain_openai import ChatOpenAI, OpenAIEmbeddings + from langgraph.graph import START, MessagesState, StateGraph from langgraph.store.base import ( BaseStore, @@ -447,7 +447,7 @@ def test_store_with_memory_persistence(redis_url: str) -> None: with RedisStore.from_conn_string(redis_url, index=index_config) as store: store.setup() - model = ChatAnthropic(model="claude-3-5-sonnet-20240620") # type: ignore[call-arg] + model = ChatOpenAI(model="gpt-4o-2024-08-06", temperature=0) def call_model( state: MessagesState, config: RunnableConfig, *, store: BaseStore @@ -490,7 +490,7 @@ def call_model( input_message = HumanMessage(content="Hi! Remember: my name is Bob") response = graph.invoke({"messages": [input_message]}, config) - assert "I'll remember that your name is Bob" in response["messages"][1].content + assert "Hi Bob" in response["messages"][1].content # Test 2: inspect the Redis store and verify that we have in fact saved the memories for the user for memory in store.search(("memories", "1")): From 10a098fae301ac21d0d2570afec9271dfc69bb1f Mon Sep 17 00:00:00 2001 From: Tyler Hutcherson Date: Fri, 28 Feb 2025 09:39:37 -0500 Subject: [PATCH 5/5] add back python 3.10 and 3.12 in the testing matrix --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3cc7ef4..61b1355 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9, 3.11, 3.13] + python-version: [3.9, '3.10', 3.11, 3.12, 3.13] redis-version: ['6.2.6-v9', 'latest', '8.0-M03'] steps: