From 88fce67724123d4cabacffbc8a07ba97534a11ff Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Wed, 16 Apr 2025 12:00:08 -0400 Subject: [PATCH] core: Removing unnecessary `pydantic` core schema rebuilds (#30848) We only need to rebuild model schemas if type annotation information isn't available during declaration - that shouldn't be the case for these types corrected here. Need to do more thorough testing to make sure these structures have complete schemas, but hopefully this boosts startup / import time. --- libs/core/langchain_core/messages/ai.py | 3 --- libs/core/langchain_core/messages/chat.py | 3 --- libs/core/langchain_core/messages/function.py | 3 --- libs/core/langchain_core/messages/human.py | 3 --- libs/core/langchain_core/messages/modifier.py | 3 --- libs/core/langchain_core/messages/system.py | 3 --- libs/core/langchain_core/messages/tool.py | 3 --- .../langchain_core/output_parsers/list.py | 3 --- .../langchain_core/output_parsers/pydantic.py | 3 --- .../langchain_core/output_parsers/string.py | 3 --- libs/core/langchain_core/prompts/pipeline.py | 3 --- libs/core/langchain_core/runnables/base.py | 3 --- .../langchain_core/runnables/configurable.py | 4 ---- libs/core/langchain_core/runnables/utils.py | 3 +-- libs/core/langchain_core/tools/simple.py | 3 --- .../chat_models/test_rate_limiting.py | 3 --- .../output_parsers/test_base_parsers.py | 2 -- .../unit_tests/prompts/test_structured.py | 3 --- .../runnables/test_runnable_events_v1.py | 3 --- .../tests/unit_tests/test_pydantic_imports.py | 20 +++++++++++++++++++ libs/core/tests/unit_tests/test_tools.py | 3 --- uv.lock | 9 +++++---- 22 files changed, 26 insertions(+), 63 deletions(-) create mode 100644 libs/core/tests/unit_tests/test_pydantic_imports.py diff --git a/libs/core/langchain_core/messages/ai.py b/libs/core/langchain_core/messages/ai.py index 6c2a08e6db1..918f717aa58 100644 --- a/libs/core/langchain_core/messages/ai.py +++ b/libs/core/langchain_core/messages/ai.py @@ -276,9 +276,6 @@ class AIMessage(BaseMessage): return (base.strip() + "\n" + "\n".join(lines)).strip() -AIMessage.model_rebuild() - - class AIMessageChunk(AIMessage, BaseMessageChunk): """Message chunk from an AI.""" diff --git a/libs/core/langchain_core/messages/chat.py b/libs/core/langchain_core/messages/chat.py index c9f11f976b6..a4791423fad 100644 --- a/libs/core/langchain_core/messages/chat.py +++ b/libs/core/langchain_core/messages/chat.py @@ -22,9 +22,6 @@ class ChatMessage(BaseMessage): """The type of the message (used during serialization). Defaults to "chat".""" -ChatMessage.model_rebuild() - - class ChatMessageChunk(ChatMessage, BaseMessageChunk): """Chat Message chunk.""" diff --git a/libs/core/langchain_core/messages/function.py b/libs/core/langchain_core/messages/function.py index c79d54ae5b2..fc1018775b7 100644 --- a/libs/core/langchain_core/messages/function.py +++ b/libs/core/langchain_core/messages/function.py @@ -30,9 +30,6 @@ class FunctionMessage(BaseMessage): """The type of the message (used for serialization). Defaults to "function".""" -FunctionMessage.model_rebuild() - - class FunctionMessageChunk(FunctionMessage, BaseMessageChunk): """Function Message chunk.""" diff --git a/libs/core/langchain_core/messages/human.py b/libs/core/langchain_core/messages/human.py index 5baa3a38856..4e19904ab33 100644 --- a/libs/core/langchain_core/messages/human.py +++ b/libs/core/langchain_core/messages/human.py @@ -52,9 +52,6 @@ class HumanMessage(BaseMessage): super().__init__(content=content, **kwargs) -HumanMessage.model_rebuild() - - class HumanMessageChunk(HumanMessage, BaseMessageChunk): """Human Message chunk.""" diff --git a/libs/core/langchain_core/messages/modifier.py b/libs/core/langchain_core/messages/modifier.py index aa43b82adca..9cfc89c5760 100644 --- a/libs/core/langchain_core/messages/modifier.py +++ b/libs/core/langchain_core/messages/modifier.py @@ -26,6 +26,3 @@ class RemoveMessage(BaseMessage): raise ValueError(msg) super().__init__("", id=id, **kwargs) - - -RemoveMessage.model_rebuild() diff --git a/libs/core/langchain_core/messages/system.py b/libs/core/langchain_core/messages/system.py index f29f2c85b1d..d63bd53a0fe 100644 --- a/libs/core/langchain_core/messages/system.py +++ b/libs/core/langchain_core/messages/system.py @@ -46,9 +46,6 @@ class SystemMessage(BaseMessage): super().__init__(content=content, **kwargs) -SystemMessage.model_rebuild() - - class SystemMessageChunk(SystemMessage, BaseMessageChunk): """System Message chunk.""" diff --git a/libs/core/langchain_core/messages/tool.py b/libs/core/langchain_core/messages/tool.py index 3a1408809c9..42c9018e151 100644 --- a/libs/core/langchain_core/messages/tool.py +++ b/libs/core/langchain_core/messages/tool.py @@ -146,9 +146,6 @@ class ToolMessage(BaseMessage, ToolOutputMixin): super().__init__(content=content, **kwargs) -ToolMessage.model_rebuild() - - class ToolMessageChunk(ToolMessage, BaseMessageChunk): """Tool Message chunk.""" diff --git a/libs/core/langchain_core/output_parsers/list.py b/libs/core/langchain_core/output_parsers/list.py index 2f52564fcd4..3dba7e3af5c 100644 --- a/libs/core/langchain_core/output_parsers/list.py +++ b/libs/core/langchain_core/output_parsers/list.py @@ -133,9 +133,6 @@ class ListOutputParser(BaseTransformOutputParser[list[str]]): yield [part] -ListOutputParser.model_rebuild() - - class CommaSeparatedListOutputParser(ListOutputParser): """Parse the output of an LLM call to a comma-separated list.""" diff --git a/libs/core/langchain_core/output_parsers/pydantic.py b/libs/core/langchain_core/output_parsers/pydantic.py index f41a8fe181e..194cc3a2d36 100644 --- a/libs/core/langchain_core/output_parsers/pydantic.py +++ b/libs/core/langchain_core/output_parsers/pydantic.py @@ -114,9 +114,6 @@ class PydanticOutputParser(JsonOutputParser, Generic[TBaseModel]): return self.pydantic_object -PydanticOutputParser.model_rebuild() - - _PYDANTIC_FORMAT_INSTRUCTIONS = """The output should be formatted as a JSON instance that conforms to the JSON schema below. As an example, for the schema {{"properties": {{"foo": {{"title": "Foo", "description": "a list of strings", "type": "array", "items": {{"type": "string"}}}}}}, "required": ["foo"]}} diff --git a/libs/core/langchain_core/output_parsers/string.py b/libs/core/langchain_core/output_parsers/string.py index 33bfffb8f7a..4f952c68892 100644 --- a/libs/core/langchain_core/output_parsers/string.py +++ b/libs/core/langchain_core/output_parsers/string.py @@ -31,6 +31,3 @@ class StrOutputParser(BaseTransformOutputParser[str]): def parse(self, text: str) -> str: """Returns the input text with no changes.""" return text - - -StrOutputParser.model_rebuild() diff --git a/libs/core/langchain_core/prompts/pipeline.py b/libs/core/langchain_core/prompts/pipeline.py index 8ec5cee9459..3c771b2dab0 100644 --- a/libs/core/langchain_core/prompts/pipeline.py +++ b/libs/core/langchain_core/prompts/pipeline.py @@ -132,6 +132,3 @@ class PipelinePromptTemplate(BasePromptTemplate): @property def _prompt_type(self) -> str: raise ValueError - - -PipelinePromptTemplate.model_rebuild() diff --git a/libs/core/langchain_core/runnables/base.py b/libs/core/langchain_core/runnables/base.py index 3b13ebcab4c..e51f1d709fd 100644 --- a/libs/core/langchain_core/runnables/base.py +++ b/libs/core/langchain_core/runnables/base.py @@ -5650,9 +5650,6 @@ class RunnableBindingBase(RunnableSerializable[Input, Output]): yield item -RunnableBindingBase.model_rebuild() - - class RunnableBinding(RunnableBindingBase[Input, Output]): """Wrap a Runnable with additional functionality. diff --git a/libs/core/langchain_core/runnables/configurable.py b/libs/core/langchain_core/runnables/configurable.py index 22a1f846d2b..0fdb2869411 100644 --- a/libs/core/langchain_core/runnables/configurable.py +++ b/libs/core/langchain_core/runnables/configurable.py @@ -8,7 +8,6 @@ from abc import abstractmethod from collections.abc import ( AsyncIterator, Iterator, - Mapping, # noqa: F401 Needed by pydantic Sequence, ) from functools import wraps @@ -464,9 +463,6 @@ class RunnableConfigurableFields(DynamicRunnable[Input, Output]): return (self.default, config) -RunnableConfigurableFields.model_rebuild() - - # Before Python 3.11 native StrEnum is not available class StrEnum(str, enum.Enum): """String enum.""" diff --git a/libs/core/langchain_core/runnables/utils.py b/libs/core/langchain_core/runnables/utils.py index c0fdeb96ae0..b9fd1b20885 100644 --- a/libs/core/langchain_core/runnables/utils.py +++ b/libs/core/langchain_core/runnables/utils.py @@ -6,6 +6,7 @@ import ast import asyncio import inspect import textwrap +from collections.abc import Mapping, Sequence from contextvars import Context from functools import lru_cache from inspect import signature @@ -33,8 +34,6 @@ if TYPE_CHECKING: Awaitable, Coroutine, Iterable, - Mapping, - Sequence, ) from langchain_core.runnables.schema import StreamEvent diff --git a/libs/core/langchain_core/tools/simple.py b/libs/core/langchain_core/tools/simple.py index e0062368f3b..05dc1917644 100644 --- a/libs/core/langchain_core/tools/simple.py +++ b/libs/core/langchain_core/tools/simple.py @@ -176,6 +176,3 @@ class Tool(BaseTool): args_schema=args_schema, **kwargs, ) - - -Tool.model_rebuild() diff --git a/libs/core/tests/unit_tests/language_models/chat_models/test_rate_limiting.py b/libs/core/tests/unit_tests/language_models/chat_models/test_rate_limiting.py index e9a72d61c6f..c4d6a50f6be 100644 --- a/libs/core/tests/unit_tests/language_models/chat_models/test_rate_limiting.py +++ b/libs/core/tests/unit_tests/language_models/chat_models/test_rate_limiting.py @@ -227,9 +227,6 @@ class SerializableModel(GenericFakeChatModel): return True -SerializableModel.model_rebuild() - - def test_serialization_with_rate_limiter() -> None: """Test model serialization with rate limiter.""" from langchain_core.load import dumps diff --git a/libs/core/tests/unit_tests/output_parsers/test_base_parsers.py b/libs/core/tests/unit_tests/output_parsers/test_base_parsers.py index 94afd7afb49..fa5e9c9c9c0 100644 --- a/libs/core/tests/unit_tests/output_parsers/test_base_parsers.py +++ b/libs/core/tests/unit_tests/output_parsers/test_base_parsers.py @@ -45,8 +45,6 @@ def test_base_generation_parser() -> None: assert isinstance(content, str) return content.swapcase() - StrInvertCase.model_rebuild() - model = GenericFakeChatModel(messages=iter([AIMessage(content="hEllo")])) chain = model | StrInvertCase() assert chain.invoke("") == "HeLLO" diff --git a/libs/core/tests/unit_tests/prompts/test_structured.py b/libs/core/tests/unit_tests/prompts/test_structured.py index 6e69936223a..8f04f2029ba 100644 --- a/libs/core/tests/unit_tests/prompts/test_structured.py +++ b/libs/core/tests/unit_tests/prompts/test_structured.py @@ -35,9 +35,6 @@ class FakeStructuredChatModel(FakeListChatModel): return "fake-messages-list-chat-model" -FakeStructuredChatModel.model_rebuild() - - def test_structured_prompt_pydantic() -> None: class OutputSchema(BaseModel): name: str diff --git a/libs/core/tests/unit_tests/runnables/test_runnable_events_v1.py b/libs/core/tests/unit_tests/runnables/test_runnable_events_v1.py index 824cbbe9056..a046070e7eb 100644 --- a/libs/core/tests/unit_tests/runnables/test_runnable_events_v1.py +++ b/libs/core/tests/unit_tests/runnables/test_runnable_events_v1.py @@ -1188,9 +1188,6 @@ class HardCodedRetriever(BaseRetriever): return self.documents -HardCodedRetriever.model_rebuild() - - async def test_event_stream_with_retriever() -> None: """Test the event stream with a retriever.""" retriever = HardCodedRetriever( diff --git a/libs/core/tests/unit_tests/test_pydantic_imports.py b/libs/core/tests/unit_tests/test_pydantic_imports.py new file mode 100644 index 00000000000..523647a7b7a --- /dev/null +++ b/libs/core/tests/unit_tests/test_pydantic_imports.py @@ -0,0 +1,20 @@ +import importlib +from pathlib import Path + +from pydantic import BaseModel + + +def test_all_models_built() -> None: + for path in Path("../core/langchain_core/").glob("*"): + module_name = path.stem + if not module_name.startswith(".") and path.suffix != ".typed": + module = importlib.import_module("langchain_core." + module_name) + all_ = getattr(module, "__all__", []) + for attr_name in all_: + attr = getattr(module, attr_name) + try: + if issubclass(attr, BaseModel): + assert attr.__pydantic_complete__ is True + except TypeError: + # This is expected for non-class attributes + pass diff --git a/libs/core/tests/unit_tests/test_tools.py b/libs/core/tests/unit_tests/test_tools.py index ca6de8f671d..3ffc15ad0fd 100644 --- a/libs/core/tests/unit_tests/test_tools.py +++ b/libs/core/tests/unit_tests/test_tools.py @@ -1091,9 +1091,6 @@ class FooBase(BaseTool): return assert_bar(bar, bar_config) -FooBase.model_rebuild() - - class AFooBase(FooBase): async def _arun(self, bar: Any, bar_config: RunnableConfig, **kwargs: Any) -> Any: return assert_bar(bar, bar_config) diff --git a/uv.lock b/uv.lock index c5cf84623ba..77b81f4a29b 100644 --- a/uv.lock +++ b/uv.lock @@ -2338,7 +2338,7 @@ dependencies = [ requires-dist = [ { name = "chromadb", specifier = ">=0.4.0,!=0.5.4,!=0.5.5,!=0.5.7,!=0.5.9,!=0.5.10,!=0.5.11,!=0.5.12,<0.7.0" }, { name = "langchain-core", editable = "libs/core" }, - { name = "numpy", marker = "python_full_version < '3.13'", specifier = ">=1.22.4" }, + { name = "numpy", marker = "python_full_version < '3.13'", specifier = ">=1.26.0" }, { name = "numpy", marker = "python_full_version >= '3.13'", specifier = ">=2.1.0" }, ] @@ -2451,7 +2451,7 @@ typing = [ { name = "langchain", editable = "libs/langchain" }, { name = "langchain-core", editable = "libs/core" }, { name = "langchain-text-splitters", editable = "libs/text-splitters" }, - { name = "mypy", specifier = ">=1.12,<2.0" }, + { name = "mypy", specifier = ">=1.15,<2.0" }, { name = "mypy-protobuf", specifier = ">=3.0.0,<4.0.0" }, { name = "types-chardet", specifier = ">=5.0.4.6,<6.0.0.0" }, { name = "types-pytz", specifier = ">=2023.3.0.0,<2024.0.0.0" }, @@ -2503,6 +2503,8 @@ test = [ { name = "numpy", marker = "python_full_version >= '3.13'", specifier = ">=2.1.0" }, { name = "pytest", specifier = ">=8,<9" }, { name = "pytest-asyncio", specifier = ">=0.21.1,<1.0.0" }, + { name = "pytest-benchmark" }, + { name = "pytest-codspeed" }, { name = "pytest-mock", specifier = ">=3.10.0,<4.0.0" }, { name = "pytest-socket", specifier = ">=0.7.0,<1.0.0" }, { name = "pytest-watcher", specifier = ">=0.3.4,<1.0.0" }, @@ -2513,8 +2515,7 @@ test = [ test-integration = [] typing = [ { name = "langchain-text-splitters", directory = "libs/text-splitters" }, - { name = "mypy", specifier = ">=1.10,<1.11" }, - { name = "types-jinja2", specifier = ">=2.11.9,<3.0.0" }, + { name = "mypy", specifier = ">=1.15,<1.16" }, { name = "types-pyyaml", specifier = ">=6.0.12.2,<7.0.0.0" }, { name = "types-requests", specifier = ">=2.28.11.5,<3.0.0.0" }, ]