From 615f8b0d47f2476d011b143d6da93549bfcea0b1 Mon Sep 17 00:00:00 2001 From: Bagatur Date: Tue, 3 Sep 2024 16:33:35 -0700 Subject: [PATCH] openai[major]: switch to pydantic v2 --- .../langchain_openai/chat_models/azure.py | 70 ++++++------ .../langchain_openai/chat_models/base.py | 108 +++++++++--------- .../langchain_openai/embeddings/azure.py | 58 +++++----- .../langchain_openai/embeddings/base.py | 73 ++++++------ .../openai/langchain_openai/llms/azure.py | 68 +++++------ .../openai/langchain_openai/llms/base.py | 56 ++++----- .../chat_models/test_base.py | 2 +- .../tests/unit_tests/chat_models/test_base.py | 2 +- .../openai/tests/unit_tests/fake/callbacks.py | 2 +- .../openai/tests/unit_tests/test_load.py | 8 +- .../openai/tests/unit_tests/test_secrets.py | 2 +- .../unit_tests/chat_models.py | 2 +- .../unit_tests/embeddings.py | 2 +- 13 files changed, 228 insertions(+), 225 deletions(-) diff --git a/libs/partners/openai/langchain_openai/chat_models/azure.py b/libs/partners/openai/langchain_openai/chat_models/azure.py index 23b9dd3d2e9..e5ae0550fd7 100644 --- a/libs/partners/openai/langchain_openai/chat_models/azure.py +++ b/libs/partners/openai/langchain_openai/chat_models/azure.py @@ -31,7 +31,7 @@ from langchain_core.output_parsers.openai_tools import ( PydanticToolsParser, ) from langchain_core.outputs import ChatResult -from langchain_core.pydantic_v1 import BaseModel, Field, SecretStr, root_validator +from pydantic import BaseModel, Field, SecretStr, model_validator from langchain_core.runnables import Runnable, RunnableMap, RunnablePassthrough from langchain_core.tools import BaseTool from langchain_core.utils import from_env, secret_from_env @@ -39,6 +39,8 @@ from langchain_core.utils.function_calling import convert_to_openai_tool from langchain_core.utils.pydantic import is_basemodel_subclass from langchain_openai.chat_models.base import BaseChatOpenAI +from typing_extensions import Self + logger = logging.getLogger(__name__) @@ -494,7 +496,7 @@ class AzureChatOpenAI(BaseChatOpenAI): default_factory=from_env("OPENAI_API_VERSION", default=None), ) """Automatically inferred from env var `OPENAI_API_VERSION` if not provided.""" - # Check OPENAI_KEY for backwards compatibility. + # Check OPENAI_API_KEY for backwards compatibility. # TODO: Remove OPENAI_API_KEY support to avoid possible conflict when using # other forms of azure credentials. openai_api_key: Optional[SecretStr] = Field( @@ -565,31 +567,31 @@ class AzureChatOpenAI(BaseChatOpenAI): def is_lc_serializable(cls) -> bool: return True - @root_validator(pre=False, skip_on_failure=True) - def validate_environment(cls, values: Dict) -> Dict: + @model_validator(mode="after") + def validate_environment(self) -> Self: """Validate that api key and python package exists in environment.""" - if values["n"] < 1: + if self.n < 1: raise ValueError("n must be at least 1.") - if values["n"] > 1 and values["streaming"]: + if self.n > 1 and self.streaming: raise ValueError("n must be 1 when streaming.") # Check OPENAI_ORGANIZATION for backwards compatibility. - values["openai_organization"] = ( - values["openai_organization"] + self.openai_organization = ( + self.openai_organization or os.getenv("OPENAI_ORG_ID") or os.getenv("OPENAI_ORGANIZATION") ) # For backwards compatibility. Before openai v1, no distinction was made # between azure_endpoint and base_url (openai_api_base). - openai_api_base = values["openai_api_base"] - if openai_api_base and values["validate_base_url"]: + openai_api_base = self.openai_api_base + if openai_api_base and self.validate_base_url: if "/openai" not in openai_api_base: raise ValueError( "As of openai>=1.0.0, Azure endpoints should be specified via " "the `azure_endpoint` param not `openai_api_base` " "(or alias `base_url`)." ) - if values["deployment_name"]: + if self.deployment_name: raise ValueError( "As of openai>=1.0.0, if `azure_deployment` (or alias " "`deployment_name`) is specified then " @@ -603,38 +605,38 @@ class AzureChatOpenAI(BaseChatOpenAI): 'base_url="https://xxx.openai.azure.com/openai/deployments/my-deployment"' ) client_params = { - "api_version": values["openai_api_version"], - "azure_endpoint": values["azure_endpoint"], - "azure_deployment": values["deployment_name"], + "api_version": self.openai_api_version, + "azure_endpoint": self.azure_endpoint, + "azure_deployment": self.deployment_name, "api_key": ( - values["openai_api_key"].get_secret_value() - if values["openai_api_key"] + self.openai_api_key.get_secret_value() + if self.openai_api_key else None ), "azure_ad_token": ( - values["azure_ad_token"].get_secret_value() - if values["azure_ad_token"] + self.azure_ad_token.get_secret_value() + if self.azure_ad_token else None ), - "azure_ad_token_provider": values["azure_ad_token_provider"], - "organization": values["openai_organization"], - "base_url": values["openai_api_base"], - "timeout": values["request_timeout"], - "max_retries": values["max_retries"], - "default_headers": values["default_headers"], - "default_query": values["default_query"], + "azure_ad_token_provider": self.azure_ad_token_provider, + "organization": self.openai_organization, + "base_url": self.openai_api_base, + "timeout": self.request_timeout, + "max_retries": self.max_retries, + "default_headers": self.default_headers, + "default_query": self.default_query, } - if not values.get("client"): - sync_specific = {"http_client": values["http_client"]} - values["root_client"] = openai.AzureOpenAI(**client_params, **sync_specific) - values["client"] = values["root_client"].chat.completions - if not values.get("async_client"): - async_specific = {"http_client": values["http_async_client"]} - values["root_async_client"] = openai.AsyncAzureOpenAI( + if not (self.client or None): + sync_specific = {"http_client": self.http_client} + self.root_client = openai.AzureOpenAI(**client_params, **sync_specific) + self.client = self.root_client.chat.completions + if not (self.async_client or None): + async_specific = {"http_client": self.http_async_client} + self.root_async_client = openai.AsyncAzureOpenAI( **client_params, **async_specific ) - values["async_client"] = values["root_async_client"].chat.completions - return values + self.async_client = self.root_async_client.chat.completions + return self def bind_tools( self, diff --git a/libs/partners/openai/langchain_openai/chat_models/base.py b/libs/partners/openai/langchain_openai/chat_models/base.py index 66b7e75edec..018d8febefb 100644 --- a/libs/partners/openai/langchain_openai/chat_models/base.py +++ b/libs/partners/openai/langchain_openai/chat_models/base.py @@ -73,15 +73,11 @@ from langchain_core.output_parsers.openai_tools import ( parse_tool_call, ) from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult -from langchain_core.pydantic_v1 import BaseModel, Field, SecretStr, root_validator +from pydantic import BaseModel, Field, model_validator, SecretStr from langchain_core.runnables import Runnable, RunnableMap, RunnablePassthrough, chain from langchain_core.runnables.config import run_in_executor from langchain_core.tools import BaseTool -from langchain_core.utils import ( - convert_to_secret_str, - get_from_dict_or_env, - get_pydantic_field_names, -) +from langchain_core.utils import get_from_dict_or_env, get_pydantic_field_names from langchain_core.utils.function_calling import ( convert_to_openai_function, convert_to_openai_tool, @@ -91,7 +87,10 @@ from langchain_core.utils.pydantic import ( TypeBaseModel, is_basemodel_subclass, ) -from langchain_core.utils.utils import build_extra_kwargs +from langchain_core.utils.utils import build_extra_kwargs, from_env, secret_from_env +from pydantic import ConfigDict +from typing_extensions import Self + logger = logging.getLogger(__name__) @@ -361,15 +360,19 @@ class BaseChatOpenAI(BaseChatModel): """What sampling temperature to use.""" model_kwargs: Dict[str, Any] = Field(default_factory=dict) """Holds any model parameters valid for `create` call not explicitly specified.""" - openai_api_key: Optional[SecretStr] = Field(default=None, alias="api_key") - """Automatically inferred from env var `OPENAI_API_KEY` if not provided.""" + openai_api_key: Optional[SecretStr] = Field( + alias="api_key", + default_factory=secret_from_env("OPENAI_API_KEY", default=None), + ) openai_api_base: Optional[str] = Field(default=None, alias="base_url") """Base URL path for API requests, leave blank if not using a proxy or service emulator.""" openai_organization: Optional[str] = Field(default=None, alias="organization") """Automatically inferred from env var `OPENAI_ORG_ID` if not provided.""" # to support explicit proxy for OpenAI - openai_proxy: Optional[str] = None + openai_proxy: Optional[str] = Field( + default_factory=from_env("OPENAI_PROXY", default=None) + ) request_timeout: Union[float, Tuple[float, float], Any, None] = Field( default=None, alias="timeout" ) @@ -428,13 +431,11 @@ class BaseChatOpenAI(BaseChatModel): include_response_headers: bool = False """Whether to include response headers in the output message response_metadata.""" - class Config: - """Configuration for this pydantic object.""" + model_config = ConfigDict(populate_by_name=True,) - allow_population_by_field_name = True - - @root_validator(pre=True) - def build_extra(cls, values: Dict[str, Any]) -> Dict[str, Any]: + @model_validator(mode="before") + @classmethod + 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", {}) @@ -443,56 +444,49 @@ class BaseChatOpenAI(BaseChatModel): ) return values - @root_validator(pre=False, skip_on_failure=True, allow_reuse=True) - def validate_environment(cls, values: Dict) -> Dict: + @model_validator(mode="after") + def validate_environment(self) -> Self: """Validate that api key and python package exists in environment.""" - if values["n"] < 1: + if self.n < 1: raise ValueError("n must be at least 1.") - if values["n"] > 1 and values["streaming"]: + if self.n > 1 and self.streaming: raise ValueError("n must be 1 when streaming.") - values["openai_api_key"] = convert_to_secret_str( - get_from_dict_or_env(values, "openai_api_key", "OPENAI_API_KEY") - ) # Check OPENAI_ORGANIZATION for backwards compatibility. - values["openai_organization"] = ( - values["openai_organization"] + self.openai_organization = ( + self.openai_organization or os.getenv("OPENAI_ORG_ID") or os.getenv("OPENAI_ORGANIZATION") ) - values["openai_api_base"] = values["openai_api_base"] or os.getenv( + self.openai_api_base = self.openai_api_base or os.getenv( "OPENAI_API_BASE" ) - values["openai_proxy"] = get_from_dict_or_env( - values, "openai_proxy", "OPENAI_PROXY", default="" - ) - client_params = { "api_key": ( - values["openai_api_key"].get_secret_value() - if values["openai_api_key"] + self.openai_api_key.get_secret_value() + if self.openai_api_key else None ), - "organization": values["openai_organization"], - "base_url": values["openai_api_base"], - "timeout": values["request_timeout"], - "max_retries": values["max_retries"], - "default_headers": values["default_headers"], - "default_query": values["default_query"], + "organization": self.openai_organization, + "base_url": self.openai_api_base, + "timeout": self.request_timeout, + "max_retries": self.max_retries, + "default_headers": self.default_headers, + "default_query": self.default_query, } - if values["openai_proxy"] and ( - values["http_client"] or values["http_async_client"] + if self.openai_proxy and ( + self.http_client or self.http_async_client ): - openai_proxy = values["openai_proxy"] - http_client = values["http_client"] - http_async_client = values["http_async_client"] + openai_proxy = self.openai_proxy + http_client = self.http_client + http_async_client = self.http_async_client raise ValueError( "Cannot specify 'openai_proxy' if one of " "'http_client'/'http_async_client' is already specified. Received:\n" f"{openai_proxy=}\n{http_client=}\n{http_async_client=}" ) - if not values.get("client"): - if values["openai_proxy"] and not values["http_client"]: + if not (self.client or None): + if self.openai_proxy and not self.http_client: try: import httpx except ImportError as e: @@ -500,12 +494,12 @@ class BaseChatOpenAI(BaseChatModel): "Could not import httpx python package. " "Please install it with `pip install httpx`." ) from e - values["http_client"] = httpx.Client(proxy=values["openai_proxy"]) - sync_specific = {"http_client": values["http_client"]} - values["root_client"] = openai.OpenAI(**client_params, **sync_specific) - values["client"] = values["root_client"].chat.completions - if not values.get("async_client"): - if values["openai_proxy"] and not values["http_async_client"]: + self.http_client = httpx.Client(proxy=self.openai_proxy) + sync_specific = {"http_client": self.http_client} + self.root_client = openai.OpenAI(**client_params, **sync_specific) + self.client = self.root_client.chat.completions + if not (self.async_client or None): + if self.openai_proxy and not self.http_async_client: try: import httpx except ImportError as e: @@ -513,15 +507,15 @@ class BaseChatOpenAI(BaseChatModel): "Could not import httpx python package. " "Please install it with `pip install httpx`." ) from e - values["http_async_client"] = httpx.AsyncClient( - proxy=values["openai_proxy"] + self.http_async_client = httpx.AsyncClient( + proxy=self.openai_proxy ) - async_specific = {"http_client": values["http_async_client"]} - values["root_async_client"] = openai.AsyncOpenAI( + async_specific = {"http_client": self.http_async_client} + self.root_async_client = openai.AsyncOpenAI( **client_params, **async_specific ) - values["async_client"] = values["root_async_client"].chat.completions - return values + self.async_client = self.root_async_client.chat.completions + return self @property def _default_params(self) -> Dict[str, Any]: diff --git a/libs/partners/openai/langchain_openai/embeddings/azure.py b/libs/partners/openai/langchain_openai/embeddings/azure.py index 3725a766285..6697cee5392 100644 --- a/libs/partners/openai/langchain_openai/embeddings/azure.py +++ b/libs/partners/openai/langchain_openai/embeddings/azure.py @@ -5,10 +5,12 @@ from __future__ import annotations from typing import Callable, Dict, Optional, Union import openai -from langchain_core.pydantic_v1 import Field, SecretStr, root_validator +from pydantic import Field, SecretStr, root_validator, model_validator from langchain_core.utils import from_env, secret_from_env from langchain_openai.embeddings.base import OpenAIEmbeddings +from typing_extensions import Self + class AzureOpenAIEmbeddings(OpenAIEmbeddings): @@ -153,21 +155,21 @@ class AzureOpenAIEmbeddings(OpenAIEmbeddings): chunk_size: int = 2048 """Maximum number of texts to embed in each batch""" - @root_validator(pre=False, skip_on_failure=True) - def validate_environment(cls, values: Dict) -> Dict: + @model_validator(mode="after") + def validate_environment(self) -> Self: """Validate that api key and python package exists in environment.""" # For backwards compatibility. Before openai v1, no distinction was made # between azure_endpoint and base_url (openai_api_base). - openai_api_base = values["openai_api_base"] - if openai_api_base and values["validate_base_url"]: + openai_api_base = self.openai_api_base + if openai_api_base and self.validate_base_url: if "/openai" not in openai_api_base: - values["openai_api_base"] += "/openai" + self.openai_api_base += "/openai" raise ValueError( "As of openai>=1.0.0, Azure endpoints should be specified via " "the `azure_endpoint` param not `openai_api_base` " "(or alias `base_url`). " ) - if values["deployment"]: + if self.deployment: raise ValueError( "As of openai>=1.0.0, if `deployment` (or alias " "`azure_deployment`) is specified then " @@ -176,38 +178,38 @@ class AzureOpenAIEmbeddings(OpenAIEmbeddings): "and `azure_endpoint`." ) client_params = { - "api_version": values["openai_api_version"], - "azure_endpoint": values["azure_endpoint"], - "azure_deployment": values["deployment"], + "api_version": self.openai_api_version, + "azure_endpoint": self.azure_endpoint, + "azure_deployment": self.deployment, "api_key": ( - values["openai_api_key"].get_secret_value() - if values["openai_api_key"] + self.openai_api_key.get_secret_value() + if self.openai_api_key else None ), "azure_ad_token": ( - values["azure_ad_token"].get_secret_value() - if values["azure_ad_token"] + self.azure_ad_token.get_secret_value() + if self.azure_ad_token else None ), - "azure_ad_token_provider": values["azure_ad_token_provider"], - "organization": values["openai_organization"], - "base_url": values["openai_api_base"], - "timeout": values["request_timeout"], - "max_retries": values["max_retries"], - "default_headers": values["default_headers"], - "default_query": values["default_query"], + "azure_ad_token_provider": self.azure_ad_token_provider, + "organization": self.openai_organization, + "base_url": self.openai_api_base, + "timeout": self.request_timeout, + "max_retries": self.max_retries, + "default_headers": self.default_headers, + "default_query": self.default_query, } - if not values.get("client"): - sync_specific = {"http_client": values["http_client"]} - values["client"] = openai.AzureOpenAI( + if not (self.client or None): + sync_specific = {"http_client": self.http_client} + self.client = openai.AzureOpenAI( **client_params, **sync_specific ).embeddings - if not values.get("async_client"): - async_specific = {"http_client": values["http_async_client"]} - values["async_client"] = openai.AsyncAzureOpenAI( + if not (self.async_client or None): + async_specific = {"http_client": self.http_async_client} + self.async_client = openai.AsyncAzureOpenAI( **client_params, **async_specific ).embeddings - return values + return self @property def _llm_type(self) -> str: diff --git a/libs/partners/openai/langchain_openai/embeddings/base.py b/libs/partners/openai/langchain_openai/embeddings/base.py index 1a6a3a0417d..e6294d32ac2 100644 --- a/libs/partners/openai/langchain_openai/embeddings/base.py +++ b/libs/partners/openai/langchain_openai/embeddings/base.py @@ -20,8 +20,12 @@ from typing import ( import openai import tiktoken from langchain_core.embeddings import Embeddings -from langchain_core.pydantic_v1 import BaseModel, Field, SecretStr, root_validator +from pydantic import BaseModel, Field, SecretStr, root_validator, model_validator from langchain_core.utils import from_env, get_pydantic_field_names, secret_from_env +from pydantic import ConfigDict +from typing_extensions import Self + + logger = logging.getLogger(__name__) @@ -263,14 +267,11 @@ class OpenAIEmbeddings(BaseModel, Embeddings): """Whether to check the token length of inputs and automatically split inputs longer than embedding_ctx_length.""" - class Config: - """Configuration for this pydantic object.""" + model_config = ConfigDict(extra="forbid",populate_by_name=True,) - extra = "forbid" - allow_population_by_field_name = True - - @root_validator(pre=True) - def build_extra(cls, values: Dict[str, Any]) -> Dict[str, Any]: + @model_validator(mode="before") + @classmethod + 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", {}) @@ -295,41 +296,41 @@ class OpenAIEmbeddings(BaseModel, Embeddings): values["model_kwargs"] = extra return values - @root_validator(pre=False, skip_on_failure=True, allow_reuse=True) - def validate_environment(cls, values: Dict) -> Dict: + @model_validator(mode="after") + def validate_environment(self) -> Self: """Validate that api key and python package exists in environment.""" - if values["openai_api_type"] in ("azure", "azure_ad", "azuread"): + if self.openai_api_type in ("azure", "azure_ad", "azuread"): raise ValueError( "If you are using Azure, " "please use the `AzureOpenAIEmbeddings` class." ) client_params = { "api_key": ( - values["openai_api_key"].get_secret_value() - if values["openai_api_key"] + self.openai_api_key.get_secret_value() + if self.openai_api_key else None ), - "organization": values["openai_organization"], - "base_url": values["openai_api_base"], - "timeout": values["request_timeout"], - "max_retries": values["max_retries"], - "default_headers": values["default_headers"], - "default_query": values["default_query"], + "organization": self.openai_organization, + "base_url": self.openai_api_base, + "timeout": self.request_timeout, + "max_retries": self.max_retries, + "default_headers": self.default_headers, + "default_query": self.default_query, } - if values["openai_proxy"] and ( - values["http_client"] or values["http_async_client"] + if self.openai_proxy and ( + self.http_client or self.http_async_client ): - openai_proxy = values["openai_proxy"] - http_client = values["http_client"] - http_async_client = values["http_async_client"] + openai_proxy = self.openai_proxy + http_client = self.http_client + http_async_client = self.http_async_client raise ValueError( "Cannot specify 'openai_proxy' if one of " "'http_client'/'http_async_client' is already specified. Received:\n" f"{openai_proxy=}\n{http_client=}\n{http_async_client=}" ) - if not values.get("client"): - if values["openai_proxy"] and not values["http_client"]: + if not (self.client or None): + if self.openai_proxy and not self.http_client: try: import httpx except ImportError as e: @@ -337,13 +338,13 @@ class OpenAIEmbeddings(BaseModel, Embeddings): "Could not import httpx python package. " "Please install it with `pip install httpx`." ) from e - values["http_client"] = httpx.Client(proxy=values["openai_proxy"]) - sync_specific = {"http_client": values["http_client"]} - values["client"] = openai.OpenAI( + self.http_client = httpx.Client(proxy=self.openai_proxy) + sync_specific = {"http_client": self.http_client} + self.client = openai.OpenAI( **client_params, **sync_specific ).embeddings - if not values.get("async_client"): - if values["openai_proxy"] and not values["http_async_client"]: + if not (self.async_client or None): + if self.openai_proxy and not self.http_async_client: try: import httpx except ImportError as e: @@ -351,14 +352,14 @@ class OpenAIEmbeddings(BaseModel, Embeddings): "Could not import httpx python package. " "Please install it with `pip install httpx`." ) from e - values["http_async_client"] = httpx.AsyncClient( - proxy=values["openai_proxy"] + self.http_async_client = httpx.AsyncClient( + proxy=self.openai_proxy ) - async_specific = {"http_client": values["http_async_client"]} - values["async_client"] = openai.AsyncOpenAI( + async_specific = {"http_client": self.http_async_client} + self.async_client = openai.AsyncOpenAI( **client_params, **async_specific ).embeddings - return values + return self @property def _invocation_params(self) -> Dict[str, Any]: diff --git a/libs/partners/openai/langchain_openai/llms/azure.py b/libs/partners/openai/langchain_openai/llms/azure.py index 0d091b325f5..e48b1380ff7 100644 --- a/libs/partners/openai/langchain_openai/llms/azure.py +++ b/libs/partners/openai/langchain_openai/llms/azure.py @@ -5,10 +5,12 @@ from typing import Any, Callable, Dict, List, Mapping, Optional, Union import openai from langchain_core.language_models import LangSmithParams -from langchain_core.pydantic_v1 import Field, SecretStr, root_validator +from pydantic import Field, SecretStr, root_validator, model_validator from langchain_core.utils import from_env, secret_from_env from langchain_openai.llms.base import BaseOpenAI +from typing_extensions import Self + logger = logging.getLogger(__name__) @@ -100,29 +102,29 @@ class AzureOpenAI(BaseOpenAI): """Return whether this model can be serialized by Langchain.""" return True - @root_validator(pre=False, skip_on_failure=True, allow_reuse=True) - def validate_environment(cls, values: Dict) -> Dict: + @model_validator(mode="after") + def validate_environment(self) -> Self: """Validate that api key and python package exists in environment.""" - if values["n"] < 1: + if self.n < 1: raise ValueError("n must be at least 1.") - if values["streaming"] and values["n"] > 1: + if self.streaming and self.n > 1: raise ValueError("Cannot stream results when n > 1.") - if values["streaming"] and values["best_of"] > 1: + if self.streaming and self.best_of > 1: raise ValueError("Cannot stream results when best_of > 1.") # For backwards compatibility. Before openai v1, no distinction was made # between azure_endpoint and base_url (openai_api_base). - openai_api_base = values["openai_api_base"] - if openai_api_base and values["validate_base_url"]: + openai_api_base = self.openai_api_base + if openai_api_base and self.validate_base_url: if "/openai" not in openai_api_base: - values["openai_api_base"] = ( - values["openai_api_base"].rstrip("/") + "/openai" + self.openai_api_base = ( + self.openai_api_base.rstrip("/") + "/openai" ) raise ValueError( "As of openai>=1.0.0, Azure endpoints should be specified via " "the `azure_endpoint` param not `openai_api_base` " "(or alias `base_url`)." ) - if values["deployment_name"]: + if self.deployment_name: raise ValueError( "As of openai>=1.0.0, if `deployment_name` (or alias " "`azure_deployment`) is specified then " @@ -130,37 +132,37 @@ class AzureOpenAI(BaseOpenAI): "Instead use `deployment_name` (or alias `azure_deployment`) " "and `azure_endpoint`." ) - values["deployment_name"] = None + self.deployment_name = None client_params = { - "api_version": values["openai_api_version"], - "azure_endpoint": values["azure_endpoint"], - "azure_deployment": values["deployment_name"], - "api_key": values["openai_api_key"].get_secret_value() - if values["openai_api_key"] + "api_version": self.openai_api_version, + "azure_endpoint": self.azure_endpoint, + "azure_deployment": self.deployment_name, + "api_key": self.openai_api_key.get_secret_value() + if self.openai_api_key else None, - "azure_ad_token": values["azure_ad_token"].get_secret_value() - if values["azure_ad_token"] + "azure_ad_token": self.azure_ad_token.get_secret_value() + if self.azure_ad_token else None, - "azure_ad_token_provider": values["azure_ad_token_provider"], - "organization": values["openai_organization"], - "base_url": values["openai_api_base"], - "timeout": values["request_timeout"], - "max_retries": values["max_retries"], - "default_headers": values["default_headers"], - "default_query": values["default_query"], + "azure_ad_token_provider": self.azure_ad_token_provider, + "organization": self.openai_organization, + "base_url": self.openai_api_base, + "timeout": self.request_timeout, + "max_retries": self.max_retries, + "default_headers": self.default_headers, + "default_query": self.default_query, } - if not values.get("client"): - sync_specific = {"http_client": values["http_client"]} - values["client"] = openai.AzureOpenAI( + if not (self.client or None): + sync_specific = {"http_client": self.http_client} + self.client = openai.AzureOpenAI( **client_params, **sync_specific ).completions - if not values.get("async_client"): - async_specific = {"http_client": values["http_async_client"]} - values["async_client"] = openai.AsyncAzureOpenAI( + if not (self.async_client or None): + async_specific = {"http_client": self.http_async_client} + self.async_client = openai.AsyncAzureOpenAI( **client_params, **async_specific ).completions - return values + return self @property def _identifying_params(self) -> Mapping[str, Any]: diff --git a/libs/partners/openai/langchain_openai/llms/base.py b/libs/partners/openai/langchain_openai/llms/base.py index 464b40e2ba9..0db987c5e0d 100644 --- a/libs/partners/openai/langchain_openai/llms/base.py +++ b/libs/partners/openai/langchain_openai/llms/base.py @@ -26,9 +26,13 @@ from langchain_core.callbacks import ( ) from langchain_core.language_models.llms import BaseLLM from langchain_core.outputs import Generation, GenerationChunk, LLMResult -from langchain_core.pydantic_v1 import Field, SecretStr, root_validator +from pydantic import Field, SecretStr, root_validator, model_validator from langchain_core.utils import get_pydantic_field_names from langchain_core.utils.utils import build_extra_kwargs, from_env, secret_from_env +from pydantic import ConfigDict +from typing_extensions import Self + + logger = logging.getLogger(__name__) @@ -152,13 +156,11 @@ class BaseOpenAI(BaseLLM): """Optional additional JSON properties to include in the request parameters when making requests to OpenAI compatible APIs, such as vLLM.""" - class Config: - """Configuration for this pydantic object.""" + model_config = ConfigDict(populate_by_name=True,) - allow_population_by_field_name = True - - @root_validator(pre=True) - def build_extra(cls, values: Dict[str, Any]) -> Dict[str, Any]: + @model_validator(mode="before") + @classmethod + 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", {}) @@ -167,41 +169,41 @@ class BaseOpenAI(BaseLLM): ) return values - @root_validator(pre=False, skip_on_failure=True, allow_reuse=True) - def validate_environment(cls, values: Dict) -> Dict: + @model_validator(mode="after") + def validate_environment(self) -> Self: """Validate that api key and python package exists in environment.""" - if values["n"] < 1: + if self.n < 1: raise ValueError("n must be at least 1.") - if values["streaming"] and values["n"] > 1: + if self.streaming and self.n > 1: raise ValueError("Cannot stream results when n > 1.") - if values["streaming"] and values["best_of"] > 1: + if self.streaming and self.best_of > 1: raise ValueError("Cannot stream results when best_of > 1.") client_params = { "api_key": ( - values["openai_api_key"].get_secret_value() - if values["openai_api_key"] + self.openai_api_key.get_secret_value() + if self.openai_api_key else None ), - "organization": values["openai_organization"], - "base_url": values["openai_api_base"], - "timeout": values["request_timeout"], - "max_retries": values["max_retries"], - "default_headers": values["default_headers"], - "default_query": values["default_query"], + "organization": self.openai_organization, + "base_url": self.openai_api_base, + "timeout": self.request_timeout, + "max_retries": self.max_retries, + "default_headers": self.default_headers, + "default_query": self.default_query, } - if not values.get("client"): - sync_specific = {"http_client": values["http_client"]} - values["client"] = openai.OpenAI( + if not (self.client or None): + sync_specific = {"http_client": self.http_client} + self.client = openai.OpenAI( **client_params, **sync_specific ).completions - if not values.get("async_client"): - async_specific = {"http_client": values["http_async_client"]} - values["async_client"] = openai.AsyncOpenAI( + if not (self.async_client or None): + async_specific = {"http_client": self.http_async_client} + self.async_client = openai.AsyncOpenAI( **client_params, **async_specific ).completions - return values + return self @property def _default_params(self) -> Dict[str, Any]: diff --git a/libs/partners/openai/tests/integration_tests/chat_models/test_base.py b/libs/partners/openai/tests/integration_tests/chat_models/test_base.py index 2e235421f93..6f2a39bffdb 100644 --- a/libs/partners/openai/tests/integration_tests/chat_models/test_base.py +++ b/libs/partners/openai/tests/integration_tests/chat_models/test_base.py @@ -20,7 +20,7 @@ from langchain_core.messages import ( ) from langchain_core.outputs import ChatGeneration, ChatResult, LLMResult from langchain_core.prompts import ChatPromptTemplate -from langchain_core.pydantic_v1 import BaseModel, Field +from pydantic import BaseModel, Field from langchain_standard_tests.integration_tests.chat_models import ( _validate_tool_call_message, ) diff --git a/libs/partners/openai/tests/unit_tests/chat_models/test_base.py b/libs/partners/openai/tests/unit_tests/chat_models/test_base.py index 4e959f00599..493ca5abeff 100644 --- a/libs/partners/openai/tests/unit_tests/chat_models/test_base.py +++ b/libs/partners/openai/tests/unit_tests/chat_models/test_base.py @@ -17,7 +17,7 @@ from langchain_core.messages import ( ToolMessage, ) from langchain_core.messages.ai import UsageMetadata -from langchain_core.pydantic_v1 import BaseModel +from pydantic import BaseModel from langchain_openai import ChatOpenAI from langchain_openai.chat_models.base import ( diff --git a/libs/partners/openai/tests/unit_tests/fake/callbacks.py b/libs/partners/openai/tests/unit_tests/fake/callbacks.py index 2beee4a2dde..e786601aaa7 100644 --- a/libs/partners/openai/tests/unit_tests/fake/callbacks.py +++ b/libs/partners/openai/tests/unit_tests/fake/callbacks.py @@ -6,7 +6,7 @@ from uuid import UUID from langchain_core.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler from langchain_core.messages import BaseMessage -from langchain_core.pydantic_v1 import BaseModel +from pydantic import BaseModel class BaseFakeCallbackHandler(BaseModel): diff --git a/libs/partners/openai/tests/unit_tests/test_load.py b/libs/partners/openai/tests/unit_tests/test_load.py index 059a10b4bf9..d25b00a0b87 100644 --- a/libs/partners/openai/tests/unit_tests/test_load.py +++ b/libs/partners/openai/tests/unit_tests/test_load.py @@ -9,7 +9,7 @@ def test_loads_openai_llm() -> None: llm_string = dumps(llm) llm2 = loads(llm_string, secrets_map={"OPENAI_API_KEY": "hello"}) - assert llm2 == llm + assert llm2.dict() == llm.dict() llm_string_2 = dumps(llm2) assert llm_string_2 == llm_string assert isinstance(llm2, OpenAI) @@ -20,7 +20,7 @@ def test_load_openai_llm() -> None: llm_obj = dumpd(llm) llm2 = load(llm_obj, secrets_map={"OPENAI_API_KEY": "hello"}) - assert llm2 == llm + assert llm2.dict() == llm.dict() assert dumpd(llm2) == llm_obj assert isinstance(llm2, OpenAI) @@ -30,7 +30,7 @@ def test_loads_openai_chat() -> None: llm_string = dumps(llm) llm2 = loads(llm_string, secrets_map={"OPENAI_API_KEY": "hello"}) - assert llm2 == llm + assert llm2.dict() == llm.dict() llm_string_2 = dumps(llm2) assert llm_string_2 == llm_string assert isinstance(llm2, ChatOpenAI) @@ -41,6 +41,6 @@ def test_load_openai_chat() -> None: llm_obj = dumpd(llm) llm2 = load(llm_obj, secrets_map={"OPENAI_API_KEY": "hello"}) - assert llm2 == llm + assert llm2.dict() == llm.dict() assert dumpd(llm2) == llm_obj assert isinstance(llm2, ChatOpenAI) diff --git a/libs/partners/openai/tests/unit_tests/test_secrets.py b/libs/partners/openai/tests/unit_tests/test_secrets.py index 0fb13e35b28..2c88858056c 100644 --- a/libs/partners/openai/tests/unit_tests/test_secrets.py +++ b/libs/partners/openai/tests/unit_tests/test_secrets.py @@ -2,7 +2,7 @@ from typing import Type, cast import pytest from langchain_core.load import dumpd -from langchain_core.pydantic_v1 import SecretStr +from pydantic import SecretStr from pytest import CaptureFixture, MonkeyPatch from langchain_openai import ( diff --git a/libs/standard-tests/langchain_standard_tests/unit_tests/chat_models.py b/libs/standard-tests/langchain_standard_tests/unit_tests/chat_models.py index f3a49b6c341..e7786e580bb 100644 --- a/libs/standard-tests/langchain_standard_tests/unit_tests/chat_models.py +++ b/libs/standard-tests/langchain_standard_tests/unit_tests/chat_models.py @@ -6,7 +6,7 @@ from unittest import mock import pytest from langchain_core.language_models import BaseChatModel -from langchain_core.pydantic_v1 import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field, SecretStr from langchain_core.runnables import RunnableBinding from langchain_core.tools import tool diff --git a/libs/standard-tests/langchain_standard_tests/unit_tests/embeddings.py b/libs/standard-tests/langchain_standard_tests/unit_tests/embeddings.py index 0a6e793c063..4638af51745 100644 --- a/libs/standard-tests/langchain_standard_tests/unit_tests/embeddings.py +++ b/libs/standard-tests/langchain_standard_tests/unit_tests/embeddings.py @@ -5,7 +5,7 @@ from unittest import mock import pytest from langchain_core.embeddings import Embeddings -from langchain_core.pydantic_v1 import SecretStr +from pydantic import SecretStr from langchain_standard_tests.base import BaseStandardTests