From e13261ac0ac11bf6581b0ef6e09818ec5ecd07fe Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Tue, 7 Jan 2025 20:09:57 -0500 Subject: [PATCH] ci: update langsmith and patch urllib3 vcr issues for notebook runner (#2949) --- docs/_scripts/_patch.py | 93 ++++++++++++++++++++++ docs/_scripts/prepare_notebooks_for_ci.py | 17 +++- poetry.lock | 95 +++++++++++++---------- pyproject.toml | 2 +- 4 files changed, 164 insertions(+), 43 deletions(-) create mode 100644 docs/_scripts/_patch.py diff --git a/docs/_scripts/_patch.py b/docs/_scripts/_patch.py new file mode 100644 index 000000000..4b8dd3a8c --- /dev/null +++ b/docs/_scripts/_patch.py @@ -0,0 +1,93 @@ +import functools + +from urllib3 import __version__ as urllib3version # type: ignore[import-untyped] +from urllib3 import connection # type: ignore[import-untyped] + + +def _ensure_str(s, encoding="utf-8", errors="strict") -> str: + if isinstance(s, str): + return s + + if isinstance(s, bytes): + return s.decode(encoding, errors) + return str(s) + + +# Copied from https://github.com/urllib3/urllib3/blob/1c994dfc8c5d5ecaee8ed3eb585d4785f5febf6e/src/urllib3/connection.py#L231 +def request(self, method, url, body=None, headers=None): + """Make the request. + + This function is based on the urllib3 request method, with modifications + to handle potential issues when using vcrpy in concurrent workloads. + + Args: + self: The HTTPConnection instance. + method (str): The HTTP method (e.g., 'GET', 'POST'). + url (str): The URL for the request. + body (Optional[Any]): The body of the request. + headers (Optional[dict]): Headers to send with the request. + + Returns: + The result of calling the parent request method. + """ + # Update the inner socket's timeout value to send the request. + # This only triggers if the connection is re-used. + if getattr(self, "sock", None) is not None: + self.sock.settimeout(self.timeout) + + if headers is None: + headers = {} + else: + # Avoid modifying the headers passed into .request() + headers = headers.copy() + if "user-agent" not in (_ensure_str(k.lower()) for k in headers): + headers["User-Agent"] = connection._get_default_user_agent() + # The above is all the same ^^^ + # The following is different: + return self._parent_request(method, url, body=body, headers=headers) + + +_PATCHED = False + + +def patch_urllib3(): + """Patch the request method of urllib3 to avoid type errors when using vcrpy. + + In concurrent workloads (such as the tracing background queue), the + connection pool can get in a state where an HTTPConnection is created + before vcrpy patches the HTTPConnection class. In urllib3 >= 2.0 this isn't + a problem since they use the proper super().request(...) syntax, but in older + versions, super(HTTPConnection, self).request is used, resulting in a TypeError + since self is no longer a subclass of "HTTPConnection" (which at this point + is vcr.stubs.VCRConnection). + + This method patches the class to fix the super() syntax to avoid mixed inheritance. + In the case of the LangSmith tracing logic, it doesn't really matter since we always + exclude cache checks for calls to LangSmith. + + The patch is only applied for urllib3 versions older than 2.0. + """ + global _PATCHED + if _PATCHED: + return + from packaging import version + + if version.parse(urllib3version) >= version.parse("2.0"): + _PATCHED = True + return + + # Lookup the parent class and its request method + parent_class = connection.HTTPConnection.__bases__[0] + parent_request = parent_class.request + + def new_request(self, *args, **kwargs): + """Handle parent request. + + This method binds the parent's request method to self and then + calls our modified request function. + """ + self._parent_request = functools.partial(parent_request, self) + return request(self, *args, **kwargs) + + connection.HTTPConnection.request = new_request + _PATCHED = True \ No newline at end of file diff --git a/docs/_scripts/prepare_notebooks_for_ci.py b/docs/_scripts/prepare_notebooks_for_ci.py index dfa1e1c70..697176c8c 100644 --- a/docs/_scripts/prepare_notebooks_for_ci.py +++ b/docs/_scripts/prepare_notebooks_for_ci.py @@ -43,7 +43,9 @@ NOTEBOOKS_NO_EXECUTION = [ "docs/docs/tutorials/lats/lats.ipynb", # issues only when running with VCR "docs/docs/tutorials/rag/langgraph_crag.ipynb", # flakiness from tavily "docs/docs/tutorials/rag/langgraph_adaptive_rag.ipynb", # Cannot create a consistent method resolution error from VCR - "docs/docs/how-tos/map-reduce.ipynb" # flakiness from structured output, only when running with VCR + "docs/docs/how-tos/map-reduce.ipynb", # flakiness from structured output, only when running with VCR + "docs/docs/tutorials/tot/tot.ipynb", + "docs/docs/how-tos/visualization.ipynb" ] @@ -86,6 +88,7 @@ def add_vcr_to_notebook( ) -> nbformat.NotebookNode: """Inject `with vcr.cassette` into each code cell of the notebook.""" + uses_langsmith = False # Inject VCR context manager into each code cell for idx, cell in enumerate(notebook.cells): if cell.cell_type != "code": @@ -120,6 +123,9 @@ def add_vcr_to_notebook( f" {line}" for line in lines ) + if any("hub.pull" in line or "from langsmith import" in line for line in lines): + uses_langsmith = True + # Add import statement vcr_import_lines = [ "import nest_asyncio", @@ -152,6 +158,15 @@ def add_vcr_to_notebook( "custom_vcr.register_serializer('advanced_compressed', AdvancedCompressedSerializer())", "custom_vcr.serializer = 'advanced_compressed'", ] + if uses_langsmith: + vcr_import_lines.extend( + # patch urllib3 to handle vcr errors, see more here: + # https://github.com/langchain-ai/langsmith-sdk/blob/main/python/langsmith/_internal/_patch.py + "import sys", + f"sys.path.insert(0, '{os.path.join(DOCS_PATH, '_scripts')}')", + "import _patch as patch_urllib3", + "patch_urllib3.patch_urllib3()", + ) import_cell = nbformat.v4.new_code_cell(source="\n".join(vcr_import_lines)) import_cell.pop("id", None) notebook.cells.insert(0, import_cell) diff --git a/poetry.lock b/poetry.lock index 400366540..74535df36 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -2862,21 +2862,21 @@ adal = ["adal (>=1.0.2)"] [[package]] name = "langchain" -version = "0.3.9" +version = "0.3.14" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "langchain-0.3.9-py3-none-any.whl", hash = "sha256:ade5a1fee2f94f2e976a6c387f97d62cc7f0b9f26cfe0132a41d2bda761e1045"}, - {file = "langchain-0.3.9.tar.gz", hash = "sha256:4950c4ad627d0aa95ce6bda7de453e22059b7e7836b562a8f781fb0b05d7294c"}, + {file = "langchain-0.3.14-py3-none-any.whl", hash = "sha256:5df9031702f7fe6c956e84256b4639a46d5d03a75be1ca4c1bc9479b358061a2"}, + {file = "langchain-0.3.14.tar.gz", hash = "sha256:4a5ae817b5832fa0e1fcadc5353fbf74bebd2f8e550294d4dc039f651ddcd3d1"}, ] [package.dependencies] aiohttp = ">=3.8.3,<4.0.0" async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""} -langchain-core = ">=0.3.21,<0.4.0" -langchain-text-splitters = ">=0.3.0,<0.4.0" -langsmith = ">=0.1.17,<0.2.0" +langchain-core = ">=0.3.29,<0.4.0" +langchain-text-splitters = ">=0.3.3,<0.4.0" +langsmith = ">=0.1.17,<0.3" numpy = [ {version = ">=1.22.4,<2", markers = "python_version < \"3.12\""}, {version = ">=1.26.2,<3", markers = "python_version >= \"3.12\""}, @@ -2906,45 +2906,46 @@ pydantic = ">=2.7.4,<3.0.0" [[package]] name = "langchain-community" -version = "0.3.1" +version = "0.3.14" description = "Community contributed LangChain integrations." optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "langchain_community-0.3.1-py3-none-any.whl", hash = "sha256:627eb26c16417764762ac47dd0d3005109f750f40242a88bb8f2958b798bcf90"}, - {file = "langchain_community-0.3.1.tar.gz", hash = "sha256:c964a70628f266a61647e58f2f0434db633d4287a729f100a81dd8b0654aec93"}, + {file = "langchain_community-0.3.14-py3-none-any.whl", hash = "sha256:cc02a0abad0551edef3e565dff643386a5b2ee45b933b6d883d4a935b9649f3c"}, + {file = "langchain_community-0.3.14.tar.gz", hash = "sha256:d8ba0fe2dbb5795bff707684b712baa5ee379227194610af415ccdfdefda0479"}, ] [package.dependencies] aiohttp = ">=3.8.3,<4.0.0" dataclasses-json = ">=0.5.7,<0.7" -langchain = ">=0.3.1,<0.4.0" -langchain-core = ">=0.3.6,<0.4.0" -langsmith = ">=0.1.125,<0.2.0" +httpx-sse = ">=0.4.0,<0.5.0" +langchain = ">=0.3.14,<0.4.0" +langchain-core = ">=0.3.29,<0.4.0" +langsmith = ">=0.1.125,<0.3" numpy = [ - {version = ">=1,<2", markers = "python_version < \"3.12\""}, - {version = ">=1.26.0,<2.0.0", markers = "python_version >= \"3.12\""}, + {version = ">=1.22.4,<2", markers = "python_version < \"3.12\""}, + {version = ">=1.26.2,<3", markers = "python_version >= \"3.12\""}, ] pydantic-settings = ">=2.4.0,<3.0.0" PyYAML = ">=5.3" requests = ">=2,<3" SQLAlchemy = ">=1.4,<3" -tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0" +tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10" [[package]] name = "langchain-core" -version = "0.3.23" +version = "0.3.29" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "langchain_core-0.3.23-py3-none-any.whl", hash = "sha256:550c0b996990830fa6515a71a1192a8a0343367999afc36d4ede14222941e420"}, - {file = "langchain_core-0.3.23.tar.gz", hash = "sha256:f9e175e3b82063cc3b160c2ca2b155832e1c6f915312e1204828f97d4aabf6e1"}, + {file = "langchain_core-0.3.29-py3-none-any.whl", hash = "sha256:817db1474871611a81105594a3e4d11704949661008e455a10e38ca9ff601a1a"}, + {file = "langchain_core-0.3.29.tar.gz", hash = "sha256:773d6aeeb612e7ce3d996c0be403433d8c6a91e77bbb7a7461c13e15cfbe5b06"}, ] [package.dependencies] jsonpatch = ">=1.33,<2.0" -langsmith = ">=0.1.125,<0.2.0" +langsmith = ">=0.1.125,<0.3" packaging = ">=23.2,<25" pydantic = [ {version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""}, @@ -3021,21 +3022,21 @@ tiktoken = ">=0.7,<1" [[package]] name = "langchain-text-splitters" -version = "0.3.0" +version = "0.3.5" description = "LangChain text splitting utilities" optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "langchain_text_splitters-0.3.0-py3-none-any.whl", hash = "sha256:e84243e45eaff16e5b776cd9c81b6d07c55c010ebcb1965deb3d1792b7358e83"}, - {file = "langchain_text_splitters-0.3.0.tar.gz", hash = "sha256:f9fe0b4d244db1d6de211e7343d4abc4aa90295aa22e1f0c89e51f33c55cd7ce"}, + {file = "langchain_text_splitters-0.3.5-py3-none-any.whl", hash = "sha256:8c9b059827438c5fa8f327b4df857e307828a5ec815163c9b5c9569a3e82c8ee"}, + {file = "langchain_text_splitters-0.3.5.tar.gz", hash = "sha256:11cb7ca3694e5bdd342bc16d3875b7f7381651d4a53cbb91d34f22412ae16443"}, ] [package.dependencies] -langchain-core = ">=0.3.0,<0.4.0" +langchain-core = ">=0.3.29,<0.4.0" [[package]] name = "langgraph" -version = "0.2.59" +version = "0.2.61" description = "Building stateful, multi-actor applications with LLMs" optional = false python-versions = ">=3.9.0,<4.0" @@ -3053,7 +3054,7 @@ url = "libs/langgraph" [[package]] name = "langgraph-checkpoint" -version = "2.0.8" +version = "2.0.9" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = "^3.9.0,<4.0" @@ -3087,7 +3088,7 @@ pymongo = ">=4.9.0,<4.10.0" [[package]] name = "langgraph-checkpoint-postgres" -version = "2.0.8" +version = "2.0.9" description = "Library with a Postgres implementation of LangGraph checkpoint saver." optional = false python-versions = "^3.9.0,<4.0" @@ -3123,7 +3124,7 @@ url = "libs/checkpoint-sqlite" [[package]] name = "langgraph-sdk" -version = "0.1.43" +version = "0.1.49" description = "SDK for interacting with LangGraph API" optional = false python-versions = "^3.9.0,<4.0" @@ -3140,23 +3141,28 @@ url = "libs/sdk-py" [[package]] name = "langsmith" -version = "0.1.129" +version = "0.2.10" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false -python-versions = "<4.0,>=3.8.1" +python-versions = "<4.0,>=3.9" files = [ - {file = "langsmith-0.1.129-py3-none-any.whl", hash = "sha256:31393fbbb17d6be5b99b9b22d530450094fab23c6c37281a6a6efb2143d05347"}, - {file = "langsmith-0.1.129.tar.gz", hash = "sha256:6c3ba66471bef41b9f87da247cc0b493268b3f54656f73648a256a205261b6a0"}, + {file = "langsmith-0.2.10-py3-none-any.whl", hash = "sha256:b02f2f174189ff72e54c88b1aa63343defd6f0f676c396a690c63a4b6495dcc2"}, + {file = "langsmith-0.2.10.tar.gz", hash = "sha256:153c7b3ccbd823528ff5bec84801e7e50a164e388919fc583252df5b27dd7830"}, ] [package.dependencies] httpx = ">=0.23.0,<1" -orjson = ">=3.9.14,<4.0.0" +orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""} pydantic = [ {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""}, {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, ] requests = ">=2,<3" +requests-toolbelt = ">=1.0.0,<2.0.0" + +[package.extras] +compression = ["zstandard (>=0.23.0,<0.24.0)"] +langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"] [[package]] name = "loguru" @@ -5114,7 +5120,6 @@ description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs optional = false python-versions = ">=3.8" files = [ - {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, ] @@ -5125,7 +5130,6 @@ description = "A collection of ASN.1-based protocols modules" optional = false python-versions = ">=3.8" files = [ - {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, ] @@ -5967,6 +5971,20 @@ requests = ">=2.0.0" [package.extras] rsa = ["oauthlib[signedtoken] (>=3.0.0)"] +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + [[package]] name = "rfc3339-validator" version = "0.1.4" @@ -6185,11 +6203,6 @@ files = [ {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f60021ec1574e56632be2a36b946f8143bf4e5e6af4a06d85281adc22938e0dd"}, {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:394397841449853c2290a32050382edaec3da89e35b3e03d6cc966aebc6a8ae6"}, {file = "scikit_learn-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:57cc1786cfd6bd118220a92ede80270132aa353647684efa385a74244a41e3b1"}, - {file = "scikit_learn-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9a702e2de732bbb20d3bad29ebd77fc05a6b427dc49964300340e4c9328b3f5"}, - {file = "scikit_learn-1.5.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:b0768ad641981f5d3a198430a1d31c3e044ed2e8a6f22166b4d546a5116d7908"}, - {file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:178ddd0a5cb0044464fc1bfc4cca5b1833bfc7bb022d70b05db8530da4bb3dd3"}, - {file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7284ade780084d94505632241bf78c44ab3b6f1e8ccab3d2af58e0e950f9c12"}, - {file = "scikit_learn-1.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:b7b0f9a0b1040830d38c39b91b3a44e1b643f4b36e36567b80b7c6bd2202a27f"}, {file = "scikit_learn-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:757c7d514ddb00ae249832fe87100d9c73c6ea91423802872d9e74970a0e40b9"}, {file = "scikit_learn-1.5.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:52788f48b5d8bca5c0736c175fa6bdaab2ef00a8f536cda698db61bd89c551c1"}, {file = "scikit_learn-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:643964678f4b5fbdc95cbf8aec638acc7aa70f5f79ee2cdad1eec3df4ba6ead8"}, @@ -7492,4 +7505,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "367f5fb480a8fa5d8ab1c0964a1e9450dbb28e6998097e7536966e7a5fe30c90" +content-hash = "981f40de9c31530b17537a089651f9e51901b945fbc01b43ac33a466c8a7d9eb" diff --git a/pyproject.toml b/pyproject.toml index ed09fc121..478880726 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ langchain-fireworks = "^0.2.0" langchain-community = "^0.3.0" langchain-experimental = "^0.3.2" langgraph-checkpoint-mongodb = "^0.1.0" -langsmith = "^0.1.129" +langsmith = "^0.2.0" chromadb = "^0.5.5" gpt4all = "^2.8.2" scikit-learn = "^1.5.2"