From 0356f8d418a7460fe806ca8839c2db20951ea142 Mon Sep 17 00:00:00 2001 From: Friedrich Wilken Date: Thu, 24 Sep 2026 19:11:12 +0200 Subject: [PATCH] fix(gen): restore async client with langchain-google-genai >= 4.3.7 The SDK's ChatGoogleGenerativeAI shadows the upstream `validate_environment` validator to inject the AI Core client. Since langchain-google-genai 4.3.7 that upstream validator also registers the client with a `_ClientCleanup` object which the `async_client` property and `aclose()` dereference, so every async call failed with AttributeError. Register the injected client in a validator of our own on the chat class, add a unit test covering `async_client` and `aclose()`, lift the `<4.3.7` pin introduced as a stopgap in #122/#129, and update the stale pin in README_sphynx.md. Refs SAP/ai-sdk-python#152 --- packages/gen/README_sphynx.md | 2 +- .../gen/gen_ai_hub/proxy/langchain/google_genai.py | 12 ++++++++++++ packages/gen/pyproject.toml | 2 +- .../gen/tests/proxy/langchain_/test_google_genai.py | 11 +++++++++++ uv.lock | 8 ++++---- 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/packages/gen/README_sphynx.md b/packages/gen/README_sphynx.md index 9719ebc6..18171076 100644 --- a/packages/gen/README_sphynx.md +++ b/packages/gen/README_sphynx.md @@ -268,7 +268,7 @@ google-genai~=1.60.0 # google boto3>=1.40.61 # amazon aiobotocore>=3.0.0 # amazon langchain~=1.2.6 -langchain_google_genai~=4.2.0 # google +langchain_google_genai~=4.4.0 # google langchain-classic~=1.0.0 langchain-community~=0.4.1 langchain-openai~=1.1.0 diff --git a/packages/gen/gen_ai_hub/proxy/langchain/google_genai.py b/packages/gen/gen_ai_hub/proxy/langchain/google_genai.py index 62268701..5190f8ab 100644 --- a/packages/gen/gen_ai_hub/proxy/langchain/google_genai.py +++ b/packages/gen/gen_ai_hub/proxy/langchain/google_genai.py @@ -4,6 +4,7 @@ from langchain_google_genai import ChatGoogleGenerativeAI as ChatGoogleGenerativeAI_ from langchain_google_genai import GoogleGenerativeAIEmbeddings as GoogleGenerativeAIEmbeddings_ +from langchain_google_genai.chat_models import _ClientCleanup # private upstream name, since 4.3.7 from pydantic import model_validator, ConfigDict from gen_ai_hub.proxy.core.base import BaseProxyClient @@ -115,6 +116,17 @@ class ChatGoogleGenerativeAI(_BaseGoogleGenerativeAI, ChatGoogleGenerativeAI_): def _init_parent(self, **kwargs): ChatGoogleGenerativeAI_.__init__(self, **kwargs) + @model_validator(mode="after") + def _register_client_cleanup(self): + """Register the injected client for cleanup. + + Upstream wraps the client in a ``_ClientCleanup`` stored in + ``_client_cleanup``, which ``async_client`` and ``aclose`` read. That + happens in a validator this class shadows, so do it here instead. + """ + self._client_cleanup = _ClientCleanup(self.client) + return self + class GoogleGenerativeAIEmbeddings(_BaseGoogleGenerativeAI, GoogleGenerativeAIEmbeddings_): """Drop-in replacement for langchain_google_genai.GoogleGenerativeAIEmbeddings.""" diff --git a/packages/gen/pyproject.toml b/packages/gen/pyproject.toml index dfc3265a..71eb75f4 100644 --- a/packages/gen/pyproject.toml +++ b/packages/gen/pyproject.toml @@ -49,7 +49,7 @@ dependencies = [ ] [project.optional-dependencies] -google = ["google-genai~=2.23.0", "langchain-google-genai~=4.3.6,<4.3.7"] +google = ["google-genai~=2.23.0", "langchain-google-genai~=4.4.0"] amazon = ["boto3>=1.40.61", "aiobotocore>=3.2.0", "langchain-aws~=1.7.0"] all = ["sap-ai-sdk-gen[google,amazon]"] diff --git a/packages/gen/tests/proxy/langchain_/test_google_genai.py b/packages/gen/tests/proxy/langchain_/test_google_genai.py index a6e807b5..0d77cb07 100644 --- a/packages/gen/tests/proxy/langchain_/test_google_genai.py +++ b/packages/gen/tests/proxy/langchain_/test_google_genai.py @@ -1,6 +1,8 @@ +import asyncio import unittest from unittest.mock import patch +from google.genai.client import AsyncClient from langchain_classic.chains import LLMChain from langchain_classic.prompts.chat import ( AIMessagePromptTemplate, @@ -19,6 +21,15 @@ class TestGoogleGenerativeAILangchain(unittest.TestCase): def setUpClass(cls) -> None: cls.proxy_client = get_mocked_ai_core_client() + def test_async_client_available(self): + # `async_client` and `aclose` read `_client_cleanup`, which our validator + # has to set because the upstream validator that normally sets it is shadowed. + chat_model = ChatGoogleGenerativeAI( + proxy_model_name="gemini-2.0-flash", proxy_client=self.proxy_client + ) + self.assertIsInstance(chat_model.async_client, AsyncClient) + asyncio.run(chat_model.aclose()) + @patch("langchain_classic.chains.base.Chain.invoke") def test_chat_model(self, mock_chain_invoke): mock_chain_invoke.return_value = GOOGLE_GEMINI_INVOKE_RESPONSE diff --git a/uv.lock b/uv.lock index b2d10fdd..074663f7 100644 --- a/uv.lock +++ b/uv.lock @@ -1719,7 +1719,7 @@ wheels = [ [[package]] name = "langchain-google-genai" -version = "4.3.6" +version = "4.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filetype" }, @@ -1727,9 +1727,9 @@ dependencies = [ { name = "langchain-core" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/14/2e/7410d35d5a073a8a5d4942d49dfc386e79e11c09c66f02429ffcaeb57a5c/langchain_google_genai-4.3.6.tar.gz", hash = "sha256:d123cd8007ae63fc989bd869ef3853312b912007e63c2815b9aa2f089cfa6a05", size = 301130, upload-time = "2026-08-26T23:53:56.92Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/4e/41798c80b574d958d189e049f13d64eb9246a66623465290f2cbfd641759/langchain_google_genai-4.4.0.tar.gz", hash = "sha256:7871beec56ac07b719f77c46997845db6ff2267b817bffb0f97877053b0895d7", size = 378415, upload-time = "2026-09-01T20:15:45.816Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/d5/dd74fb715bea978e14c8ba706e228a3637951477525b182553a37a138b1a/langchain_google_genai-4.3.6-py3-none-any.whl", hash = "sha256:00b75df114a519e434b304daabcd98ea885af87a78a4b059b3063490f65feb44", size = 78524, upload-time = "2026-08-26T23:53:55.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/10/83bb535e78c2cf767a6193c2f3b00b2894c1581a36ef85911ebaa3b9a891/langchain_google_genai-4.4.0-py3-none-any.whl", hash = "sha256:8e23a1307bd2158590bbf9d99f1d658fc84a9d9ddb77fb1b372f8875a2bafbbf", size = 81617, upload-time = "2026-09-01T20:15:44.533Z" }, ] [[package]] @@ -4235,7 +4235,7 @@ requires-dist = [ { name = "langchain", specifier = "~=1.4.0" }, { name = "langchain-aws", marker = "extra == 'amazon'", specifier = "~=1.7.0" }, { name = "langchain-classic", specifier = "~=1.0.0" }, - { name = "langchain-google-genai", marker = "extra == 'google'", specifier = "~=4.3.6,<4.3.7" }, + { name = "langchain-google-genai", marker = "extra == 'google'", specifier = "~=4.4.0" }, { name = "langchain-openai", specifier = "~=1.6.0" }, { name = "langcodes", specifier = "~=3.5.1" }, { name = "openai", specifier = ">=1.66.0" },