mirror of
https://github.com/hwchase17/langchain.git
synced 2026-06-09 10:17:00 +00:00
multiple: pydantic 2 compatibility, v0.3 (#26443)
Signed-off-by: ChengZi <chen.zhang@zilliz.com> Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com> Co-authored-by: Bagatur <22008038+baskaryan@users.noreply.github.com> Co-authored-by: Dan O'Donovan <dan.odonovan@gmail.com> Co-authored-by: Tom Daniel Grande <tomdgrande@gmail.com> Co-authored-by: Grande <Tom.Daniel.Grande@statsbygg.no> Co-authored-by: Bagatur <baskaryan@gmail.com> Co-authored-by: ccurme <chester.curme@gmail.com> Co-authored-by: Harrison Chase <hw.chase.17@gmail.com> Co-authored-by: Tomaz Bratanic <bratanic.tomaz@gmail.com> Co-authored-by: ZhangShenao <15201440436@163.com> Co-authored-by: Friso H. Kingma <fhkingma@gmail.com> Co-authored-by: ChengZi <chen.zhang@zilliz.com> Co-authored-by: Nuno Campos <nuno@langchain.dev> Co-authored-by: Morgante Pell <morgantep@google.com>
This commit is contained in:
@@ -49,12 +49,6 @@ from langchain_core.output_parsers import (
|
||||
)
|
||||
from langchain_core.output_parsers.base import OutputParserLike
|
||||
from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult
|
||||
from langchain_core.pydantic_v1 import (
|
||||
BaseModel,
|
||||
Field,
|
||||
SecretStr,
|
||||
root_validator,
|
||||
)
|
||||
from langchain_core.runnables import (
|
||||
Runnable,
|
||||
RunnableMap,
|
||||
@@ -69,7 +63,15 @@ from langchain_core.utils import (
|
||||
)
|
||||
from langchain_core.utils.function_calling import convert_to_openai_tool
|
||||
from langchain_core.utils.pydantic import is_basemodel_subclass
|
||||
from typing_extensions import NotRequired
|
||||
from pydantic import (
|
||||
BaseModel,
|
||||
ConfigDict,
|
||||
Field,
|
||||
PrivateAttr,
|
||||
SecretStr,
|
||||
model_validator,
|
||||
)
|
||||
from typing_extensions import NotRequired, Self
|
||||
|
||||
from langchain_anthropic.output_parsers import extract_tool_calls
|
||||
|
||||
@@ -114,7 +116,7 @@ def _merge_messages(
|
||||
"""Merge runs of human/tool messages into single human messages with content blocks.""" # noqa: E501
|
||||
merged: list = []
|
||||
for curr in messages:
|
||||
curr = curr.copy(deep=True)
|
||||
curr = curr.model_copy(deep=True)
|
||||
if isinstance(curr, ToolMessage):
|
||||
if isinstance(curr.content, list) and all(
|
||||
isinstance(block, dict) and block.get("type") == "tool_result"
|
||||
@@ -383,7 +385,7 @@ class ChatAnthropic(BaseChatModel):
|
||||
Tool calling:
|
||||
.. code-block:: python
|
||||
|
||||
from langchain_core.pydantic_v1 import BaseModel, Field
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class GetWeather(BaseModel):
|
||||
'''Get the current weather in a given location'''
|
||||
@@ -421,7 +423,7 @@ class ChatAnthropic(BaseChatModel):
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from langchain_core.pydantic_v1 import BaseModel, Field
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class Joke(BaseModel):
|
||||
'''Joke to tell user.'''
|
||||
@@ -508,13 +510,12 @@ class ChatAnthropic(BaseChatModel):
|
||||
|
||||
""" # noqa: E501
|
||||
|
||||
class Config:
|
||||
"""Configuration for this pydantic object."""
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
)
|
||||
|
||||
allow_population_by_field_name = True
|
||||
|
||||
_client: anthropic.Client = Field(default=None)
|
||||
_async_client: anthropic.AsyncClient = Field(default=None)
|
||||
_client: anthropic.Client = PrivateAttr(default=None)
|
||||
_async_client: anthropic.AsyncClient = PrivateAttr(default=None)
|
||||
|
||||
model: str = Field(alias="model_name")
|
||||
"""Model name to use."""
|
||||
@@ -626,8 +627,9 @@ class ChatAnthropic(BaseChatModel):
|
||||
ls_params["ls_stop"] = ls_stop
|
||||
return ls_params
|
||||
|
||||
@root_validator(pre=True)
|
||||
def build_extra(cls, values: Dict) -> Dict:
|
||||
@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(
|
||||
@@ -635,28 +637,25 @@ class ChatAnthropic(BaseChatModel):
|
||||
)
|
||||
return values
|
||||
|
||||
@root_validator(pre=False, skip_on_failure=True)
|
||||
def post_init(cls, values: Dict) -> Dict:
|
||||
api_key = values["anthropic_api_key"].get_secret_value()
|
||||
api_url = values["anthropic_api_url"]
|
||||
client_params = {
|
||||
@model_validator(mode="after")
|
||||
def post_init(self) -> Self:
|
||||
api_key = self.anthropic_api_key.get_secret_value()
|
||||
api_url = self.anthropic_api_url
|
||||
client_params: Dict[str, Any] = {
|
||||
"api_key": api_key,
|
||||
"base_url": api_url,
|
||||
"max_retries": values["max_retries"],
|
||||
"default_headers": values.get("default_headers"),
|
||||
"max_retries": self.max_retries,
|
||||
"default_headers": (self.default_headers or None),
|
||||
}
|
||||
# value <= 0 indicates the param should be ignored. None is a meaningful value
|
||||
# for Anthropic client and treated differently than not specifying the param at
|
||||
# all.
|
||||
if (
|
||||
values["default_request_timeout"] is None
|
||||
or values["default_request_timeout"] > 0
|
||||
):
|
||||
client_params["timeout"] = values["default_request_timeout"]
|
||||
if self.default_request_timeout is None or self.default_request_timeout > 0:
|
||||
client_params["timeout"] = self.default_request_timeout
|
||||
|
||||
values["_client"] = anthropic.Client(**client_params)
|
||||
values["_async_client"] = anthropic.AsyncClient(**client_params)
|
||||
return values
|
||||
self._client = anthropic.Client(**client_params)
|
||||
self._async_client = anthropic.AsyncClient(**client_params)
|
||||
return self
|
||||
|
||||
def _get_request_payload(
|
||||
self,
|
||||
@@ -803,21 +802,17 @@ class ChatAnthropic(BaseChatModel):
|
||||
] = None,
|
||||
**kwargs: Any,
|
||||
) -> Runnable[LanguageModelInput, BaseMessage]:
|
||||
"""Bind tool-like objects to this chat model.
|
||||
r"""Bind tool-like objects to this chat model.
|
||||
|
||||
Args:
|
||||
tools: A list of tool definitions to bind to this chat model.
|
||||
Supports Anthropic format tool schemas and any tool definition handled
|
||||
by :meth:`langchain_core.utils.function_calling.convert_to_openai_tool`.
|
||||
tool_choice: Which tool to require the model to call.
|
||||
Options are:
|
||||
- name of the tool (str): calls corresponding tool;
|
||||
- ``"auto"`` or None: automatically selects a tool (including no tool);
|
||||
- ``"any"``: force at least one tool to be called;
|
||||
- or a dict of the form:
|
||||
``{"type": "tool", "name": "tool_name"}``,
|
||||
or ``{"type: "any"}``,
|
||||
or ``{"type: "auto"}``;
|
||||
tool_choice: Which tool to require the model to call. Options are:
|
||||
|
||||
- name of the tool as a string or as dict ``{"type": "tool", "name": "<<tool_name>>"}``: calls corresponding tool;
|
||||
- ``"auto"``, ``{"type: "auto"}``, or None: automatically selects a tool (including no tool);
|
||||
- ``"any"`` or ``{"type: "any"}``: force at least one tool to be called;
|
||||
kwargs: Any additional parameters are passed directly to
|
||||
``self.bind(**kwargs)``.
|
||||
|
||||
@@ -825,7 +820,7 @@ class ChatAnthropic(BaseChatModel):
|
||||
.. code-block:: python
|
||||
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langchain_core.pydantic_v1 import BaseModel, Field
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class GetWeather(BaseModel):
|
||||
'''Get the current weather in a given location'''
|
||||
@@ -854,7 +849,7 @@ class ChatAnthropic(BaseChatModel):
|
||||
.. code-block:: python
|
||||
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langchain_core.pydantic_v1 import BaseModel, Field
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class GetWeather(BaseModel):
|
||||
'''Get the current weather in a given location'''
|
||||
@@ -876,7 +871,7 @@ class ChatAnthropic(BaseChatModel):
|
||||
.. code-block:: python
|
||||
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langchain_core.pydantic_v1 import BaseModel, Field
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class GetWeather(BaseModel):
|
||||
'''Get the current weather in a given location'''
|
||||
@@ -897,7 +892,7 @@ class ChatAnthropic(BaseChatModel):
|
||||
.. code-block:: python
|
||||
|
||||
from langchain_anthropic import ChatAnthropic, convert_to_anthropic_tool
|
||||
from langchain_core.pydantic_v1 import BaseModel, Field
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class GetWeather(BaseModel):
|
||||
'''Get the current weather in a given location'''
|
||||
@@ -928,12 +923,14 @@ class ChatAnthropic(BaseChatModel):
|
||||
llm_with_tools.invoke("what is the weather like in San Francisco",)
|
||||
|
||||
This outputs:
|
||||
.. code-block:: pycon
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
AIMessage(content=[{'text': "Certainly! I can help you find out the current weather in San Francisco. To get this information, I'll use the GetWeather function. Let me fetch that data for you right away.", 'type': 'text'}, {'id': 'toolu_01TS5h8LNo7p5imcG7yRiaUM', 'input': {'location': 'San Francisco, CA'}, 'name': 'GetWeather', 'type': 'tool_use'}], response_metadata={'id': 'msg_01Xg7Wr5inFWgBxE5jH9rpRo', 'model': 'claude-3-5-sonnet-20240620', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 171, 'output_tokens': 96, 'cache_creation_input_tokens': 1470, 'cache_read_input_tokens': 0}}, id='run-b36a5b54-5d69-470e-a1b0-b932d00b089e-0', tool_calls=[{'name': 'GetWeather', 'args': {'location': 'San Francisco, CA'}, 'id': 'toolu_01TS5h8LNo7p5imcG7yRiaUM', 'type': 'tool_call'}], usage_metadata={'input_tokens': 171, 'output_tokens': 96, 'total_tokens': 267})
|
||||
|
||||
If we invoke the tool again, we can see that the "usage" information in the AIMessage.response_metadata shows that we had a cache hit:
|
||||
.. code-block:: pycon
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
AIMessage(content=[{'text': 'To get the current weather in San Francisco, I can use the GetWeather function. Let me check that for you.', 'type': 'text'}, {'id': 'toolu_01HtVtY1qhMFdPprx42qU2eA', 'input': {'location': 'San Francisco, CA'}, 'name': 'GetWeather', 'type': 'tool_use'}], response_metadata={'id': 'msg_016RfWHrRvW6DAGCdwB6Ac64', 'model': 'claude-3-5-sonnet-20240620', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 171, 'output_tokens': 82, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 1470}}, id='run-88b1f825-dcb7-4277-ac27-53df55d22001-0', tool_calls=[{'name': 'GetWeather', 'args': {'location': 'San Francisco, CA'}, 'id': 'toolu_01HtVtY1qhMFdPprx42qU2eA', 'type': 'tool_call'}], usage_metadata={'input_tokens': 171, 'output_tokens': 82, 'total_tokens': 253})
|
||||
|
||||
@@ -1007,7 +1004,7 @@ class ChatAnthropic(BaseChatModel):
|
||||
.. code-block:: python
|
||||
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langchain_core.pydantic_v1 import BaseModel
|
||||
from pydantic import BaseModel
|
||||
|
||||
class AnswerWithJustification(BaseModel):
|
||||
'''An answer to the user question along with justification for the answer.'''
|
||||
@@ -1028,7 +1025,7 @@ class ChatAnthropic(BaseChatModel):
|
||||
.. code-block:: python
|
||||
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langchain_core.pydantic_v1 import BaseModel
|
||||
from pydantic import BaseModel
|
||||
|
||||
class AnswerWithJustification(BaseModel):
|
||||
'''An answer to the user question along with justification for the answer.'''
|
||||
|
||||
@@ -7,7 +7,7 @@ from typing import (
|
||||
)
|
||||
|
||||
from langchain_core._api import deprecated
|
||||
from langchain_core.pydantic_v1 import Field
|
||||
from pydantic import PrivateAttr
|
||||
|
||||
from langchain_anthropic.chat_models import ChatAnthropic
|
||||
|
||||
@@ -156,4 +156,4 @@ def _xml_to_tool_calls(elem: Any, tools: List[Dict]) -> List[Dict[str, Any]]:
|
||||
class ChatAnthropicTools(ChatAnthropic):
|
||||
"""Chat model for interacting with Anthropic functions."""
|
||||
|
||||
_xmllib: Any = Field(default=None)
|
||||
_xmllib: Any = PrivateAttr(default=None)
|
||||
|
||||
@@ -21,7 +21,6 @@ from langchain_core.language_models import BaseLanguageModel, LangSmithParams
|
||||
from langchain_core.language_models.llms import LLM
|
||||
from langchain_core.outputs import GenerationChunk
|
||||
from langchain_core.prompt_values import PromptValue
|
||||
from langchain_core.pydantic_v1 import Field, SecretStr, root_validator
|
||||
from langchain_core.utils import (
|
||||
get_pydantic_field_names,
|
||||
)
|
||||
@@ -30,6 +29,8 @@ from langchain_core.utils.utils import (
|
||||
from_env,
|
||||
secret_from_env,
|
||||
)
|
||||
from pydantic import ConfigDict, Field, SecretStr, model_validator
|
||||
from typing_extensions import Self
|
||||
|
||||
|
||||
class _AnthropicCommon(BaseLanguageModel):
|
||||
@@ -84,8 +85,9 @@ class _AnthropicCommon(BaseLanguageModel):
|
||||
count_tokens: Optional[Callable[[str], int]] = None
|
||||
model_kwargs: Dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
@root_validator(pre=True)
|
||||
def build_extra(cls, values: Dict) -> Dict:
|
||||
@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(
|
||||
@@ -93,25 +95,25 @@ class _AnthropicCommon(BaseLanguageModel):
|
||||
)
|
||||
return values
|
||||
|
||||
@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."""
|
||||
values["client"] = anthropic.Anthropic(
|
||||
base_url=values["anthropic_api_url"],
|
||||
api_key=values["anthropic_api_key"].get_secret_value(),
|
||||
timeout=values["default_request_timeout"],
|
||||
max_retries=values["max_retries"],
|
||||
self.client = anthropic.Anthropic(
|
||||
base_url=self.anthropic_api_url,
|
||||
api_key=self.anthropic_api_key.get_secret_value(),
|
||||
timeout=self.default_request_timeout,
|
||||
max_retries=self.max_retries,
|
||||
)
|
||||
values["async_client"] = anthropic.AsyncAnthropic(
|
||||
base_url=values["anthropic_api_url"],
|
||||
api_key=values["anthropic_api_key"].get_secret_value(),
|
||||
timeout=values["default_request_timeout"],
|
||||
max_retries=values["max_retries"],
|
||||
self.async_client = anthropic.AsyncAnthropic(
|
||||
base_url=self.anthropic_api_url,
|
||||
api_key=self.anthropic_api_key.get_secret_value(),
|
||||
timeout=self.default_request_timeout,
|
||||
max_retries=self.max_retries,
|
||||
)
|
||||
values["HUMAN_PROMPT"] = anthropic.HUMAN_PROMPT
|
||||
values["AI_PROMPT"] = anthropic.AI_PROMPT
|
||||
values["count_tokens"] = values["client"].count_tokens
|
||||
return values
|
||||
self.HUMAN_PROMPT = anthropic.HUMAN_PROMPT
|
||||
self.AI_PROMPT = anthropic.AI_PROMPT
|
||||
self.count_tokens = self.client.count_tokens
|
||||
return self
|
||||
|
||||
@property
|
||||
def _default_params(self) -> Mapping[str, Any]:
|
||||
@@ -160,14 +162,14 @@ class AnthropicLLM(LLM, _AnthropicCommon):
|
||||
model = AnthropicLLM()
|
||||
"""
|
||||
|
||||
class Config:
|
||||
"""Configuration for this pydantic object."""
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
arbitrary_types_allowed=True,
|
||||
)
|
||||
|
||||
allow_population_by_field_name = True
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
@root_validator(pre=True)
|
||||
def raise_warning(cls, values: Dict) -> Dict:
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def raise_warning(cls, values: Dict) -> Any:
|
||||
"""Raise warning that this class is deprecated."""
|
||||
warnings.warn(
|
||||
"This Anthropic LLM is deprecated. "
|
||||
|
||||
@@ -4,7 +4,7 @@ from langchain_core.messages import AIMessage, ToolCall
|
||||
from langchain_core.messages.tool import tool_call
|
||||
from langchain_core.output_parsers import BaseGenerationOutputParser
|
||||
from langchain_core.outputs import ChatGeneration, Generation
|
||||
from langchain_core.pydantic_v1 import BaseModel
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
class ToolsOutputParser(BaseGenerationOutputParser):
|
||||
@@ -17,8 +17,9 @@ class ToolsOutputParser(BaseGenerationOutputParser):
|
||||
pydantic_schemas: Optional[List[Type[BaseModel]]] = None
|
||||
"""Pydantic schemas to parse tool calls into."""
|
||||
|
||||
class Config:
|
||||
extra = "forbid"
|
||||
model_config = ConfigDict(
|
||||
extra="forbid",
|
||||
)
|
||||
|
||||
def parse_result(self, result: List[Generation], *, partial: bool = False) -> Any:
|
||||
"""Parse a list of candidate model Generations into a specific format.
|
||||
|
||||
Reference in New Issue
Block a user