diff --git a/libs/ibm/langchain_ibm/chat_models.py b/libs/ibm/langchain_ibm/chat_models.py index 7d45455..e5efc2a 100644 --- a/libs/ibm/langchain_ibm/chat_models.py +++ b/libs/ibm/langchain_ibm/chat_models.py @@ -58,7 +58,6 @@ ) from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult from langchain_core.prompt_values import ChatPromptValue -from langchain_core.pydantic_v1 import BaseModel, Field, SecretStr, root_validator from langchain_core.runnables import Runnable, RunnableMap, RunnablePassthrough from langchain_core.tools import BaseTool from langchain_core.utils import convert_to_secret_str, get_from_dict_or_env @@ -66,6 +65,13 @@ convert_to_openai_function, convert_to_openai_tool, ) +from pydantic import ( + BaseModel, + ConfigDict, + Field, + SecretStr, + model_validator, +) logger = logging.getLogger(__name__) @@ -374,10 +380,9 @@ class ChatWatsonx(BaseChatModel): watsonx_model: ModelInference = Field(default=None, exclude=True) #: :meta private: - class Config: - """Configuration for this pydantic object.""" - - allow_population_by_field_name = True + model_config = ConfigDict( + populate_by_name=True, + ) @classmethod def is_lc_serializable(cls) -> bool: @@ -420,8 +425,9 @@ def lc_secrets(self) -> Dict[str, str]: "instance_id": "WATSONX_INSTANCE_ID", } - @root_validator(pre=False, skip_on_failure=True) - def validate_environment(cls, values: Dict) -> Dict: + @model_validator(mode="before") + @classmethod + def validate_environment(cls, values: Dict) -> Any: """Validate that credentials and python package exists in environment.""" values["url"] = convert_to_secret_str( get_from_dict_or_env(values, "url", "WATSONX_URL") @@ -432,11 +438,11 @@ def validate_environment(cls, values: Dict) -> Dict: ) else: if ( - not values["token"] + not values.get("token") and "WATSONX_TOKEN" not in os.environ - and not values["password"] + and not values.get("password") and "WATSONX_PASSWORD" not in os.environ - and not values["apikey"] + and not values.get("apikey") and "WATSONX_APIKEY" not in os.environ ): raise ValueError( @@ -447,54 +453,62 @@ def validate_environment(cls, values: Dict) -> Dict: " or pass 'token', 'password' or 'apikey'" " as a named parameter." ) - elif values["token"] or "WATSONX_TOKEN" in os.environ: + elif values.get("token") or "WATSONX_TOKEN" in os.environ: values["token"] = convert_to_secret_str( get_from_dict_or_env(values, "token", "WATSONX_TOKEN") ) - elif values["password"] or "WATSONX_PASSWORD" in os.environ: + elif values.get("password") or "WATSONX_PASSWORD" in os.environ: values["password"] = convert_to_secret_str( get_from_dict_or_env(values, "password", "WATSONX_PASSWORD") ) values["username"] = convert_to_secret_str( get_from_dict_or_env(values, "username", "WATSONX_USERNAME") ) - elif values["apikey"] or "WATSONX_APIKEY" in os.environ: + elif values.get("apikey") or "WATSONX_APIKEY" in os.environ: values["apikey"] = convert_to_secret_str( get_from_dict_or_env(values, "apikey", "WATSONX_APIKEY") ) values["username"] = convert_to_secret_str( get_from_dict_or_env(values, "username", "WATSONX_USERNAME") ) - if not values["instance_id"] or "WATSONX_INSTANCE_ID" not in os.environ: + if not values.get("instance_id") or "WATSONX_INSTANCE_ID" not in os.environ: values["instance_id"] = convert_to_secret_str( get_from_dict_or_env(values, "instance_id", "WATSONX_INSTANCE_ID") ) credentials = Credentials( - url=values["url"].get_secret_value() if values["url"] else None, - api_key=values["apikey"].get_secret_value() if values["apikey"] else None, - token=values["token"].get_secret_value() if values["token"] else None, + url=values["url"].get_secret_value() if values.get("url") else None, + api_key=values["apikey"].get_secret_value() + if values.get("apikey") + else None, + token=values["token"].get_secret_value() if values.get("token") else None, password=( - values["password"].get_secret_value() if values["password"] else None + values["password"].get_secret_value() + if values.get("password") + else None ), username=( - values["username"].get_secret_value() if values["username"] else None + values["username"].get_secret_value() + if values.get("username") + else None ), instance_id=( values["instance_id"].get_secret_value() - if values["instance_id"] + if values.get("instance_id") else None ), - version=values["version"].get_secret_value() if values["version"] else None, - verify=values["verify"], + version=values["version"].get_secret_value() + if values.get("version") + else None, + verify=values.get("verify"), ) watsonx_chat = ModelInference( - model_id=values["model_id"], - deployment_id=values["deployment_id"], + model_id=values.get("model_id", ""), + deployment_id=values.get("deployment_id", ""), credentials=credentials, - params=values["params"], - project_id=values["project_id"], - space_id=values["space_id"], + params=values.get("params"), + project_id=values.get("project_id", ""), + space_id=values.get("space_id", ""), ) values["watsonx_model"] = watsonx_chat diff --git a/libs/ibm/langchain_ibm/embeddings.py b/libs/ibm/langchain_ibm/embeddings.py index 3566fa2..078bdc6 100644 --- a/libs/ibm/langchain_ibm/embeddings.py +++ b/libs/ibm/langchain_ibm/embeddings.py @@ -1,17 +1,17 @@ import os -from typing import Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Union from ibm_watsonx_ai import APIClient, Credentials # type: ignore from ibm_watsonx_ai.foundation_models.embeddings import Embeddings # type: ignore from langchain_core.embeddings import Embeddings as LangChainEmbeddings -from langchain_core.pydantic_v1 import ( +from langchain_core.utils import convert_to_secret_str, get_from_dict_or_env +from pydantic import ( BaseModel, - Extra, + ConfigDict, Field, SecretStr, - root_validator, + model_validator, ) -from langchain_core.utils import convert_to_secret_str, get_from_dict_or_env class WatsonxEmbeddings(BaseModel, LangChainEmbeddings): @@ -61,23 +61,24 @@ class WatsonxEmbeddings(BaseModel, LangChainEmbeddings): watsonx_client: APIClient = Field(default=None) #: :meta private: - class Config: - """Configuration for this pydantic object.""" - - extra = Extra.forbid - arbitrary_types_allowed = True + model_config = ConfigDict( + extra="forbid", + arbitrary_types_allowed=True, + protected_namespaces=(), + ) - @root_validator(pre=False, skip_on_failure=True) - def validate_environment(cls, values: Dict) -> Dict: + @model_validator(mode="before") + @classmethod + def validate_environment(cls, values: Dict) -> Any: """Validate that credentials and python package exists in environment.""" if isinstance(values.get("watsonx_client"), APIClient): watsonx_embed = Embeddings( - model_id=values["model_id"], - params=values["params"], - api_client=values["watsonx_client"], - project_id=values["project_id"], - space_id=values["space_id"], - verify=values["verify"], + model_id=values.get("model_id", ""), + params=values.get("params"), + api_client=values.get("watsonx_client"), + project_id=values.get("project_id", ""), + space_id=values.get("space_id", ""), + verify=values.get("verify"), ) values["watsonx_embed"] = watsonx_embed @@ -91,11 +92,11 @@ def validate_environment(cls, values: Dict) -> Dict: ) else: if ( - not values["token"] + not values.get("token") and "WATSONX_TOKEN" not in os.environ - and not values["password"] + and not values.get("password") and "WATSONX_PASSWORD" not in os.environ - and not values["apikey"] + and not values.get("apikey") and "WATSONX_APIKEY" not in os.environ ): raise ValueError( @@ -106,25 +107,28 @@ def validate_environment(cls, values: Dict) -> Dict: " or pass 'token', 'password' or 'apikey'" " as a named parameter." ) - elif values["token"] or "WATSONX_TOKEN" in os.environ: + elif values.get("token") or "WATSONX_TOKEN" in os.environ: values["token"] = convert_to_secret_str( get_from_dict_or_env(values, "token", "WATSONX_TOKEN") ) - elif values["password"] or "WATSONX_PASSWORD" in os.environ: + elif values.get("password") or "WATSONX_PASSWORD" in os.environ: values["password"] = convert_to_secret_str( get_from_dict_or_env(values, "password", "WATSONX_PASSWORD") ) values["username"] = convert_to_secret_str( get_from_dict_or_env(values, "username", "WATSONX_USERNAME") ) - elif values["apikey"] or "WATSONX_APIKEY" in os.environ: + elif values.get("apikey") or "WATSONX_APIKEY" in os.environ: values["apikey"] = convert_to_secret_str( get_from_dict_or_env(values, "apikey", "WATSONX_APIKEY") ) values["username"] = convert_to_secret_str( get_from_dict_or_env(values, "username", "WATSONX_USERNAME") ) - if not values["instance_id"] or "WATSONX_INSTANCE_ID" not in os.environ: + if ( + not values.get("instance_id") + or "WATSONX_INSTANCE_ID" not in os.environ + ): values["instance_id"] = convert_to_secret_str( get_from_dict_or_env( values, "instance_id", "WATSONX_INSTANCE_ID" @@ -132,32 +136,34 @@ def validate_environment(cls, values: Dict) -> Dict: ) credentials = Credentials( - url=values["url"].get_secret_value() if values["url"] else None, + url=values["url"].get_secret_value() if values.get("url") else None, api_key=values["apikey"].get_secret_value() - if values["apikey"] + if values.get("apikey") + else None, + token=values["token"].get_secret_value() + if values.get("token") else None, - token=values["token"].get_secret_value() if values["token"] else None, password=values["password"].get_secret_value() - if values["password"] + if values.get("password") else None, username=values["username"].get_secret_value() - if values["username"] + if values.get("username") else None, instance_id=values["instance_id"].get_secret_value() - if values["instance_id"] + if values.get("instance_id") else None, version=values["version"].get_secret_value() - if values["version"] + if values.get("version") else None, - verify=values["verify"], + verify=values.get("verify"), ) watsonx_embed = Embeddings( - model_id=values["model_id"], - params=values["params"], + model_id=values.get("model_id", ""), + params=values.get("params"), credentials=credentials, - project_id=values["project_id"], - space_id=values["space_id"], + project_id=values.get("project_id", ""), + space_id=values.get("space_id", ""), ) values["watsonx_embed"] = watsonx_embed diff --git a/libs/ibm/langchain_ibm/llms.py b/libs/ibm/langchain_ibm/llms.py index d90fdc5..51779f2 100644 --- a/libs/ibm/langchain_ibm/llms.py +++ b/libs/ibm/langchain_ibm/llms.py @@ -8,8 +8,13 @@ from langchain_core.callbacks import CallbackManagerForLLMRun from langchain_core.language_models.llms import BaseLLM from langchain_core.outputs import Generation, GenerationChunk, LLMResult -from langchain_core.pydantic_v1 import Extra, Field, SecretStr, root_validator from langchain_core.utils import convert_to_secret_str, get_from_dict_or_env +from pydantic import ( + ConfigDict, + Field, + SecretStr, + model_validator, +) logger = logging.getLogger(__name__) textgen_valid_params = [ @@ -99,10 +104,9 @@ class WatsonxLLM(BaseLLM): watsonx_client: APIClient = Field(default=None) #: :meta private: - class Config: - """Configuration for this pydantic object.""" - - extra = Extra.forbid + model_config = ConfigDict( + extra="forbid", + ) @classmethod def is_lc_serializable(cls) -> bool: @@ -131,8 +135,9 @@ def lc_secrets(self) -> Dict[str, str]: "instance_id": "WATSONX_INSTANCE_ID", } - @root_validator(pre=False, skip_on_failure=True) - def validate_environment(cls, values: Dict) -> Dict: + @model_validator(mode="before") + @classmethod + def validate_environment(cls, values: Dict) -> Any: """Validate that credentials and python package exists in environment.""" if isinstance(values.get("watsonx_model"), (ModelInference, Model)): values["model_id"] = getattr(values["watsonx_model"], "model_id") @@ -150,12 +155,12 @@ def validate_environment(cls, values: Dict) -> Dict: elif isinstance(values.get("watsonx_client"), APIClient): watsonx_model = ModelInference( - model_id=values["model_id"], - params=values["params"], - api_client=values["watsonx_client"], - project_id=values["project_id"], - space_id=values["space_id"], - verify=values["verify"], + model_id=values.get("model_id", ""), + params=values.get("params"), + api_client=values.get("watsonx_client"), + project_id=values.get("project_id", ""), + space_id=values.get("space_id", ""), + verify=values.get("verify"), ) values["watsonx_model"] = watsonx_model @@ -169,11 +174,11 @@ def validate_environment(cls, values: Dict) -> Dict: ) else: if ( - not values["token"] + not values.get("token") and "WATSONX_TOKEN" not in os.environ - and not values["password"] + and not values.get("password") and "WATSONX_PASSWORD" not in os.environ - and not values["apikey"] + and not values.get("apikey") and "WATSONX_APIKEY" not in os.environ ): raise ValueError( @@ -184,58 +189,63 @@ def validate_environment(cls, values: Dict) -> Dict: " or pass 'token', 'password' or 'apikey'" " as a named parameter." ) - elif values["token"] or "WATSONX_TOKEN" in os.environ: + elif values.get("token") or "WATSONX_TOKEN" in os.environ: values["token"] = convert_to_secret_str( get_from_dict_or_env(values, "token", "WATSONX_TOKEN") ) - elif values["password"] or "WATSONX_PASSWORD" in os.environ: + elif values.get("password") or "WATSONX_PASSWORD" in os.environ: values["password"] = convert_to_secret_str( get_from_dict_or_env(values, "password", "WATSONX_PASSWORD") ) values["username"] = convert_to_secret_str( get_from_dict_or_env(values, "username", "WATSONX_USERNAME") ) - elif values["apikey"] or "WATSONX_APIKEY" in os.environ: + elif values.get("apikey") or "WATSONX_APIKEY" in os.environ: values["apikey"] = convert_to_secret_str( get_from_dict_or_env(values, "apikey", "WATSONX_APIKEY") ) values["username"] = convert_to_secret_str( get_from_dict_or_env(values, "username", "WATSONX_USERNAME") ) - if not values["instance_id"] or "WATSONX_INSTANCE_ID" not in os.environ: + if ( + not values.get("instance_id") + or "WATSONX_INSTANCE_ID" not in os.environ + ): values["instance_id"] = convert_to_secret_str( get_from_dict_or_env( values, "instance_id", "WATSONX_INSTANCE_ID" ) ) credentials = Credentials( - url=values["url"].get_secret_value() if values["url"] else None, + url=values["url"].get_secret_value() if values.get("url") else None, api_key=values["apikey"].get_secret_value() - if values["apikey"] + if values.get("apikey") + else None, + token=values["token"].get_secret_value() + if values.get("token") else None, - token=values["token"].get_secret_value() if values["token"] else None, password=values["password"].get_secret_value() - if values["password"] + if values.get("password") else None, username=values["username"].get_secret_value() - if values["username"] + if values.get("username") else None, instance_id=values["instance_id"].get_secret_value() - if values["instance_id"] + if values.get("instance_id") else None, version=values["version"].get_secret_value() - if values["version"] + if values.get("version") else None, - verify=values["verify"], + verify=values.get("verify"), ) watsonx_model = ModelInference( - model_id=values["model_id"], - deployment_id=values["deployment_id"], + model_id=values.get("model_id", ""), + deployment_id=values.get("deployment_id", ""), credentials=credentials, - params=values["params"], - project_id=values["project_id"], - space_id=values["space_id"], + params=values.get("params"), + project_id=values.get("project_id", ""), + space_id=values.get("space_id", ""), ) values["watsonx_model"] = watsonx_model diff --git a/libs/ibm/poetry.lock b/libs/ibm/poetry.lock index a91931e..f78c3b7 100644 --- a/libs/ibm/poetry.lock +++ b/libs/ibm/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "annotated-types" @@ -408,19 +408,19 @@ files = [ [[package]] name = "langchain-core" -version = "0.2.29" +version = "0.3.0" description = "Building applications with LLMs through composability" optional = false -python-versions = ">=3.8.1,<4.0" +python-versions = ">=3.9,<4.0" files = [] develop = false [package.dependencies] jsonpatch = "^1.33" -langsmith = "^0.1.75" +langsmith = "^0.1.117" packaging = ">=23.2,<25" pydantic = [ - {version = ">=1,<3", 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" @@ -431,21 +431,22 @@ typing-extensions = ">=4.7" type = "git" url = "https://github.com/langchain-ai/langchain.git" reference = "HEAD" -resolved_reference = "d77c7c4236df8e56fbe3acc8e0a71b57b48f1678" +resolved_reference = "a319a0ff1d17057d750f7e4a8fee98aa8f68703c" subdirectory = "libs/core" [[package]] name = "langsmith" -version = "0.1.98" +version = "0.1.121" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = "<4.0,>=3.8.1" files = [ - {file = "langsmith-0.1.98-py3-none-any.whl", hash = "sha256:f79e8a128652bbcee4606d10acb6236973b5cd7dde76e3741186d3b97b5698e9"}, - {file = "langsmith-0.1.98.tar.gz", hash = "sha256:e07678219a0502e8f26d35294e72127a39d25e32fafd091af5a7bb661e9a6bd1"}, + {file = "langsmith-0.1.121-py3-none-any.whl", hash = "sha256:fdb1ac8a671d3904201bfeea197d87bded46a10d08f1034af464211872e29893"}, + {file = "langsmith-0.1.121.tar.gz", hash = "sha256:e9381b82a5bd484af9a51c3e96faea572746b8d617b070c1cda40cbbe48e33df"}, ] [package.dependencies] +httpx = ">=0.23.0,<1" orjson = ">=3.9.14,<4.0.0" pydantic = [ {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""}, @@ -1246,4 +1247,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = ">=3.10,<4.0" -content-hash = "1377b3c14ce0358d6ea7103f33f493598cba742b9cc8244f6a37b0b84455bd30" +content-hash = "62b6b86e3d90ca10c505e6fcc7e21d3265ed59475d65514cc1d6d387208f57ac" diff --git a/libs/ibm/pyproject.toml b/libs/ibm/pyproject.toml index fe0b421..dbcee5d 100644 --- a/libs/ibm/pyproject.toml +++ b/libs/ibm/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain-ibm" -version = "0.1.12" +version = "0.2.0" description = "An integration package connecting IBM watsonx.ai and LangChain" authors = ["IBM"] readme = "README.md" @@ -12,7 +12,7 @@ license = "MIT" [tool.poetry.dependencies] python = ">=3.10,<4.0" -langchain-core = ">=0.2.2,<0.3" +langchain-core = ">=0.3.0,<0.4" ibm-watsonx-ai = "^1.0.8" [tool.poetry.group.test] diff --git a/libs/ibm/scripts/check_pydantic.sh b/libs/ibm/scripts/check_pydantic.sh deleted file mode 100755 index 06b5bb8..0000000 --- a/libs/ibm/scripts/check_pydantic.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -# -# This script searches for lines starting with "import pydantic" or "from pydantic" -# in tracked files within a Git repository. -# -# Usage: ./scripts/check_pydantic.sh /path/to/repository - -# Check if a path argument is provided -if [ $# -ne 1 ]; then - echo "Usage: $0 /path/to/repository" - exit 1 -fi - -repository_path="$1" - -# Search for lines matching the pattern within the specified repository -result=$(git -C "$repository_path" grep -E '^import pydantic|^from pydantic') - -# Check if any matching lines were found -if [ -n "$result" ]; then - echo "ERROR: The following lines need to be updated:" - echo "$result" - echo "Please replace the code with an import from langchain_core.pydantic_v1." - echo "For example, replace 'from pydantic import BaseModel'" - echo "with 'from langchain_core.pydantic_v1 import BaseModel'" - exit 1 -fi diff --git a/libs/ibm/tests/integration_tests/test_chat_models.py b/libs/ibm/tests/integration_tests/test_chat_models.py index a30b91e..05cf96d 100644 --- a/libs/ibm/tests/integration_tests/test_chat_models.py +++ b/libs/ibm/tests/integration_tests/test_chat_models.py @@ -12,7 +12,7 @@ SystemMessage, ) from langchain_core.prompts import ChatPromptTemplate -from langchain_core.pydantic_v1 import BaseModel +from pydantic import BaseModel from langchain_ibm import ChatWatsonx