diff --git a/libs/community/langchain_community/chat_models/snowflake.py b/libs/community/langchain_community/chat_models/snowflake.py index 524838188303d..84883b2cd87f6 100644 --- a/libs/community/langchain_community/chat_models/snowflake.py +++ b/libs/community/langchain_community/chat_models/snowflake.py @@ -17,7 +17,7 @@ get_pydantic_field_names, pre_init, ) -from langchain_core.utils.utils import build_extra_kwargs +from langchain_core.utils.utils import _build_model_kwargs from pydantic import Field, SecretStr, model_validator SUPPORTED_ROLES: List[str] = [ @@ -131,10 +131,7 @@ class ChatSnowflakeCortex(BaseChatModel): def build_extra(cls, values: Dict[str, Any]) -> Any: """Build extra kwargs from additional params that were passed in.""" all_required_field_names = get_pydantic_field_names(cls) - extra = values.get("model_kwargs", {}) - values["model_kwargs"] = build_extra_kwargs( - extra, values, all_required_field_names - ) + values = _build_model_kwargs(values, all_required_field_names) return values @pre_init diff --git a/libs/community/langchain_community/llms/anthropic.py b/libs/community/langchain_community/llms/anthropic.py index 07b40d6eda705..0a6af6799d821 100644 --- a/libs/community/langchain_community/llms/anthropic.py +++ b/libs/community/langchain_community/llms/anthropic.py @@ -26,7 +26,7 @@ get_pydantic_field_names, pre_init, ) -from langchain_core.utils.utils import build_extra_kwargs, convert_to_secret_str +from langchain_core.utils.utils import _build_model_kwargs, convert_to_secret_str from pydantic import ConfigDict, Field, SecretStr, model_validator @@ -69,11 +69,8 @@ class _AnthropicCommon(BaseLanguageModel): @model_validator(mode="before") @classmethod def build_extra(cls, values: Dict) -> Any: - extra = values.get("model_kwargs", {}) all_required_field_names = get_pydantic_field_names(cls) - values["model_kwargs"] = build_extra_kwargs( - extra, values, all_required_field_names - ) + values = _build_model_kwargs(values, all_required_field_names) return values @pre_init diff --git a/libs/community/langchain_community/llms/llamacpp.py b/libs/community/langchain_community/llms/llamacpp.py index 15d1119fcad5d..a045878fd2668 100644 --- a/libs/community/langchain_community/llms/llamacpp.py +++ b/libs/community/langchain_community/llms/llamacpp.py @@ -8,7 +8,7 @@ from langchain_core.language_models.llms import LLM from langchain_core.outputs import GenerationChunk from langchain_core.utils import get_pydantic_field_names, pre_init -from langchain_core.utils.utils import build_extra_kwargs +from langchain_core.utils.utils import _build_model_kwargs from pydantic import Field, model_validator logger = logging.getLogger(__name__) @@ -199,10 +199,7 @@ def validate_environment(cls, values: Dict) -> Dict: def build_model_kwargs(cls, values: Dict[str, Any]) -> Any: """Build extra kwargs from additional params that were passed in.""" all_required_field_names = get_pydantic_field_names(cls) - extra = values.get("model_kwargs", {}) - values["model_kwargs"] = build_extra_kwargs( - extra, values, all_required_field_names - ) + values = _build_model_kwargs(values, all_required_field_names) return values @property diff --git a/libs/community/langchain_community/llms/openai.py b/libs/community/langchain_community/llms/openai.py index aeb16883fd434..cc33d07ac4e70 100644 --- a/libs/community/langchain_community/llms/openai.py +++ b/libs/community/langchain_community/llms/openai.py @@ -34,7 +34,7 @@ pre_init, ) from langchain_core.utils.pydantic import get_fields -from langchain_core.utils.utils import build_extra_kwargs +from langchain_core.utils.utils import _build_model_kwargs from pydantic import ConfigDict, Field, model_validator from langchain_community.utils.openai import is_openai_v1 @@ -268,10 +268,7 @@ def __new__(cls, **data: Any) -> Union[OpenAIChat, BaseOpenAI]: # type: ignore def build_extra(cls, values: Dict[str, Any]) -> Any: """Build extra kwargs from additional params that were passed in.""" all_required_field_names = get_pydantic_field_names(cls) - extra = values.get("model_kwargs", {}) - values["model_kwargs"] = build_extra_kwargs( - extra, values, all_required_field_names - ) + values = _build_model_kwargs(values, all_required_field_names) return values @pre_init diff --git a/libs/community/tests/unit_tests/chat_models/test_anthropic.py b/libs/community/tests/unit_tests/chat_models/test_anthropic.py index 41452ce0b8331..8e6a659743bf0 100644 --- a/libs/community/tests/unit_tests/chat_models/test_anthropic.py +++ b/libs/community/tests/unit_tests/chat_models/test_anthropic.py @@ -33,9 +33,12 @@ def test_anthropic_model_kwargs() -> None: @pytest.mark.requires("anthropic") -def test_anthropic_invalid_model_kwargs() -> None: - with pytest.raises(ValueError): - ChatAnthropic(model_kwargs={"max_tokens_to_sample": 5}) +def test_anthropic_fields_in_model_kwargs() -> None: + """Test that for backwards compatibility fields can be passed in as model_kwargs.""" + llm = ChatAnthropic(model_kwargs={"max_tokens_to_sample": 5}) + assert llm.max_tokens_to_sample == 5 + llm = ChatAnthropic(model_kwargs={"max_tokens": 5}) + assert llm.max_tokens_to_sample == 5 @pytest.mark.requires("anthropic") diff --git a/libs/community/tests/unit_tests/llms/test_openai.py b/libs/community/tests/unit_tests/llms/test_openai.py index 83b229d8c2e1d..bd5f8b3bd9064 100644 --- a/libs/community/tests/unit_tests/llms/test_openai.py +++ b/libs/community/tests/unit_tests/llms/test_openai.py @@ -26,13 +26,12 @@ def test_openai_model_kwargs() -> None: @pytest.mark.requires("openai") -def test_openai_invalid_model_kwargs() -> None: - with pytest.raises(ValueError): - OpenAI(model_kwargs={"model_name": "foo"}) - - # Test that "model" cannot be specified in kwargs - with pytest.raises(ValueError): - OpenAI(model_kwargs={"model": "gpt-3.5-turbo-instruct"}) +def test_openai_fields_model_kwargs() -> None: + """Test that for backwards compatibility fields can be passed in as model_kwargs.""" + llm = OpenAI(model_kwargs={"model_name": "foo"}, api_key="foo") + assert llm.model_name == "foo" + llm = OpenAI(model_kwargs={"model": "foo"}, api_key="foo") + assert llm.model_name == "foo" @pytest.mark.requires("openai") diff --git a/libs/core/langchain_core/utils/__init__.py b/libs/core/langchain_core/utils/__init__.py index 2e560b21e96d8..7822d3b62519e 100644 --- a/libs/core/langchain_core/utils/__init__.py +++ b/libs/core/langchain_core/utils/__init__.py @@ -32,6 +32,7 @@ ) __all__ = [ + "build_extra_kwargs", "StrictFormatter", "check_package_version", "convert_to_secret_str", @@ -46,7 +47,6 @@ "raise_for_status_with_text", "xor_args", "try_load_from_hub", - "build_extra_kwargs", "image", "get_from_env", "get_from_dict_or_env", diff --git a/libs/core/langchain_core/utils/utils.py b/libs/core/langchain_core/utils/utils.py index e8d7b34bd26be..7bbea2d4e0cac 100644 --- a/libs/core/langchain_core/utils/utils.py +++ b/libs/core/langchain_core/utils/utils.py @@ -210,6 +210,51 @@ def get_pydantic_field_names(pydantic_cls: Any) -> set[str]: return all_required_field_names +def _build_model_kwargs( + values: dict[str, Any], + all_required_field_names: set[str], +) -> dict[str, Any]: + """Build "model_kwargs" param from Pydanitc constructor values. + + Args: + values: All init args passed in by user. + all_required_field_names: All required field names for the pydantic class. + + Returns: + Dict[str, Any]: Extra kwargs. + + Raises: + ValueError: If a field is specified in both values and extra_kwargs. + ValueError: If a field is specified in model_kwargs. + """ + extra_kwargs = values.get("model_kwargs", {}) + for field_name in list(values): + if field_name in extra_kwargs: + raise ValueError(f"Found {field_name} supplied twice.") + if field_name not in all_required_field_names: + warnings.warn( + f"""WARNING! {field_name} is not default parameter. + {field_name} was transferred to model_kwargs. + Please confirm that {field_name} is what you intended.""", + stacklevel=7, + ) + extra_kwargs[field_name] = values.pop(field_name) + + invalid_model_kwargs = all_required_field_names.intersection(extra_kwargs.keys()) + if invalid_model_kwargs: + warnings.warn( + f"Parameters {invalid_model_kwargs} should be specified explicitly. " + f"Instead they were passed in as part of `model_kwargs` parameter.", + stacklevel=7, + ) + for k in invalid_model_kwargs: + values[k] = extra_kwargs.pop(k) + + values["model_kwargs"] = extra_kwargs + return values + + +# DON'T USE! Kept for backwards-compatibility but should never have been public. def build_extra_kwargs( extra_kwargs: dict[str, Any], values: dict[str, Any], diff --git a/libs/core/tests/unit_tests/utils/test_imports.py b/libs/core/tests/unit_tests/utils/test_imports.py index f33491ed29583..67fe97e65698b 100644 --- a/libs/core/tests/unit_tests/utils/test_imports.py +++ b/libs/core/tests/unit_tests/utils/test_imports.py @@ -17,8 +17,8 @@ "raise_for_status_with_text", "xor_args", "try_load_from_hub", - "build_extra_kwargs", "image", + "build_extra_kwargs", "get_from_dict_or_env", "get_from_env", "stringify_dict", diff --git a/libs/partners/anthropic/langchain_anthropic/chat_models.py b/libs/partners/anthropic/langchain_anthropic/chat_models.py index 93ec406fc5e32..251afb015c528 100644 --- a/libs/partners/anthropic/langchain_anthropic/chat_models.py +++ b/libs/partners/anthropic/langchain_anthropic/chat_models.py @@ -56,13 +56,13 @@ ) from langchain_core.tools import BaseTool from langchain_core.utils import ( - build_extra_kwargs, from_env, get_pydantic_field_names, secret_from_env, ) from langchain_core.utils.function_calling import convert_to_openai_tool from langchain_core.utils.pydantic import is_basemodel_subclass +from langchain_core.utils.utils import _build_model_kwargs from pydantic import ( BaseModel, ConfigDict, @@ -646,11 +646,8 @@ def _get_ls_params( @model_validator(mode="before") @classmethod def build_extra(cls, values: Dict) -> Any: - extra = values.get("model_kwargs", {}) all_required_field_names = get_pydantic_field_names(cls) - values["model_kwargs"] = build_extra_kwargs( - extra, values, all_required_field_names - ) + values = _build_model_kwargs(values, all_required_field_names) return values @model_validator(mode="after") diff --git a/libs/partners/anthropic/langchain_anthropic/llms.py b/libs/partners/anthropic/langchain_anthropic/llms.py index 99e7df965fb1c..5b53663d7e2a4 100644 --- a/libs/partners/anthropic/langchain_anthropic/llms.py +++ b/libs/partners/anthropic/langchain_anthropic/llms.py @@ -25,7 +25,7 @@ get_pydantic_field_names, ) from langchain_core.utils.utils import ( - build_extra_kwargs, + _build_model_kwargs, from_env, secret_from_env, ) @@ -88,11 +88,8 @@ class _AnthropicCommon(BaseLanguageModel): @model_validator(mode="before") @classmethod def build_extra(cls, values: Dict) -> Any: - extra = values.get("model_kwargs", {}) all_required_field_names = get_pydantic_field_names(cls) - values["model_kwargs"] = build_extra_kwargs( - extra, values, all_required_field_names - ) + values = _build_model_kwargs(values, all_required_field_names) return values @model_validator(mode="after") diff --git a/libs/partners/anthropic/tests/unit_tests/test_chat_models.py b/libs/partners/anthropic/tests/unit_tests/test_chat_models.py index 8c9f908c19fb5..90906edf5474d 100644 --- a/libs/partners/anthropic/tests/unit_tests/test_chat_models.py +++ b/libs/partners/anthropic/tests/unit_tests/test_chat_models.py @@ -61,9 +61,12 @@ def test_anthropic_model_kwargs() -> None: @pytest.mark.requires("anthropic") -def test_anthropic_invalid_model_kwargs() -> None: - with pytest.raises(ValueError): - ChatAnthropic(model="foo", model_kwargs={"max_tokens_to_sample": 5}) # type: ignore[call-arg] +def test_anthropic_fields_in_model_kwargs() -> None: + """Test that for backwards compatibility fields can be passed in as model_kwargs.""" + llm = ChatAnthropic(model="foo", model_kwargs={"max_tokens_to_sample": 5}) # type: ignore[call-arg] + assert llm.max_tokens == 5 + llm = ChatAnthropic(model="foo", model_kwargs={"max_tokens": 5}) # type: ignore[call-arg] + assert llm.max_tokens == 5 @pytest.mark.requires("anthropic") diff --git a/libs/partners/fireworks/langchain_fireworks/chat_models.py b/libs/partners/fireworks/langchain_fireworks/chat_models.py index a07398f6d35b2..11b24197f607a 100644 --- a/libs/partners/fireworks/langchain_fireworks/chat_models.py +++ b/libs/partners/fireworks/langchain_fireworks/chat_models.py @@ -79,7 +79,7 @@ convert_to_openai_tool, ) from langchain_core.utils.pydantic import is_basemodel_subclass -from langchain_core.utils.utils import build_extra_kwargs, from_env, secret_from_env +from langchain_core.utils.utils import _build_model_kwargs, from_env, secret_from_env from pydantic import ( BaseModel, ConfigDict, @@ -366,10 +366,7 @@ def is_lc_serializable(cls) -> bool: def build_extra(cls, values: Dict[str, Any]) -> Any: """Build extra kwargs from additional params that were passed in.""" all_required_field_names = get_pydantic_field_names(cls) - extra = values.get("model_kwargs", {}) - values["model_kwargs"] = build_extra_kwargs( - extra, values, all_required_field_names - ) + values = _build_model_kwargs(values, all_required_field_names) return values @model_validator(mode="after") diff --git a/libs/partners/fireworks/langchain_fireworks/llms.py b/libs/partners/fireworks/langchain_fireworks/llms.py index 3189483c914a5..fad10e9039f53 100644 --- a/libs/partners/fireworks/langchain_fireworks/llms.py +++ b/libs/partners/fireworks/langchain_fireworks/llms.py @@ -11,7 +11,7 @@ ) from langchain_core.language_models.llms import LLM from langchain_core.utils import get_pydantic_field_names -from langchain_core.utils.utils import build_extra_kwargs, secret_from_env +from langchain_core.utils.utils import _build_model_kwargs, secret_from_env from pydantic import ConfigDict, Field, SecretStr, model_validator from langchain_fireworks.version import __version__ @@ -93,10 +93,7 @@ class Fireworks(LLM): def build_extra(cls, values: Dict[str, Any]) -> Any: """Build extra kwargs from additional params that were passed in.""" all_required_field_names = get_pydantic_field_names(cls) - extra = values.get("model_kwargs", {}) - values["model_kwargs"] = build_extra_kwargs( - extra, values, all_required_field_names - ) + values = _build_model_kwargs(values, all_required_field_names) return values @property diff --git a/libs/partners/openai/langchain_openai/chat_models/base.py b/libs/partners/openai/langchain_openai/chat_models/base.py index 4e91fab34a8d5..baaa74f637b31 100644 --- a/libs/partners/openai/langchain_openai/chat_models/base.py +++ b/libs/partners/openai/langchain_openai/chat_models/base.py @@ -90,7 +90,7 @@ TypeBaseModel, is_basemodel_subclass, ) -from langchain_core.utils.utils import build_extra_kwargs, from_env, secret_from_env +from langchain_core.utils.utils import _build_model_kwargs, from_env, secret_from_env from pydantic import BaseModel, ConfigDict, Field, SecretStr, model_validator from typing_extensions import Self @@ -477,10 +477,7 @@ class BaseChatOpenAI(BaseChatModel): def build_extra(cls, values: Dict[str, Any]) -> Any: """Build extra kwargs from additional params that were passed in.""" all_required_field_names = get_pydantic_field_names(cls) - extra = values.get("model_kwargs", {}) - values["model_kwargs"] = build_extra_kwargs( - extra, values, all_required_field_names - ) + values = _build_model_kwargs(values, all_required_field_names) return values @model_validator(mode="after") diff --git a/libs/partners/openai/langchain_openai/llms/base.py b/libs/partners/openai/langchain_openai/llms/base.py index 0c773b347a60b..633d473ae81d5 100644 --- a/libs/partners/openai/langchain_openai/llms/base.py +++ b/libs/partners/openai/langchain_openai/llms/base.py @@ -27,7 +27,7 @@ from langchain_core.language_models.llms import BaseLLM from langchain_core.outputs import Generation, GenerationChunk, LLMResult from langchain_core.utils import get_pydantic_field_names -from langchain_core.utils.utils import build_extra_kwargs, from_env, secret_from_env +from langchain_core.utils.utils import _build_model_kwargs, from_env, secret_from_env from pydantic import ConfigDict, Field, SecretStr, model_validator from typing_extensions import Self @@ -160,10 +160,7 @@ class BaseOpenAI(BaseLLM): def build_extra(cls, values: Dict[str, Any]) -> Any: """Build extra kwargs from additional params that were passed in.""" all_required_field_names = get_pydantic_field_names(cls) - extra = values.get("model_kwargs", {}) - values["model_kwargs"] = build_extra_kwargs( - extra, values, all_required_field_names - ) + values = _build_model_kwargs(values, all_required_field_names) return values @model_validator(mode="after") diff --git a/libs/partners/openai/tests/unit_tests/llms/test_base.py b/libs/partners/openai/tests/unit_tests/llms/test_base.py index 45955b097c331..e9b43190cd6d4 100644 --- a/libs/partners/openai/tests/unit_tests/llms/test_base.py +++ b/libs/partners/openai/tests/unit_tests/llms/test_base.py @@ -30,9 +30,12 @@ def test_openai_model_kwargs() -> None: assert llm.model_kwargs == {"foo": "bar"} -def test_openai_invalid_model_kwargs() -> None: - with pytest.raises(ValueError): - OpenAI(model_kwargs={"model_name": "foo"}) +def test_openai_fields_in_model_kwargs() -> None: + """Test that for backwards compatibility fields can be passed in as model_kwargs.""" + llm = OpenAI(model_kwargs={"model_name": "foo"}) + assert llm.model_name == "foo" + llm = OpenAI(model_kwargs={"model": "foo"}) + assert llm.model_name == "foo" def test_openai_incorrect_field() -> None: