diff --git a/libs/genai/langchain_google_genai/_function_utils.py b/libs/genai/langchain_google_genai/_function_utils.py index e187577f..2be3cd2a 100644 --- a/libs/genai/langchain_google_genai/_function_utils.py +++ b/libs/genai/langchain_google_genai/_function_utils.py @@ -21,8 +21,10 @@ import google.ai.generativelanguage as glm import google.ai.generativelanguage_v1beta.types as gapic -import proto # type: ignore[import] -from google.generativeai.types.content_types import ToolDict # type: ignore[import] +import proto # type: ignore[import-untyped] +from google.generativeai.types.content_types import ( # type: ignore[import-untyped] + ToolDict, +) from langchain_core.tools import BaseTool from langchain_core.tools import tool as callable_as_lc_tool from langchain_core.utils.function_calling import ( @@ -144,7 +146,7 @@ def convert_to_genai_function_declarations( tools: Sequence[_ToolsType], ) -> gapic.Tool: if not isinstance(tools, collections.abc.Sequence): - logger.warning( + logger.warning( # type: ignore[unreachable] "convert_to_genai_function_declarations expects a Sequence " "and not a single tool." ) @@ -237,7 +239,7 @@ def _format_base_tool_to_function_declaration( if issubclass(tool.args_schema, BaseModel): schema = tool.args_schema.model_json_schema() - elif issubclass(tool.args_schema, BaseModelV1): + elif issubclass(tool.args_schema, BaseModelV1): # type: ignore[unreachable] schema = tool.args_schema.schema() else: raise NotImplementedError( @@ -259,7 +261,7 @@ def _convert_pydantic_to_genai_function( ) -> gapic.FunctionDeclaration: if issubclass(pydantic_model, BaseModel): schema = pydantic_model.model_json_schema() - elif issubclass(pydantic_model, BaseModelV1): + elif issubclass(pydantic_model, BaseModelV1): # type: ignore[unreachable] schema = pydantic_model.schema() else: raise NotImplementedError( @@ -460,7 +462,7 @@ def _tool_choice_to_tool_config( def is_basemodel_subclass_safe(tool: Type) -> bool: if safe_import("langchain_core.utils.pydantic", "is_basemodel_subclass"): from langchain_core.utils.pydantic import ( - is_basemodel_subclass, # type: ignore[import] + is_basemodel_subclass, ) return is_basemodel_subclass(tool) diff --git a/libs/genai/langchain_google_genai/_genai_extension.py b/libs/genai/langchain_google_genai/_genai_extension.py index f7b39163..4d0e0044 100644 --- a/libs/genai/langchain_google_genai/_genai_extension.py +++ b/libs/genai/langchain_google_genai/_genai_extension.py @@ -21,7 +21,7 @@ from google.api_core import client_options as client_options_lib from google.api_core import exceptions as gapi_exception from google.api_core import gapic_v1 -from google.auth import credentials, exceptions # type: ignore +from google.auth import credentials, exceptions from google.protobuf import timestamp_pb2 _logger = logging.getLogger(__name__) diff --git a/libs/genai/langchain_google_genai/_image_utils.py b/libs/genai/langchain_google_genai/_image_utils.py index a0baba49..d30989ae 100644 --- a/libs/genai/langchain_google_genai/_image_utils.py +++ b/libs/genai/langchain_google_genai/_image_utils.py @@ -54,7 +54,7 @@ def load_bytes(self, image_string: str) -> bytes: "Please pass in images as Google Cloud Storage URI, " "b64 encoded image string (data:image/...), or valid image url." ) - return self._bytes_from_file(image_string) + return self._bytes_from_file(image_string) # type: ignore[unreachable] raise ValueError( "Image string must be one of: Google Cloud Storage URI, " diff --git a/libs/genai/langchain_google_genai/chat_models.py b/libs/genai/langchain_google_genai/chat_models.py index 91a1cfa8..1c79313e 100644 --- a/libs/genai/langchain_google_genai/chat_models.py +++ b/libs/genai/langchain_google_genai/chat_models.py @@ -25,7 +25,7 @@ import google.api_core # TODO: remove ignore once the google package is published with types -import proto # type: ignore[import] +import proto # type: ignore[import-untyped] from google.ai.generativelanguage_v1beta import ( GenerativeServiceAsyncClient as v1betaGenerativeServiceAsyncClient, ) @@ -44,10 +44,10 @@ ToolConfig, VideoMetadata, ) -from google.generativeai.caching import CachedContent # type: ignore[import] -from google.generativeai.types import Tool as GoogleTool # type: ignore[import] +from google.generativeai.caching import CachedContent # type: ignore[import-untyped] +from google.generativeai.types import Tool as GoogleTool # type: ignore[import-untyped] from google.generativeai.types import caching_types, content_types -from google.generativeai.types.content_types import ( # type: ignore[import] +from google.generativeai.types.content_types import ( # type: ignore[import-untyped] FunctionDeclarationType, ToolDict, ) @@ -212,7 +212,7 @@ async def _achat_with_retry(generation_method: Callable, **kwargs: Any) -> Any: Any: The result from the chat generation method. """ retry_decorator = _create_retry_decorator() - from google.api_core.exceptions import InvalidArgument # type: ignore + from google.api_core.exceptions import InvalidArgument @retry_decorator async def _achat_with_retry(**kwargs: Any) -> Any: @@ -787,10 +787,10 @@ class Joke(BaseModel): raise an error.""" cached_content: Optional[str] = None - """The name of the cached content used as context to serve the prediction. + """The name of the cached content used as context to serve the prediction. - Note: only used in explicit caching, where users can have control over caching - (e.g. what content to cache) and enjoy guaranteed cost savings. Format: + Note: only used in explicit caching, where users can have control over caching + (e.g. what content to cache) and enjoy guaranteed cost savings. Format: ``cachedContents/{cachedContent}``. """ @@ -1275,7 +1275,7 @@ def bind_tools( f"both:\n\n{tool_choice=}\n\n{tool_config=}" ) try: - formatted_tools: list = [convert_to_openai_tool(tool) for tool in tools] # type: ignore[arg-type] + formatted_tools: list = [convert_to_openai_tool(tool) for tool in tools] except Exception: formatted_tools = [ tool_to_dict(convert_to_genai_function_declarations(tools)) @@ -1381,4 +1381,4 @@ def _get_tool_name( tool: Union[ToolDict, GoogleTool], ) -> str: genai_tool = tool_to_dict(convert_to_genai_function_declarations([tool])) - return [f["name"] for f in genai_tool["function_declarations"]][0] # type: ignore[index] + return [f["name"] for f in genai_tool["function_declarations"]][0] diff --git a/libs/genai/langchain_google_genai/llms.py b/libs/genai/langchain_google_genai/llms.py index 0da089ce..da73335a 100644 --- a/libs/genai/langchain_google_genai/llms.py +++ b/libs/genai/langchain_google_genai/llms.py @@ -4,7 +4,7 @@ from typing import Any, Callable, Dict, Iterator, List, Optional, Union import google.api_core -import google.generativeai as genai # type: ignore[import] +import google.generativeai as genai # type: ignore[import-untyped] from langchain_core.callbacks import ( AsyncCallbackManagerForLLMRun, CallbackManagerForLLMRun, @@ -16,10 +16,7 @@ from pydantic import BaseModel, ConfigDict, Field, SecretStr, model_validator from typing_extensions import Self -from langchain_google_genai._enums import ( - HarmBlockThreshold, - HarmCategory, -) +from langchain_google_genai._enums import HarmBlockThreshold, HarmCategory class GoogleModelFamily(str, Enum): @@ -170,9 +167,9 @@ class _BaseGoogleGenerativeAI(BaseModel): ) safety_settings: Optional[Dict[HarmCategory, HarmBlockThreshold]] = None - """The default safety settings to use for all generations. - - For example: + """The default safety settings to use for all generations. + + For example: from google.generativeai.types.safety_types import HarmBlockThreshold, HarmCategory diff --git a/libs/genai/poetry.lock b/libs/genai/poetry.lock index 4f0f797f..dba24878 100644 --- a/libs/genai/poetry.lock +++ b/libs/genai/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -265,8 +265,8 @@ grpcio-status = [ {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, ] proto-plus = [ - {version = ">=1.25.0,<2.0.0dev", markers = "python_version >= \"3.13\""}, {version = ">=1.22.3,<2.0.0dev", markers = "python_version < \"3.13\""}, + {version = ">=1.25.0,<2.0.0dev", markers = "python_version >= \"3.13\""}, ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" requests = ">=2.18.0,<3.0.0.dev0" @@ -591,8 +591,8 @@ jsonpatch = "^1.33" langsmith = "^0.1.125" packaging = ">=23.2,<25" pydantic = [ - {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, {version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""}, + {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, ] PyYAML = ">=5.3" tenacity = ">=8.1.0,!=8.4.0,<10.0.0" @@ -637,8 +637,8 @@ files = [ httpx = ">=0.23.0,<1" orjson = ">=3.9.14,<4.0.0" pydantic = [ - {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, {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" @@ -922,8 +922,8 @@ files = [ annotated-types = ">=0.6.0" pydantic-core = "2.23.4" typing-extensions = [ - {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, {version = ">=4.6.1", markers = "python_version < \"3.13\""}, + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, ] [package.extras] @@ -1470,4 +1470,4 @@ watchmedo = ["PyYAML (>=3.10)"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "988b5a557506419d3750476d499929c8e3d3f7a0f37f4ca8c5ec5064227f2671" +content-hash = "b3b70d24f0166a3299c88d9cdeff5c4e0fbe51af55c1f265b8081af298016c84" diff --git a/libs/genai/pyproject.toml b/libs/genai/pyproject.toml index 3f3e4105..14a218ef 100644 --- a/libs/genai/pyproject.toml +++ b/libs/genai/pyproject.toml @@ -15,6 +15,7 @@ python = ">=3.9,<4.0" langchain-core = ">=0.3.15,<0.4" google-generativeai = "^0.8.0" pydantic = ">=2,<3" +grpcio = "^1.66.2" [tool.poetry.group.test] optional = true @@ -26,8 +27,14 @@ pytest-mock = "^3.10.0" syrupy = "^4.0.2" pytest-watcher = "^0.3.4" pytest-asyncio = "^0.21.1" -numpy = "^1.26.2" +types-requests = "^2.31.0.20231231" +types-protobuf = "^4.24.0.4" +numpy = [ + { version = "^1", python = "<3.12" }, + { version = "^1.26.2", python = ">=3.12" }, +] langchain-tests = "0.3.1" +google-generativeai = "^0.8.0" [tool.codespell] ignore-words-list = "rouge" @@ -59,7 +66,6 @@ mypy = "^1.10" types-requests = "^2.28.11.5" types-google-cloud-ndb = "^2.2.0.1" types-protobuf = "^4.24.0.20240302" -numpy = "^1.26.2" [tool.poetry.group.dev] @@ -68,6 +74,7 @@ optional = true [tool.poetry.group.dev.dependencies] types-requests = "^2.31.0.10" types-google-cloud-ndb = "^2.2.0.1" +langchain-core = { git = "https://github.com/langchain-ai/langchain.git", subdirectory = "libs/core" } [tool.ruff.lint] select = [ @@ -77,7 +84,17 @@ select = [ ] [tool.mypy] -disallow_untyped_defs = "True" +disallow_untyped_defs = true +check_untyped_defs = true +error_summary = false +pretty = true +show_column_numbers = true +show_error_codes = true +show_error_context = true +warn_redundant_casts = true +warn_unreachable = true +warn_unused_configs = true +warn_unused_ignores = true [tool.coverage.run] omit = ["tests/*"] diff --git a/libs/genai/tests/integration_tests/test_chat_models.py b/libs/genai/tests/integration_tests/test_chat_models.py index 225ffa79..ceb02d87 100644 --- a/libs/genai/tests/integration_tests/test_chat_models.py +++ b/libs/genai/tests/integration_tests/test_chat_models.py @@ -17,11 +17,8 @@ from langchain_core.tools import tool from pydantic import BaseModel -from langchain_google_genai import ( - ChatGoogleGenerativeAI, - HarmBlockThreshold, - HarmCategory, -) +from langchain_google_genai import ChatGoogleGenerativeAI +from langchain_google_genai._enums import HarmBlockThreshold, HarmCategory from langchain_google_genai.chat_models import ChatGoogleGenerativeAIError _MODEL = "models/gemini-1.0-pro-001" # TODO: Use nano when it's available. diff --git a/libs/genai/tests/integration_tests/test_llms.py b/libs/genai/tests/integration_tests/test_llms.py index 53b9ddb5..e7c72c53 100644 --- a/libs/genai/tests/integration_tests/test_llms.py +++ b/libs/genai/tests/integration_tests/test_llms.py @@ -23,7 +23,7 @@ def test_google_generativeai_call(model_name: str) -> None: if model_name: llm = GoogleGenerativeAI(max_tokens=10, model=model_name) else: - llm = GoogleGenerativeAI(max_tokens=10) # type: ignore[call-arg] + llm = GoogleGenerativeAI(max_tokens=10, model=model_names[0]) output = llm("Say foo:") assert isinstance(output, str) assert llm._llm_type == "google_palm" diff --git a/libs/genai/tests/unit_tests/test_chat_models.py b/libs/genai/tests/unit_tests/test_chat_models.py index 7be08fa3..64f9c47e 100644 --- a/libs/genai/tests/unit_tests/test_chat_models.py +++ b/libs/genai/tests/unit_tests/test_chat_models.py @@ -37,7 +37,7 @@ def test_integration_initialization() -> None: """Test chat model initialization.""" llm = ChatGoogleGenerativeAI( model="gemini-nano", - google_api_key=SecretStr("..."), # type: ignore[call-arg] + google_api_key=SecretStr("..."), top_k=2, top_p=1, temperature=0.7, @@ -53,7 +53,7 @@ def test_integration_initialization() -> None: llm = ChatGoogleGenerativeAI( model="gemini-nano", - google_api_key=SecretStr("..."), # type: ignore[call-arg] + google_api_key=SecretStr("..."), max_output_tokens=10, ) ls_params = llm._get_ls_params() @@ -67,7 +67,7 @@ def test_integration_initialization() -> None: ChatGoogleGenerativeAI( model="gemini-nano", - api_key=SecretStr("..."), + google_api_key=SecretStr("..."), top_k=2, top_p=1, temperature=0.7, @@ -81,14 +81,14 @@ def test_initialization_inside_threadpool() -> None: executor.submit( ChatGoogleGenerativeAI, model="gemini-nano", - google_api_key=SecretStr("secret-api-key"), # type: ignore[call-arg] + google_api_key=SecretStr("secret-api-key"), ).result() def test_initalization_without_async() -> None: chat = ChatGoogleGenerativeAI( model="gemini-nano", - google_api_key=SecretStr("secret-api-key"), # type: ignore[call-arg] + google_api_key=SecretStr("secret-api-key"), ) assert chat.async_client is None @@ -97,7 +97,7 @@ def test_initialization_with_async() -> None: async def initialize_chat_with_async_client() -> ChatGoogleGenerativeAI: model = ChatGoogleGenerativeAI( model="gemini-nano", - google_api_key=SecretStr("secret-api-key"), # type: ignore[call-arg] + google_api_key=SecretStr("secret-api-key"), ) _ = model.async_client return model @@ -110,7 +110,7 @@ async def initialize_chat_with_async_client() -> ChatGoogleGenerativeAI: def test_api_key_is_string() -> None: chat = ChatGoogleGenerativeAI( model="gemini-nano", - google_api_key=SecretStr("secret-api-key"), # type: ignore[call-arg] + google_api_key=SecretStr("secret-api-key"), ) assert isinstance(chat.google_api_key, SecretStr) @@ -118,7 +118,7 @@ def test_api_key_is_string() -> None: def test_api_key_masked_when_passed_via_constructor(capsys: CaptureFixture) -> None: chat = ChatGoogleGenerativeAI( model="gemini-nano", - google_api_key=SecretStr("secret-api-key"), # type: ignore[call-arg] + google_api_key=SecretStr("secret-api-key"), ) print(chat.google_api_key, end="") # noqa: T201 captured = capsys.readouterr() @@ -271,7 +271,7 @@ def test_additional_headers_support(headers: Optional[Dict[str, str]]) -> None: ): chat = ChatGoogleGenerativeAI( model="gemini-pro", - google_api_key=param_secret_api_key, # type: ignore[call-arg] + google_api_key=param_secret_api_key, client_options=param_client_options, transport=param_transport, additional_headers=headers, @@ -547,7 +547,7 @@ def test_parse_response_candidate(raw_candidate: Dict, expected: AIMessage) -> N def test_serialize() -> None: - llm = ChatGoogleGenerativeAI(model="gemini-pro-1.5", google_api_key="test-key") # type: ignore[call-arg] + llm = ChatGoogleGenerativeAI(model="gemini-pro-1.5", api_key=SecretStr("test-key")) serialized = dumps(llm) llm_loaded = loads( serialized, diff --git a/libs/genai/tests/unit_tests/test_function_utils.py b/libs/genai/tests/unit_tests/test_function_utils.py index 536c2a99..ba9f6293 100644 --- a/libs/genai/tests/unit_tests/test_function_utils.py +++ b/libs/genai/tests/unit_tests/test_function_utils.py @@ -41,7 +41,7 @@ def sum_two_numbers(a: float, b: float) -> str: """ return str(a + b) - schema = convert_to_genai_function_declarations([sum_two_numbers]) # type: ignore + schema = convert_to_genai_function_declarations([sum_two_numbers]) function_declaration = schema.function_declarations[0] assert function_declaration.name == "sum_two_numbers" assert function_declaration.parameters @@ -52,7 +52,7 @@ def do_something_optional(a: float, b: float = 0) -> str: """Some description""" return str(a + b) - schema = convert_to_genai_function_declarations([do_something_optional]) # type: ignore + schema = convert_to_genai_function_declarations([do_something_optional]) function_declaration = schema.function_declarations[0] assert function_declaration.name == "do_something_optional" assert function_declaration.parameters diff --git a/libs/genai/tests/unit_tests/test_genai_aqa.py b/libs/genai/tests/unit_tests/test_genai_aqa.py index c8cd521b..4deaaf9d 100644 --- a/libs/genai/tests/unit_tests/test_genai_aqa.py +++ b/libs/genai/tests/unit_tests/test_genai_aqa.py @@ -3,11 +3,9 @@ import google.ai.generativelanguage as genai import pytest -from langchain_google_genai import ( - AqaInput, - GenAIAqa, -) +from langchain_google_genai import AqaInput, GenAIAqa from langchain_google_genai import _genai_extension as genaix +from langchain_google_genai._enums import HarmBlockThreshold, HarmCategory # Make sure the tests do not hit actual production servers. genaix.set_config( @@ -54,8 +52,8 @@ def test_invoke(mock_generate_answer: MagicMock) -> None: answer_style=genai.GenerateAnswerRequest.AnswerStyle.EXTRACTIVE, safety_settings=[ genai.SafetySetting( - category=genai.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, - threshold=genai.SafetySetting.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE, + category=HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, + threshold=HarmBlockThreshold.BLOCK_LOW_AND_ABOVE, ) ], ) @@ -80,11 +78,10 @@ def test_invoke(mock_generate_answer: MagicMock) -> None: assert len(request.safety_settings) == 1 assert ( request.safety_settings[0].category - == genai.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT + == HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT ) assert ( - request.safety_settings[0].threshold - == genai.SafetySetting.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE + request.safety_settings[0].threshold == HarmBlockThreshold.BLOCK_LOW_AND_ABOVE ) assert request.temperature == 0.5 diff --git a/libs/genai/tests/unit_tests/test_llms.py b/libs/genai/tests/unit_tests/test_llms.py index 29a68980..73b6762f 100644 --- a/libs/genai/tests/unit_tests/test_llms.py +++ b/libs/genai/tests/unit_tests/test_llms.py @@ -1,3 +1,5 @@ +from pydantic import SecretStr + from langchain_google_genai.llms import GoogleGenerativeAI, GoogleModelFamily @@ -10,7 +12,7 @@ def test_model_family() -> None: def test_tracing_params() -> None: # Test standard tracing params - llm = GoogleGenerativeAI(model="gemini-pro", google_api_key="foo") # type: ignore[call-arg] + llm = GoogleGenerativeAI(model="gemini-pro", google_api_key=SecretStr("foo")) ls_params = llm._get_ls_params() assert ls_params == { "ls_provider": "google_genai", @@ -23,7 +25,7 @@ def test_tracing_params() -> None: model="gemini-pro", temperature=0.1, max_output_tokens=10, - google_api_key="foo", # type: ignore[call-arg] + google_api_key=SecretStr("foo"), ) ls_params = llm._get_ls_params() assert ls_params == {