From deae8cc164f56dfcf144c0d1a84178a3f390e532 Mon Sep 17 00:00:00 2001 From: ccurme Date: Tue, 5 Aug 2025 09:50:02 -0300 Subject: [PATCH 1/3] feat(core): support returning v1 ToolMessage in tools (#32397) --- libs/core/langchain_core/messages/v1.py | 3 +- libs/core/langchain_core/runnables/base.py | 9 +- libs/core/langchain_core/tools/base.py | 46 ++- libs/core/langchain_core/tools/convert.py | 24 +- libs/core/langchain_core/tools/retriever.py | 9 +- libs/core/langchain_core/tools/structured.py | 10 +- libs/core/tests/unit_tests/test_tools.py | 347 +++++++++++++++---- 7 files changed, 373 insertions(+), 75 deletions(-) diff --git a/libs/core/langchain_core/messages/v1.py b/libs/core/langchain_core/messages/v1.py index 1b779e4afd4..159e2faacaf 100644 --- a/libs/core/langchain_core/messages/v1.py +++ b/libs/core/langchain_core/messages/v1.py @@ -19,6 +19,7 @@ from langchain_core.messages.ai import ( add_usage, ) from langchain_core.messages.base import merge_content +from langchain_core.messages.tool import ToolOutputMixin from langchain_core.messages.tool import invalid_tool_call as create_invalid_tool_call from langchain_core.messages.tool import tool_call as create_tool_call from langchain_core.utils._merge import merge_dicts @@ -645,7 +646,7 @@ class SystemMessage: @dataclass -class ToolMessage: +class ToolMessage(ToolOutputMixin): """A message containing the result of a tool execution. Represents the output from executing a tool or function call, diff --git a/libs/core/langchain_core/runnables/base.py b/libs/core/langchain_core/runnables/base.py index 6c3938631ea..30f11c9ab7e 100644 --- a/libs/core/langchain_core/runnables/base.py +++ b/libs/core/langchain_core/runnables/base.py @@ -2361,6 +2361,7 @@ class Runnable(ABC, Generic[Input, Output]): name: Optional[str] = None, description: Optional[str] = None, arg_types: Optional[dict[str, type]] = None, + output_version: Literal["v0", "v1"] = "v0", ) -> BaseTool: """Create a BaseTool from a Runnable. @@ -2376,6 +2377,11 @@ class Runnable(ABC, Generic[Input, Output]): name: The name of the tool. Defaults to None. description: The description of the tool. Defaults to None. arg_types: A dictionary of argument names to types. Defaults to None. + output_version: Version of ToolMessage to return given + :class:`~langchain_core.messages.content_blocks.ToolCall` input. + + If ``"v0"``, output will be a v0 :class:`~langchain_core.messages.tool.ToolMessage`. + If ``"v1"``, output will be a v1 :class:`~langchain_core.messages.v1.ToolMessage`. Returns: A BaseTool instance. @@ -2451,7 +2457,7 @@ class Runnable(ABC, Generic[Input, Output]): .. versionadded:: 0.2.14 - """ + """ # noqa: E501 # Avoid circular import from langchain_core.tools import convert_runnable_to_tool @@ -2461,6 +2467,7 @@ class Runnable(ABC, Generic[Input, Output]): name=name, description=description, arg_types=arg_types, + output_version=output_version, ) diff --git a/libs/core/langchain_core/tools/base.py b/libs/core/langchain_core/tools/base.py index e54a09709d6..5573a08a1d5 100644 --- a/libs/core/langchain_core/tools/base.py +++ b/libs/core/langchain_core/tools/base.py @@ -47,6 +47,7 @@ from langchain_core.callbacks import ( Callbacks, ) from langchain_core.messages.tool import ToolCall, ToolMessage, ToolOutputMixin +from langchain_core.messages.v1 import ToolMessage as ToolMessageV1 from langchain_core.runnables import ( RunnableConfig, RunnableSerializable, @@ -498,6 +499,14 @@ class ChildTool(BaseTool): two-tuple corresponding to the (content, artifact) of a ToolMessage. """ + output_version: Literal["v0", "v1"] = "v0" + """Version of ToolMessage to return given + :class:`~langchain_core.messages.content_blocks.ToolCall` input. + + If ``"v0"``, output will be a v0 :class:`~langchain_core.messages.tool.ToolMessage`. + If ``"v1"``, output will be a v1 :class:`~langchain_core.messages.v1.ToolMessage`. + """ + def __init__(self, **kwargs: Any) -> None: """Initialize the tool.""" if ( @@ -835,7 +844,7 @@ class ChildTool(BaseTool): content = None artifact = None - status = "success" + status: Literal["success", "error"] = "success" error_to_raise: Union[Exception, KeyboardInterrupt, None] = None try: child_config = patch_config(config, callbacks=run_manager.get_child()) @@ -879,7 +888,14 @@ class ChildTool(BaseTool): if error_to_raise: run_manager.on_tool_error(error_to_raise) raise error_to_raise - output = _format_output(content, artifact, tool_call_id, self.name, status) + output = _format_output( + content, + artifact, + tool_call_id, + self.name, + status, + output_version=self.output_version, + ) run_manager.on_tool_end(output, color=color, name=self.name, **kwargs) return output @@ -945,7 +961,7 @@ class ChildTool(BaseTool): ) content = None artifact = None - status = "success" + status: Literal["success", "error"] = "success" error_to_raise: Optional[Union[Exception, KeyboardInterrupt]] = None try: tool_args, tool_kwargs = self._to_args_and_kwargs(tool_input, tool_call_id) @@ -993,7 +1009,14 @@ class ChildTool(BaseTool): await run_manager.on_tool_error(error_to_raise) raise error_to_raise - output = _format_output(content, artifact, tool_call_id, self.name, status) + output = _format_output( + content, + artifact, + tool_call_id, + self.name, + status, + output_version=self.output_version, + ) await run_manager.on_tool_end(output, color=color, name=self.name, **kwargs) return output @@ -1131,7 +1154,9 @@ def _format_output( artifact: Any, tool_call_id: Optional[str], name: str, - status: str, + status: Literal["success", "error"], + *, + output_version: Literal["v0", "v1"] = "v0", ) -> Union[ToolOutputMixin, Any]: """Format tool output as a ToolMessage if appropriate. @@ -1141,6 +1166,7 @@ def _format_output( tool_call_id: The ID of the tool call. name: The name of the tool. status: The execution status. + output_version: The version of the ToolMessage to return. Returns: The formatted output, either as a ToolMessage or the original content. @@ -1149,7 +1175,15 @@ def _format_output( return content if not _is_message_content_type(content): content = _stringify(content) - return ToolMessage( + if output_version == "v0": + return ToolMessage( + content, + artifact=artifact, + tool_call_id=tool_call_id, + name=name, + status=status, + ) + return ToolMessageV1( content, artifact=artifact, tool_call_id=tool_call_id, diff --git a/libs/core/langchain_core/tools/convert.py b/libs/core/langchain_core/tools/convert.py index 8b103fd54d6..dcbfae56225 100644 --- a/libs/core/langchain_core/tools/convert.py +++ b/libs/core/langchain_core/tools/convert.py @@ -22,6 +22,7 @@ def tool( response_format: Literal["content", "content_and_artifact"] = "content", parse_docstring: bool = False, error_on_invalid_docstring: bool = True, + output_version: Literal["v0", "v1"] = "v0", ) -> Callable[[Union[Callable, Runnable]], BaseTool]: ... @@ -37,6 +38,7 @@ def tool( response_format: Literal["content", "content_and_artifact"] = "content", parse_docstring: bool = False, error_on_invalid_docstring: bool = True, + output_version: Literal["v0", "v1"] = "v0", ) -> BaseTool: ... @@ -51,6 +53,7 @@ def tool( response_format: Literal["content", "content_and_artifact"] = "content", parse_docstring: bool = False, error_on_invalid_docstring: bool = True, + output_version: Literal["v0", "v1"] = "v0", ) -> BaseTool: ... @@ -65,6 +68,7 @@ def tool( response_format: Literal["content", "content_and_artifact"] = "content", parse_docstring: bool = False, error_on_invalid_docstring: bool = True, + output_version: Literal["v0", "v1"] = "v0", ) -> Callable[[Union[Callable, Runnable]], BaseTool]: ... @@ -79,6 +83,7 @@ def tool( response_format: Literal["content", "content_and_artifact"] = "content", parse_docstring: bool = False, error_on_invalid_docstring: bool = True, + output_version: Literal["v0", "v1"] = "v0", ) -> Union[ BaseTool, Callable[[Union[Callable, Runnable]], BaseTool], @@ -118,6 +123,11 @@ def tool( error_on_invalid_docstring: if ``parse_docstring`` is provided, configure whether to raise ValueError on invalid Google Style docstrings. Defaults to True. + output_version: Version of ToolMessage to return given + :class:`~langchain_core.messages.content_blocks.ToolCall` input. + + If ``"v0"``, output will be a v0 :class:`~langchain_core.messages.tool.ToolMessage`. + If ``"v1"``, output will be a v1 :class:`~langchain_core.messages.v1.ToolMessage`. Returns: The tool. @@ -216,7 +226,7 @@ def tool( \"\"\" return bar - """ # noqa: D214, D410, D411 + """ # noqa: D214, D410, D411, E501 def _create_tool_factory( tool_name: str, @@ -274,6 +284,7 @@ def tool( response_format=response_format, parse_docstring=parse_docstring, error_on_invalid_docstring=error_on_invalid_docstring, + output_version=output_version, ) # If someone doesn't want a schema applied, we must treat it as # a simple string->string function @@ -290,6 +301,7 @@ def tool( return_direct=return_direct, coroutine=coroutine, response_format=response_format, + output_version=output_version, ) return _tool_factory @@ -383,6 +395,7 @@ def convert_runnable_to_tool( name: Optional[str] = None, description: Optional[str] = None, arg_types: Optional[dict[str, type]] = None, + output_version: Literal["v0", "v1"] = "v0", ) -> BaseTool: """Convert a Runnable into a BaseTool. @@ -392,10 +405,15 @@ def convert_runnable_to_tool( name: The name of the tool. Defaults to None. description: The description of the tool. Defaults to None. arg_types: The types of the arguments. Defaults to None. + output_version: Version of ToolMessage to return given + :class:`~langchain_core.messages.content_blocks.ToolCall` input. + + If ``"v0"``, output will be a v0 :class:`~langchain_core.messages.tool.ToolMessage`. + If ``"v1"``, output will be a v1 :class:`~langchain_core.messages.v1.ToolMessage`. Returns: The tool. - """ + """ # noqa: E501 if args_schema: runnable = runnable.with_types(input_type=args_schema) description = description or _get_description_from_runnable(runnable) @@ -408,6 +426,7 @@ def convert_runnable_to_tool( func=runnable.invoke, coroutine=runnable.ainvoke, description=description, + output_version=output_version, ) async def ainvoke_wrapper( @@ -435,4 +454,5 @@ def convert_runnable_to_tool( coroutine=ainvoke_wrapper, description=description, args_schema=args_schema, + output_version=output_version, ) diff --git a/libs/core/langchain_core/tools/retriever.py b/libs/core/langchain_core/tools/retriever.py index 002fa5e80d6..b3f3f4be10e 100644 --- a/libs/core/langchain_core/tools/retriever.py +++ b/libs/core/langchain_core/tools/retriever.py @@ -72,6 +72,7 @@ def create_retriever_tool( document_prompt: Optional[BasePromptTemplate] = None, document_separator: str = "\n\n", response_format: Literal["content", "content_and_artifact"] = "content", + output_version: Literal["v0", "v1"] = "v1", ) -> Tool: r"""Create a tool to do retrieval of documents. @@ -88,10 +89,15 @@ def create_retriever_tool( "content_and_artifact" then the output is expected to be a two-tuple corresponding to the (content, artifact) of a ToolMessage (artifact being a list of documents in this case). Defaults to "content". + output_version: Version of ToolMessage to return given + :class:`~langchain_core.messages.content_blocks.ToolCall` input. + + If ``"v0"``, output will be a v0 :class:`~langchain_core.messages.tool.ToolMessage`. + If ``"v1"``, output will be a v1 :class:`~langchain_core.messages.v1.ToolMessage`. Returns: Tool class to pass to an agent. - """ + """ # noqa: E501 document_prompt = document_prompt or PromptTemplate.from_template("{page_content}") func = partial( _get_relevant_documents, @@ -114,4 +120,5 @@ def create_retriever_tool( coroutine=afunc, args_schema=RetrieverInput, response_format=response_format, + output_version=output_version, ) diff --git a/libs/core/langchain_core/tools/structured.py b/libs/core/langchain_core/tools/structured.py index a419a1ede62..c1326512ee2 100644 --- a/libs/core/langchain_core/tools/structured.py +++ b/libs/core/langchain_core/tools/structured.py @@ -129,6 +129,7 @@ class StructuredTool(BaseTool): response_format: Literal["content", "content_and_artifact"] = "content", parse_docstring: bool = False, error_on_invalid_docstring: bool = False, + output_version: Literal["v0", "v1"] = "v0", **kwargs: Any, ) -> StructuredTool: """Create tool from a given function. @@ -157,6 +158,12 @@ class StructuredTool(BaseTool): error_on_invalid_docstring: if ``parse_docstring`` is provided, configure whether to raise ValueError on invalid Google Style docstrings. Defaults to False. + output_version: Version of ToolMessage to return given + :class:`~langchain_core.messages.content_blocks.ToolCall` input. + + If ``"v0"``, output will be a v0 :class:`~langchain_core.messages.tool.ToolMessage`. + If ``"v1"``, output will be a v1 :class:`~langchain_core.messages.v1.ToolMessage`. + kwargs: Additional arguments to pass to the tool Returns: @@ -175,7 +182,7 @@ class StructuredTool(BaseTool): tool = StructuredTool.from_function(add) tool.run(1, 2) # 3 - """ + """ # noqa: E501 if func is not None: source_function = func elif coroutine is not None: @@ -232,6 +239,7 @@ class StructuredTool(BaseTool): description=description_, return_direct=return_direct, response_format=response_format, + output_version=output_version, **kwargs, ) diff --git a/libs/core/tests/unit_tests/test_tools.py b/libs/core/tests/unit_tests/test_tools.py index 57b4573d70d..cd52d37c9fa 100644 --- a/libs/core/tests/unit_tests/test_tools.py +++ b/libs/core/tests/unit_tests/test_tools.py @@ -37,6 +37,7 @@ from langchain_core.callbacks.manager import ( from langchain_core.documents import Document from langchain_core.messages import ToolCall, ToolMessage from langchain_core.messages.tool import ToolOutputMixin +from langchain_core.messages.v1 import ToolMessage as ToolMessageV1 from langchain_core.retrievers import BaseRetriever from langchain_core.runnables import ( Runnable, @@ -70,6 +71,7 @@ from langchain_core.utils.pydantic import ( ) from tests.unit_tests.fake.callbacks import FakeCallbackHandler from tests.unit_tests.pydantic_utils import _schema +from tests.unit_tests.stubs import AnyStr def _get_tool_call_json_schema(tool: BaseTool) -> dict: @@ -1379,17 +1381,28 @@ def test_tool_annotated_descriptions() -> None: } -def test_tool_call_input_tool_message_output() -> None: +@pytest.mark.parametrize("output_version", ["v0", "v1"]) +def test_tool_call_input_tool_message(output_version: Literal["v0", "v1"]) -> None: tool_call = { "name": "structured_api", "args": {"arg1": 1, "arg2": True, "arg3": {"img": "base64string..."}}, "id": "123", "type": "tool_call", } - tool = _MockStructuredTool() - expected = ToolMessage( - "1 True {'img': 'base64string...'}", tool_call_id="123", name="structured_api" - ) + tool = _MockStructuredTool(output_version=output_version) + if output_version == "v0": + expected: Union[ToolMessage, ToolMessageV1] = ToolMessage( + "1 True {'img': 'base64string...'}", + tool_call_id="123", + name="structured_api", + ) + else: + expected = ToolMessageV1( + "1 True {'img': 'base64string...'}", + tool_call_id="123", + name="structured_api", + id=AnyStr("lc_abc123"), + ) actual = tool.invoke(tool_call) assert actual == expected @@ -1421,6 +1434,14 @@ def _mock_structured_tool_with_artifact( return f"{arg1} {arg2}", {"arg1": arg1, "arg2": arg2, "arg3": arg3} +@tool("structured_api", response_format="content_and_artifact", output_version="v1") +def _mock_structured_tool_with_artifact_v1( + *, arg1: int, arg2: bool, arg3: Optional[dict] = None +) -> tuple[str, dict]: + """A Structured Tool.""" + return f"{arg1} {arg2}", {"arg1": arg1, "arg2": arg2, "arg3": arg3} + + @pytest.mark.parametrize( "tool", [_MockStructuredToolWithRawOutput(), _mock_structured_tool_with_artifact] ) @@ -1445,6 +1466,38 @@ def test_tool_call_input_tool_message_with_artifact(tool: BaseTool) -> None: assert actual_content == expected.content +@pytest.mark.parametrize( + "tool", + [ + _MockStructuredToolWithRawOutput(output_version="v1"), + _mock_structured_tool_with_artifact_v1, + ], +) +def test_tool_call_input_tool_message_with_artifact_v1(tool: BaseTool) -> None: + tool_call: dict = { + "name": "structured_api", + "args": {"arg1": 1, "arg2": True, "arg3": {"img": "base64string..."}}, + "id": "123", + "type": "tool_call", + } + expected = ToolMessageV1( + "1 True", + artifact=tool_call["args"], + tool_call_id="123", + name="structured_api", + id=AnyStr("lc_abc123"), + ) + actual = tool.invoke(tool_call) + assert actual == expected + + tool_call.pop("type") + with pytest.raises(ValidationError): + tool.invoke(tool_call) + + actual_content = tool.invoke(tool_call["args"]) + assert actual_content == expected.text + + def test_convert_from_runnable_dict() -> None: # Test with typed dict input class Args(TypedDict): @@ -1550,6 +1603,17 @@ def injected_tool(x: int, y: Annotated[str, InjectedToolArg]) -> str: return y +@tool("foo", parse_docstring=True, output_version="v1") +def injected_tool_v1(x: int, y: Annotated[str, InjectedToolArg]) -> str: + """Foo. + + Args: + x: abc + y: 123 + """ + return y + + class InjectedTool(BaseTool): name: str = "foo" description: str = "foo." @@ -1587,7 +1651,12 @@ def injected_tool_with_schema(x: int, y: str) -> str: return y -@pytest.mark.parametrize("tool_", [InjectedTool()]) +@tool("foo", args_schema=fooSchema, output_version="v1") +def injected_tool_with_schema_v1(x: int, y: str) -> str: + return y + + +@pytest.mark.parametrize("tool_", [InjectedTool(), InjectedTool(output_version="v1")]) def test_tool_injected_arg_without_schema(tool_: BaseTool) -> None: assert _schema(tool_.get_input_schema()) == { "title": "foo", @@ -1607,14 +1676,25 @@ def test_tool_injected_arg_without_schema(tool_: BaseTool) -> None: "required": ["x"], } assert tool_.invoke({"x": 5, "y": "bar"}) == "bar" - assert tool_.invoke( - { - "name": "foo", - "args": {"x": 5, "y": "bar"}, - "id": "123", - "type": "tool_call", - } - ) == ToolMessage("bar", tool_call_id="123", name="foo") + if tool_.output_version == "v0": + expected: Union[ToolMessage, ToolMessageV1] = ToolMessage( + "bar", tool_call_id="123", name="foo" + ) + else: + expected = ToolMessageV1( + "bar", tool_call_id="123", name="foo", id=AnyStr("lc_abc123") + ) + assert ( + tool_.invoke( + { + "name": "foo", + "args": {"x": 5, "y": "bar"}, + "id": "123", + "type": "tool_call", + } + ) + == expected + ) expected_error = ( ValidationError if not isinstance(tool_, InjectedTool) else TypeError ) @@ -1634,7 +1714,12 @@ def test_tool_injected_arg_without_schema(tool_: BaseTool) -> None: @pytest.mark.parametrize( "tool_", - [injected_tool_with_schema, InjectedToolWithSchema()], + [ + injected_tool_with_schema, + InjectedToolWithSchema(), + injected_tool_with_schema_v1, + InjectedToolWithSchema(output_version="v1"), + ], ) def test_tool_injected_arg_with_schema(tool_: BaseTool) -> None: assert _schema(tool_.get_input_schema()) == { @@ -1655,14 +1740,25 @@ def test_tool_injected_arg_with_schema(tool_: BaseTool) -> None: "required": ["x"], } assert tool_.invoke({"x": 5, "y": "bar"}) == "bar" - assert tool_.invoke( - { - "name": "foo", - "args": {"x": 5, "y": "bar"}, - "id": "123", - "type": "tool_call", - } - ) == ToolMessage("bar", tool_call_id="123", name="foo") + if tool_.output_version == "v0": + expected: Union[ToolMessage, ToolMessageV1] = ToolMessage( + "bar", tool_call_id="123", name="foo" + ) + else: + expected = ToolMessageV1( + "bar", tool_call_id="123", name="foo", id=AnyStr("lc_abc123") + ) + assert ( + tool_.invoke( + { + "name": "foo", + "args": {"x": 5, "y": "bar"}, + "id": "123", + "type": "tool_call", + } + ) + == expected + ) expected_error = ( ValidationError if not isinstance(tool_, InjectedTool) else TypeError ) @@ -1680,8 +1776,9 @@ def test_tool_injected_arg_with_schema(tool_: BaseTool) -> None: } -def test_tool_injected_arg() -> None: - tool_ = injected_tool +@pytest.mark.parametrize("output_version", ["v0", "v1"]) +def test_tool_injected_arg(output_version: Literal["v0", "v1"]) -> None: + tool_ = injected_tool if output_version == "v0" else injected_tool_v1 assert _schema(tool_.get_input_schema()) == { "title": "foo", "description": "Foo.", @@ -1700,14 +1797,25 @@ def test_tool_injected_arg() -> None: "required": ["x"], } assert tool_.invoke({"x": 5, "y": "bar"}) == "bar" - assert tool_.invoke( - { - "name": "foo", - "args": {"x": 5, "y": "bar"}, - "id": "123", - "type": "tool_call", - } - ) == ToolMessage("bar", tool_call_id="123", name="foo") + if output_version == "v0": + expected: Union[ToolMessage, ToolMessageV1] = ToolMessage( + "bar", tool_call_id="123", name="foo" + ) + else: + expected = ToolMessageV1( + "bar", tool_call_id="123", name="foo", id=AnyStr("lc_abc123") + ) + assert ( + tool_.invoke( + { + "name": "foo", + "args": {"x": 5, "y": "bar"}, + "id": "123", + "type": "tool_call", + } + ) + == expected + ) expected_error = ( ValidationError if not isinstance(tool_, InjectedTool) else TypeError ) @@ -1725,7 +1833,8 @@ def test_tool_injected_arg() -> None: } -def test_tool_inherited_injected_arg() -> None: +@pytest.mark.parametrize("output_version", ["v0", "v1"]) +def test_tool_inherited_injected_arg(output_version: Literal["v0", "v1"]) -> None: class BarSchema(BaseModel): """bar.""" @@ -1746,7 +1855,7 @@ def test_tool_inherited_injected_arg() -> None: def _run(self, x: int, y: str) -> Any: return y - tool_ = InheritedInjectedArgTool() + tool_ = InheritedInjectedArgTool(output_version=output_version) assert tool_.get_input_schema().model_json_schema() == { "title": "FooSchema", # Matches the title from the provided schema "description": "foo.", @@ -1766,14 +1875,25 @@ def test_tool_inherited_injected_arg() -> None: "required": ["x"], } assert tool_.invoke({"x": 5, "y": "bar"}) == "bar" - assert tool_.invoke( - { - "name": "foo", - "args": {"x": 5, "y": "bar"}, - "id": "123", - "type": "tool_call", - } - ) == ToolMessage("bar", tool_call_id="123", name="foo") + if output_version == "v0": + expected: Union[ToolMessage, ToolMessageV1] = ToolMessage( + "bar", tool_call_id="123", name="foo" + ) + else: + expected = ToolMessageV1( + "bar", tool_call_id="123", name="foo", id=AnyStr("lc_abc123") + ) + assert ( + tool_.invoke( + { + "name": "foo", + "args": {"x": 5, "y": "bar"}, + "id": "123", + "type": "tool_call", + } + ) + == expected + ) expected_error = ( ValidationError if not isinstance(tool_, InjectedTool) else TypeError ) @@ -2133,7 +2253,8 @@ def test_tool_annotations_preserved() -> None: assert schema.__annotations__ == expected_type_hints -def test_create_retriever_tool() -> None: +@pytest.mark.parametrize("output_version", ["v0", "v1"]) +def test_create_retriever_tool(output_version: Literal["v0", "v1"]) -> None: class MyRetriever(BaseRetriever): def _get_relevant_documents( self, query: str, *, run_manager: CallbackManagerForRetrieverRun @@ -2142,21 +2263,36 @@ def test_create_retriever_tool() -> None: retriever = MyRetriever() retriever_tool = tools.create_retriever_tool( - retriever, "retriever_tool_content", "Retriever Tool Content" + retriever, + "retriever_tool_content", + "Retriever Tool Content", + output_version=output_version, ) assert isinstance(retriever_tool, BaseTool) assert retriever_tool.name == "retriever_tool_content" assert retriever_tool.description == "Retriever Tool Content" assert retriever_tool.invoke("bar") == "foo bar\n\nbar" - assert retriever_tool.invoke( - ToolCall( - name="retriever_tool_content", - args={"query": "bar"}, - id="123", - type="tool_call", + if output_version == "v0": + expected: Union[ToolMessage, ToolMessageV1] = ToolMessage( + "foo bar\n\nbar", tool_call_id="123", name="retriever_tool_content" ) - ) == ToolMessage( - "foo bar\n\nbar", tool_call_id="123", name="retriever_tool_content" + else: + expected = ToolMessageV1( + "foo bar\n\nbar", + tool_call_id="123", + name="retriever_tool_content", + id=AnyStr("lc_abc123"), + ) + assert ( + retriever_tool.invoke( + ToolCall( + name="retriever_tool_content", + args={"query": "bar"}, + id="123", + type="tool_call", + ) + ) + == expected ) retriever_tool_artifact = tools.create_retriever_tool( @@ -2164,23 +2300,37 @@ def test_create_retriever_tool() -> None: "retriever_tool_artifact", "Retriever Tool Artifact", response_format="content_and_artifact", + output_version=output_version, ) assert isinstance(retriever_tool_artifact, BaseTool) assert retriever_tool_artifact.name == "retriever_tool_artifact" assert retriever_tool_artifact.description == "Retriever Tool Artifact" assert retriever_tool_artifact.invoke("bar") == "foo bar\n\nbar" - assert retriever_tool_artifact.invoke( - ToolCall( + if output_version == "v0": + expected = ToolMessage( + "foo bar\n\nbar", + artifact=[Document(page_content="foo bar"), Document(page_content="bar")], + tool_call_id="123", name="retriever_tool_artifact", - args={"query": "bar"}, - id="123", - type="tool_call", ) - ) == ToolMessage( - "foo bar\n\nbar", - artifact=[Document(page_content="foo bar"), Document(page_content="bar")], - tool_call_id="123", - name="retriever_tool_artifact", + else: + expected = ToolMessageV1( + "foo bar\n\nbar", + artifact=[Document(page_content="foo bar"), Document(page_content="bar")], + tool_call_id="123", + name="retriever_tool_artifact", + id=AnyStr("lc_abc123"), + ) + assert ( + retriever_tool_artifact.invoke( + ToolCall( + name="retriever_tool_artifact", + args={"query": "bar"}, + id="123", + type="tool_call", + ) + ) + == expected ) @@ -2313,6 +2463,45 @@ def test_tool_injected_tool_call_id() -> None: ) == ToolMessage(0, tool_call_id="bar") # type: ignore[arg-type] +def test_tool_injected_tool_call_id_v1() -> None: + @tool + def foo(x: int, tool_call_id: Annotated[str, InjectedToolCallId]) -> ToolMessageV1: + """Foo.""" + return ToolMessageV1(str(x), tool_call_id=tool_call_id) + + assert foo.invoke( + { + "type": "tool_call", + "args": {"x": 0}, + "name": "foo", + "id": "bar", + } + ) == ToolMessageV1("0", tool_call_id="bar", id=AnyStr("lc_abc123")) + + with pytest.raises( + ValueError, + match="When tool includes an InjectedToolCallId argument, " + "tool must always be invoked with a full model ToolCall", + ): + assert foo.invoke({"x": 0}) + + @tool + def foo2( + x: int, tool_call_id: Annotated[str, InjectedToolCallId()] + ) -> ToolMessageV1: + """Foo.""" + return ToolMessageV1(str(x), tool_call_id=tool_call_id) + + assert foo2.invoke( + { + "type": "tool_call", + "args": {"x": 0}, + "name": "foo", + "id": "bar", + } + ) == ToolMessageV1("0", tool_call_id="bar", id=AnyStr("lc_abc123")) + + def test_tool_uninjected_tool_call_id() -> None: @tool def foo(x: int, tool_call_id: str) -> ToolMessage: @@ -2332,6 +2521,25 @@ def test_tool_uninjected_tool_call_id() -> None: ) == ToolMessage(0, tool_call_id="zap") # type: ignore[arg-type] +def test_tool_uninjected_tool_call_id_v1() -> None: + @tool + def foo(x: int, tool_call_id: str) -> ToolMessageV1: + """Foo.""" + return ToolMessageV1(str(x), tool_call_id=tool_call_id) + + with pytest.raises(ValueError, match="1 validation error for foo"): + foo.invoke({"type": "tool_call", "args": {"x": 0}, "name": "foo", "id": "bar"}) + + assert foo.invoke( + { + "type": "tool_call", + "args": {"x": 0, "tool_call_id": "zap"}, + "name": "foo", + "id": "bar", + } + ) == ToolMessageV1("0", tool_call_id="zap", id=AnyStr("lc_abc123")) + + def test_tool_return_output_mixin() -> None: class Bar(ToolOutputMixin): def __init__(self, x: int) -> None: @@ -2457,6 +2665,19 @@ def test_empty_string_tool_call_id() -> None: ) +def test_empty_string_tool_call_id_v1() -> None: + @tool(output_version="v1") + def foo(x: int) -> str: + """Foo.""" + return "hi" + + assert foo.invoke( + {"type": "tool_call", "args": {"x": 0}, "id": ""} + ) == ToolMessageV1( + content="hi", name="foo", tool_call_id="", id=AnyStr("lc_abc123") + ) + + def test_tool_decorator_description() -> None: # test basic tool @tool From c36b123c8ce51c6d24a7dc6aeba149b1f1f141a6 Mon Sep 17 00:00:00 2001 From: ccurme Date: Tue, 5 Aug 2025 10:21:31 -0300 Subject: [PATCH 2/3] fix(core): refactor new types into top-level v1 namespace (#32403) --- libs/core/langchain_core/callbacks/base.py | 2 +- libs/core/langchain_core/callbacks/manager.py | 8 +++---- .../callbacks/streaming_stdout.py | 2 +- libs/core/langchain_core/callbacks/usage.py | 2 +- .../langchain_core/language_models/_utils.py | 2 +- .../langchain_core/language_models/base.py | 2 +- .../language_models/fake_chat_models.py | 8 +++---- .../language_models/v1/__init__.py | 1 - libs/core/langchain_core/messages/utils.py | 12 +++++----- .../langchain_core/output_parsers/base.py | 2 +- .../langchain_core/output_parsers/json.py | 2 +- .../langchain_core/output_parsers/list.py | 2 +- .../output_parsers/openai_functions.py | 2 +- .../output_parsers/openai_tools.py | 2 +- .../langchain_core/output_parsers/pydantic.py | 2 +- .../output_parsers/transform.py | 2 +- .../core/langchain_core/output_parsers/xml.py | 2 +- libs/core/langchain_core/prompt_values.py | 10 ++++----- libs/core/langchain_core/tools/base.py | 2 +- libs/core/langchain_core/tracers/base.py | 2 +- libs/core/langchain_core/tracers/core.py | 12 +++++----- .../langchain_core/tracers/event_stream.py | 6 ++--- libs/core/langchain_core/tracers/langchain.py | 4 ++-- .../core/langchain_core/tracers/log_stream.py | 2 +- libs/core/langchain_core/v1/__init__.py | 1 + .../{language_models => }/v1/chat_models.py | 8 +++---- .../{messages/v1.py => v1/messages.py} | 0 .../tests/benchmarks/test_async_callbacks.py | 4 ++-- libs/core/tests/unit_tests/fake/callbacks.py | 2 +- .../unit_tests/fake/test_fake_chat_model.py | 4 ++-- .../language_models/chat_models/test_base.py | 2 +- .../messages/test_response_metadata.py | 2 +- .../output_parsers/test_base_parsers.py | 2 +- .../output_parsers/test_openai_tools.py | 4 ++-- .../runnables/__snapshots__/test_graph.ambr | 20 ++++++++--------- .../__snapshots__/test_runnable.ambr | 20 ++++++++--------- libs/core/tests/unit_tests/test_messages.py | 4 ++-- libs/core/tests/unit_tests/test_tools.py | 2 +- .../tracers/test_async_base_tracer.py | 4 ++-- .../unit_tests/tracers/test_base_tracer.py | 4 ++-- .../agents/output_parsers/openai_functions.py | 2 +- .../agents/output_parsers/openai_tools.py | 2 +- .../langchain/agents/output_parsers/tools.py | 2 +- .../langchain/callbacks/streaming_aiter.py | 2 +- .../callbacks/streaming_aiter_final_only.py | 2 +- .../langchain/smith/evaluation/progress.py | 2 +- .../callbacks/fake_callback_handler.py | 2 +- .../unit_tests/llms/test_fake_chat_model.py | 4 ++-- .../langchain_openai/chat_models/_compat.py | 2 +- .../langchain_openai/chat_models/base_v1.py | 22 +++++++++---------- .../chat_models/test_responses_api.py | 6 ++--- .../tests/unit_tests/chat_models/test_base.py | 2 +- .../chat_models/test_responses_stream.py | 2 +- .../openai/tests/unit_tests/fake/callbacks.py | 2 +- 54 files changed, 115 insertions(+), 115 deletions(-) delete mode 100644 libs/core/langchain_core/language_models/v1/__init__.py create mode 100644 libs/core/langchain_core/v1/__init__.py rename libs/core/langchain_core/{language_models => }/v1/chat_models.py (99%) rename libs/core/langchain_core/{messages/v1.py => v1/messages.py} (100%) diff --git a/libs/core/langchain_core/callbacks/base.py b/libs/core/langchain_core/callbacks/base.py index 3ca427f84fc..b6be125af48 100644 --- a/libs/core/langchain_core/callbacks/base.py +++ b/libs/core/langchain_core/callbacks/base.py @@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Any, Optional, Union from typing_extensions import Self -from langchain_core.messages.v1 import AIMessage, AIMessageChunk, MessageV1 +from langchain_core.v1.messages import AIMessage, AIMessageChunk, MessageV1 if TYPE_CHECKING: from collections.abc import Sequence diff --git a/libs/core/langchain_core/callbacks/manager.py b/libs/core/langchain_core/callbacks/manager.py index b5f8300bc37..93efa45d336 100644 --- a/libs/core/langchain_core/callbacks/manager.py +++ b/libs/core/langchain_core/callbacks/manager.py @@ -38,15 +38,15 @@ from langchain_core.callbacks.base import ( from langchain_core.callbacks.stdout import StdOutCallbackHandler from langchain_core.messages import BaseMessage, get_buffer_string from langchain_core.messages.utils import convert_from_v1_message -from langchain_core.messages.v1 import ( +from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, LLMResult +from langchain_core.tracers.schemas import Run +from langchain_core.utils.env import env_var_is_set +from langchain_core.v1.messages import ( AIMessage, AIMessageChunk, MessageV1, MessageV1Types, ) -from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, LLMResult -from langchain_core.tracers.schemas import Run -from langchain_core.utils.env import env_var_is_set if TYPE_CHECKING: from collections.abc import AsyncGenerator, Coroutine, Generator, Sequence diff --git a/libs/core/langchain_core/callbacks/streaming_stdout.py b/libs/core/langchain_core/callbacks/streaming_stdout.py index f14d2962047..31ac544f55a 100644 --- a/libs/core/langchain_core/callbacks/streaming_stdout.py +++ b/libs/core/langchain_core/callbacks/streaming_stdout.py @@ -12,8 +12,8 @@ from langchain_core.callbacks.base import BaseCallbackHandler if TYPE_CHECKING: from langchain_core.agents import AgentAction, AgentFinish from langchain_core.messages import BaseMessage - from langchain_core.messages.v1 import AIMessage, MessageV1 from langchain_core.outputs import LLMResult + from langchain_core.v1.messages import AIMessage, MessageV1 class StreamingStdOutCallbackHandler(BaseCallbackHandler): diff --git a/libs/core/langchain_core/callbacks/usage.py b/libs/core/langchain_core/callbacks/usage.py index bc88d4b393b..5aef7e714dd 100644 --- a/libs/core/langchain_core/callbacks/usage.py +++ b/libs/core/langchain_core/callbacks/usage.py @@ -12,8 +12,8 @@ from langchain_core.callbacks import BaseCallbackHandler from langchain_core.messages import AIMessage from langchain_core.messages.ai import UsageMetadata, add_usage from langchain_core.messages.utils import convert_from_v1_message -from langchain_core.messages.v1 import AIMessage as AIMessageV1 from langchain_core.outputs import ChatGeneration, LLMResult +from langchain_core.v1.messages import AIMessage as AIMessageV1 class UsageMetadataCallbackHandler(BaseCallbackHandler): diff --git a/libs/core/langchain_core/language_models/_utils.py b/libs/core/langchain_core/language_models/_utils.py index 6d4b6c8475e..ec814261763 100644 --- a/libs/core/langchain_core/language_models/_utils.py +++ b/libs/core/langchain_core/language_models/_utils.py @@ -4,7 +4,7 @@ from collections.abc import Sequence from typing import Optional from langchain_core.messages import BaseMessage -from langchain_core.messages.v1 import MessageV1 +from langchain_core.v1.messages import MessageV1 def _is_openai_data_block(block: dict) -> bool: diff --git a/libs/core/langchain_core/language_models/base.py b/libs/core/langchain_core/language_models/base.py index 1fef01a6859..e421d9ab842 100644 --- a/libs/core/langchain_core/language_models/base.py +++ b/libs/core/langchain_core/language_models/base.py @@ -28,10 +28,10 @@ from langchain_core.messages import ( MessageLikeRepresentation, get_buffer_string, ) -from langchain_core.messages.v1 import AIMessage as AIMessageV1 from langchain_core.prompt_values import PromptValue from langchain_core.runnables import Runnable, RunnableSerializable from langchain_core.utils import get_pydantic_field_names +from langchain_core.v1.messages import AIMessage as AIMessageV1 if TYPE_CHECKING: from langchain_core.outputs import LLMResult diff --git a/libs/core/langchain_core/language_models/fake_chat_models.py b/libs/core/langchain_core/language_models/fake_chat_models.py index 0d18bb14c7a..6d1b5347b9c 100644 --- a/libs/core/langchain_core/language_models/fake_chat_models.py +++ b/libs/core/langchain_core/language_models/fake_chat_models.py @@ -13,13 +13,13 @@ from langchain_core.callbacks import ( CallbackManagerForLLMRun, ) from langchain_core.language_models.chat_models import BaseChatModel, SimpleChatModel -from langchain_core.language_models.v1.chat_models import BaseChatModelV1 from langchain_core.messages import AIMessage, AIMessageChunk, BaseMessage -from langchain_core.messages.v1 import AIMessage as AIMessageV1 -from langchain_core.messages.v1 import AIMessageChunk as AIMessageChunkV1 -from langchain_core.messages.v1 import MessageV1 from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult from langchain_core.runnables import RunnableConfig +from langchain_core.v1.chat_models import BaseChatModelV1 +from langchain_core.v1.messages import AIMessage as AIMessageV1 +from langchain_core.v1.messages import AIMessageChunk as AIMessageChunkV1 +from langchain_core.v1.messages import MessageV1 class FakeMessagesListChatModel(BaseChatModel): diff --git a/libs/core/langchain_core/language_models/v1/__init__.py b/libs/core/langchain_core/language_models/v1/__init__.py deleted file mode 100644 index eaffaa213ba..00000000000 --- a/libs/core/langchain_core/language_models/v1/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""LangChain v1.0 chat models.""" diff --git a/libs/core/langchain_core/messages/utils.py b/libs/core/langchain_core/messages/utils.py index affb0b15a53..287963d33c2 100644 --- a/libs/core/langchain_core/messages/utils.py +++ b/libs/core/langchain_core/messages/utils.py @@ -40,12 +40,12 @@ from langchain_core.messages.human import HumanMessage, HumanMessageChunk from langchain_core.messages.modifier import RemoveMessage from langchain_core.messages.system import SystemMessage, SystemMessageChunk from langchain_core.messages.tool import ToolCall, ToolMessage, ToolMessageChunk -from langchain_core.messages.v1 import AIMessage as AIMessageV1 -from langchain_core.messages.v1 import AIMessageChunk as AIMessageChunkV1 -from langchain_core.messages.v1 import HumanMessage as HumanMessageV1 -from langchain_core.messages.v1 import MessageV1, MessageV1Types -from langchain_core.messages.v1 import SystemMessage as SystemMessageV1 -from langchain_core.messages.v1 import ToolMessage as ToolMessageV1 +from langchain_core.v1.messages import AIMessage as AIMessageV1 +from langchain_core.v1.messages import AIMessageChunk as AIMessageChunkV1 +from langchain_core.v1.messages import HumanMessage as HumanMessageV1 +from langchain_core.v1.messages import MessageV1, MessageV1Types +from langchain_core.v1.messages import SystemMessage as SystemMessageV1 +from langchain_core.v1.messages import ToolMessage as ToolMessageV1 if TYPE_CHECKING: from langchain_text_splitters import TextSplitter diff --git a/libs/core/langchain_core/output_parsers/base.py b/libs/core/langchain_core/output_parsers/base.py index b91ac475842..af144af70de 100644 --- a/libs/core/langchain_core/output_parsers/base.py +++ b/libs/core/langchain_core/output_parsers/base.py @@ -18,10 +18,10 @@ from typing_extensions import override from langchain_core.language_models import LanguageModelOutput from langchain_core.messages import AnyMessage, BaseMessage -from langchain_core.messages.v1 import AIMessage, MessageV1, MessageV1Types from langchain_core.outputs import ChatGeneration, Generation from langchain_core.runnables import Runnable, RunnableConfig, RunnableSerializable from langchain_core.runnables.config import run_in_executor +from langchain_core.v1.messages import AIMessage, MessageV1, MessageV1Types if TYPE_CHECKING: from langchain_core.prompt_values import PromptValue diff --git a/libs/core/langchain_core/output_parsers/json.py b/libs/core/langchain_core/output_parsers/json.py index 1384774afff..54f1196b80e 100644 --- a/libs/core/langchain_core/output_parsers/json.py +++ b/libs/core/langchain_core/output_parsers/json.py @@ -13,7 +13,6 @@ from pydantic.v1 import BaseModel from typing_extensions import override from langchain_core.exceptions import OutputParserException -from langchain_core.messages.v1 import AIMessage from langchain_core.output_parsers.format_instructions import JSON_FORMAT_INSTRUCTIONS from langchain_core.output_parsers.transform import BaseCumulativeTransformOutputParser from langchain_core.outputs import Generation @@ -22,6 +21,7 @@ from langchain_core.utils.json import ( parse_json_markdown, parse_partial_json, ) +from langchain_core.v1.messages import AIMessage # Union type needs to be last assignment to PydanticBaseModel to make mypy happy. PydanticBaseModel = Union[BaseModel, pydantic.BaseModel] diff --git a/libs/core/langchain_core/output_parsers/list.py b/libs/core/langchain_core/output_parsers/list.py index ce825ee2b7d..08b8c65e750 100644 --- a/libs/core/langchain_core/output_parsers/list.py +++ b/libs/core/langchain_core/output_parsers/list.py @@ -12,8 +12,8 @@ from typing import TYPE_CHECKING, TypeVar, Union from typing_extensions import override from langchain_core.messages import BaseMessage -from langchain_core.messages.v1 import AIMessage from langchain_core.output_parsers.transform import BaseTransformOutputParser +from langchain_core.v1.messages import AIMessage if TYPE_CHECKING: from collections.abc import AsyncIterator, Iterator diff --git a/libs/core/langchain_core/output_parsers/openai_functions.py b/libs/core/langchain_core/output_parsers/openai_functions.py index 5abf5c70036..6c333fc797e 100644 --- a/libs/core/langchain_core/output_parsers/openai_functions.py +++ b/libs/core/langchain_core/output_parsers/openai_functions.py @@ -11,13 +11,13 @@ from pydantic.v1 import BaseModel as BaseModelV1 from typing_extensions import override from langchain_core.exceptions import OutputParserException -from langchain_core.messages.v1 import AIMessage from langchain_core.output_parsers import ( BaseCumulativeTransformOutputParser, BaseGenerationOutputParser, ) from langchain_core.output_parsers.json import parse_partial_json from langchain_core.outputs import ChatGeneration, Generation +from langchain_core.v1.messages import AIMessage class OutputFunctionsParser(BaseGenerationOutputParser[Any]): diff --git a/libs/core/langchain_core/output_parsers/openai_tools.py b/libs/core/langchain_core/output_parsers/openai_tools.py index 8a8cc67e4d9..f4bf85b970e 100644 --- a/libs/core/langchain_core/output_parsers/openai_tools.py +++ b/libs/core/langchain_core/output_parsers/openai_tools.py @@ -12,11 +12,11 @@ from langchain_core.exceptions import OutputParserException from langchain_core.messages import AIMessage, InvalidToolCall from langchain_core.messages.tool import invalid_tool_call from langchain_core.messages.tool import tool_call as create_tool_call -from langchain_core.messages.v1 import AIMessage as AIMessageV1 from langchain_core.output_parsers.transform import BaseCumulativeTransformOutputParser from langchain_core.outputs import ChatGeneration, Generation from langchain_core.utils.json import parse_partial_json from langchain_core.utils.pydantic import TypeBaseModel +from langchain_core.v1.messages import AIMessage as AIMessageV1 logger = logging.getLogger(__name__) diff --git a/libs/core/langchain_core/output_parsers/pydantic.py b/libs/core/langchain_core/output_parsers/pydantic.py index 98db8a331aa..544e47f404f 100644 --- a/libs/core/langchain_core/output_parsers/pydantic.py +++ b/libs/core/langchain_core/output_parsers/pydantic.py @@ -8,13 +8,13 @@ from pydantic import SkipValidation from typing_extensions import override from langchain_core.exceptions import OutputParserException -from langchain_core.messages.v1 import AIMessage from langchain_core.output_parsers import JsonOutputParser from langchain_core.outputs import Generation from langchain_core.utils.pydantic import ( PydanticBaseModel, TBaseModel, ) +from langchain_core.v1.messages import AIMessage class PydanticOutputParser(JsonOutputParser, Generic[TBaseModel]): diff --git a/libs/core/langchain_core/output_parsers/transform.py b/libs/core/langchain_core/output_parsers/transform.py index aec4075d217..b2eccc674be 100644 --- a/libs/core/langchain_core/output_parsers/transform.py +++ b/libs/core/langchain_core/output_parsers/transform.py @@ -12,7 +12,6 @@ from typing import ( from typing_extensions import override from langchain_core.messages import BaseMessage, BaseMessageChunk -from langchain_core.messages.v1 import AIMessage, AIMessageChunk from langchain_core.output_parsers.base import BaseOutputParser, T from langchain_core.outputs import ( ChatGeneration, @@ -21,6 +20,7 @@ from langchain_core.outputs import ( GenerationChunk, ) from langchain_core.runnables.config import run_in_executor +from langchain_core.v1.messages import AIMessage, AIMessageChunk if TYPE_CHECKING: from collections.abc import AsyncIterator, Iterator diff --git a/libs/core/langchain_core/output_parsers/xml.py b/libs/core/langchain_core/output_parsers/xml.py index e5f023d228c..318a783c6b2 100644 --- a/libs/core/langchain_core/output_parsers/xml.py +++ b/libs/core/langchain_core/output_parsers/xml.py @@ -13,9 +13,9 @@ from typing_extensions import override from langchain_core.exceptions import OutputParserException from langchain_core.messages import BaseMessage from langchain_core.messages.utils import convert_from_v1_message -from langchain_core.messages.v1 import AIMessage from langchain_core.output_parsers.transform import BaseTransformOutputParser from langchain_core.runnables.utils import AddableDict +from langchain_core.v1.messages import AIMessage XML_FORMAT_INSTRUCTIONS = """The output should be formatted as a XML file. 1. Output should conform to the tags below. diff --git a/libs/core/langchain_core/prompt_values.py b/libs/core/langchain_core/prompt_values.py index 68007a7c8ce..fec4de6f741 100644 --- a/libs/core/langchain_core/prompt_values.py +++ b/libs/core/langchain_core/prompt_values.py @@ -23,11 +23,11 @@ from langchain_core.messages import ( get_buffer_string, ) from langchain_core.messages import content_blocks as types -from langchain_core.messages.v1 import AIMessage as AIMessageV1 -from langchain_core.messages.v1 import HumanMessage as HumanMessageV1 -from langchain_core.messages.v1 import MessageV1, ResponseMetadata -from langchain_core.messages.v1 import SystemMessage as SystemMessageV1 -from langchain_core.messages.v1 import ToolMessage as ToolMessageV1 +from langchain_core.v1.messages import AIMessage as AIMessageV1 +from langchain_core.v1.messages import HumanMessage as HumanMessageV1 +from langchain_core.v1.messages import MessageV1, ResponseMetadata +from langchain_core.v1.messages import SystemMessage as SystemMessageV1 +from langchain_core.v1.messages import ToolMessage as ToolMessageV1 def _convert_to_v1(message: BaseMessage) -> MessageV1: diff --git a/libs/core/langchain_core/tools/base.py b/libs/core/langchain_core/tools/base.py index 5573a08a1d5..7eacb7347a5 100644 --- a/libs/core/langchain_core/tools/base.py +++ b/libs/core/langchain_core/tools/base.py @@ -47,7 +47,6 @@ from langchain_core.callbacks import ( Callbacks, ) from langchain_core.messages.tool import ToolCall, ToolMessage, ToolOutputMixin -from langchain_core.messages.v1 import ToolMessage as ToolMessageV1 from langchain_core.runnables import ( RunnableConfig, RunnableSerializable, @@ -69,6 +68,7 @@ from langchain_core.utils.pydantic import ( is_pydantic_v1_subclass, is_pydantic_v2_subclass, ) +from langchain_core.v1.messages import ToolMessage as ToolMessageV1 if TYPE_CHECKING: import uuid diff --git a/libs/core/langchain_core/tracers/base.py b/libs/core/langchain_core/tracers/base.py index 13fbe0e90ce..d8ef0480484 100644 --- a/libs/core/langchain_core/tracers/base.py +++ b/libs/core/langchain_core/tracers/base.py @@ -16,8 +16,8 @@ from typing_extensions import override from langchain_core.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler from langchain_core.exceptions import TracerException # noqa: F401 -from langchain_core.messages.v1 import AIMessage, AIMessageChunk, MessageV1 from langchain_core.tracers.core import _TracerCore +from langchain_core.v1.messages import AIMessage, AIMessageChunk, MessageV1 if TYPE_CHECKING: from collections.abc import Sequence diff --git a/libs/core/langchain_core/tracers/core.py b/libs/core/langchain_core/tracers/core.py index cdef8ddcb7b..c7074c9af2e 100644 --- a/libs/core/langchain_core/tracers/core.py +++ b/libs/core/langchain_core/tracers/core.py @@ -19,12 +19,6 @@ from typing import ( from langchain_core.exceptions import TracerException from langchain_core.load import dumpd from langchain_core.messages.utils import convert_from_v1_message -from langchain_core.messages.v1 import ( - AIMessage, - AIMessageChunk, - MessageV1, - MessageV1Types, -) from langchain_core.outputs import ( ChatGeneration, ChatGenerationChunk, @@ -32,6 +26,12 @@ from langchain_core.outputs import ( LLMResult, ) from langchain_core.tracers.schemas import Run +from langchain_core.v1.messages import ( + AIMessage, + AIMessageChunk, + MessageV1, + MessageV1Types, +) if TYPE_CHECKING: from collections.abc import Coroutine, Sequence diff --git a/libs/core/langchain_core/tracers/event_stream.py b/libs/core/langchain_core/tracers/event_stream.py index 57289431935..32d3b399c7e 100644 --- a/libs/core/langchain_core/tracers/event_stream.py +++ b/libs/core/langchain_core/tracers/event_stream.py @@ -19,7 +19,6 @@ from typing_extensions import NotRequired, TypedDict, override from langchain_core.callbacks.base import AsyncCallbackHandler from langchain_core.messages import AIMessageChunk, BaseMessage, BaseMessageChunk -from langchain_core.messages.v1 import MessageV1 from langchain_core.outputs import ( ChatGenerationChunk, GenerationChunk, @@ -39,15 +38,16 @@ from langchain_core.runnables.utils import ( from langchain_core.tracers._streaming import _StreamingCallbackHandler from langchain_core.tracers.memory_stream import _MemoryStream from langchain_core.utils.aiter import aclosing, py_anext +from langchain_core.v1.messages import MessageV1 if TYPE_CHECKING: from collections.abc import AsyncIterator, Iterator, Sequence from langchain_core.documents import Document - from langchain_core.messages.v1 import AIMessage as AIMessageV1 - from langchain_core.messages.v1 import AIMessageChunk as AIMessageChunkV1 from langchain_core.runnables import Runnable, RunnableConfig from langchain_core.tracers.log_stream import LogEntry + from langchain_core.v1.messages import AIMessage as AIMessageV1 + from langchain_core.v1.messages import AIMessageChunk as AIMessageChunkV1 logger = logging.getLogger(__name__) diff --git a/libs/core/langchain_core/tracers/langchain.py b/libs/core/langchain_core/tracers/langchain.py index dbd8a63a2b7..5d5f1e5ad80 100644 --- a/libs/core/langchain_core/tracers/langchain.py +++ b/libs/core/langchain_core/tracers/langchain.py @@ -22,14 +22,14 @@ from typing_extensions import override from langchain_core.env import get_runtime_environment from langchain_core.load import dumpd from langchain_core.messages.utils import convert_from_v1_message -from langchain_core.messages.v1 import MessageV1Types from langchain_core.tracers.base import BaseTracer from langchain_core.tracers.schemas import Run +from langchain_core.v1.messages import MessageV1Types if TYPE_CHECKING: from langchain_core.messages import BaseMessage - from langchain_core.messages.v1 import AIMessageChunk, MessageV1 from langchain_core.outputs import ChatGenerationChunk, GenerationChunk + from langchain_core.v1.messages import AIMessageChunk, MessageV1 logger = logging.getLogger(__name__) _LOGGED = set() diff --git a/libs/core/langchain_core/tracers/log_stream.py b/libs/core/langchain_core/tracers/log_stream.py index 0ce4b788972..5ddc7e4ec69 100644 --- a/libs/core/langchain_core/tracers/log_stream.py +++ b/libs/core/langchain_core/tracers/log_stream.py @@ -32,9 +32,9 @@ if TYPE_CHECKING: from collections.abc import AsyncIterator, Iterator, Sequence from uuid import UUID - from langchain_core.messages.v1 import AIMessageChunk from langchain_core.runnables.utils import Input, Output from langchain_core.tracers.schemas import Run + from langchain_core.v1.messages import AIMessageChunk class LogEntry(TypedDict): diff --git a/libs/core/langchain_core/v1/__init__.py b/libs/core/langchain_core/v1/__init__.py new file mode 100644 index 00000000000..ad3d9e1cc1c --- /dev/null +++ b/libs/core/langchain_core/v1/__init__.py @@ -0,0 +1 @@ +"""LangChain v1.0 types.""" diff --git a/libs/core/langchain_core/language_models/v1/chat_models.py b/libs/core/langchain_core/v1/chat_models.py similarity index 99% rename from libs/core/langchain_core/language_models/v1/chat_models.py rename to libs/core/langchain_core/v1/chat_models.py index f8d83cb4bbe..7a588e9691d 100644 --- a/libs/core/langchain_core/language_models/v1/chat_models.py +++ b/libs/core/langchain_core/v1/chat_models.py @@ -52,10 +52,6 @@ from langchain_core.messages.utils import ( convert_from_v1_message, convert_to_messages_v1, ) -from langchain_core.messages.v1 import AIMessage as AIMessageV1 -from langchain_core.messages.v1 import AIMessageChunk as AIMessageChunkV1 -from langchain_core.messages.v1 import HumanMessage as HumanMessageV1 -from langchain_core.messages.v1 import MessageV1, add_ai_message_chunks from langchain_core.outputs import ( ChatGeneration, ChatGenerationChunk, @@ -71,6 +67,10 @@ from langchain_core.utils.function_calling import ( convert_to_openai_tool, ) from langchain_core.utils.pydantic import TypeBaseModel, is_basemodel_subclass +from langchain_core.v1.messages import AIMessage as AIMessageV1 +from langchain_core.v1.messages import AIMessageChunk as AIMessageChunkV1 +from langchain_core.v1.messages import HumanMessage as HumanMessageV1 +from langchain_core.v1.messages import MessageV1, add_ai_message_chunks if TYPE_CHECKING: from langchain_core.output_parsers.base import OutputParserLike diff --git a/libs/core/langchain_core/messages/v1.py b/libs/core/langchain_core/v1/messages.py similarity index 100% rename from libs/core/langchain_core/messages/v1.py rename to libs/core/langchain_core/v1/messages.py diff --git a/libs/core/tests/benchmarks/test_async_callbacks.py b/libs/core/tests/benchmarks/test_async_callbacks.py index c29598c3aa7..4b5938110c4 100644 --- a/libs/core/tests/benchmarks/test_async_callbacks.py +++ b/libs/core/tests/benchmarks/test_async_callbacks.py @@ -10,9 +10,9 @@ from typing_extensions import override from langchain_core.callbacks.base import AsyncCallbackHandler from langchain_core.language_models import GenericFakeChatModel from langchain_core.messages import AIMessage, BaseMessage -from langchain_core.messages.v1 import AIMessageChunk as AIMessageChunkV1 -from langchain_core.messages.v1 import MessageV1 from langchain_core.outputs import ChatGenerationChunk, GenerationChunk +from langchain_core.v1.messages import AIMessageChunk as AIMessageChunkV1 +from langchain_core.v1.messages import MessageV1 class MyCustomAsyncHandler(AsyncCallbackHandler): diff --git a/libs/core/tests/unit_tests/fake/callbacks.py b/libs/core/tests/unit_tests/fake/callbacks.py index 30a79601f07..ecd849b8eff 100644 --- a/libs/core/tests/unit_tests/fake/callbacks.py +++ b/libs/core/tests/unit_tests/fake/callbacks.py @@ -9,7 +9,7 @@ from typing_extensions import override from langchain_core.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler from langchain_core.messages import BaseMessage -from langchain_core.messages.v1 import MessageV1 +from langchain_core.v1.messages import MessageV1 class BaseFakeCallbackHandler(BaseModel): diff --git a/libs/core/tests/unit_tests/fake/test_fake_chat_model.py b/libs/core/tests/unit_tests/fake/test_fake_chat_model.py index 40ed46f0745..4721877e5cc 100644 --- a/libs/core/tests/unit_tests/fake/test_fake_chat_model.py +++ b/libs/core/tests/unit_tests/fake/test_fake_chat_model.py @@ -15,9 +15,9 @@ from langchain_core.language_models import ( ParrotFakeChatModel, ) from langchain_core.messages import AIMessage, AIMessageChunk, BaseMessage, HumanMessage -from langchain_core.messages.v1 import AIMessageChunk as AIMessageChunkV1 -from langchain_core.messages.v1 import MessageV1 from langchain_core.outputs import ChatGenerationChunk, GenerationChunk +from langchain_core.v1.messages import AIMessageChunk as AIMessageChunkV1 +from langchain_core.v1.messages import MessageV1 from tests.unit_tests.stubs import ( _any_id_ai_message, _any_id_ai_message_chunk, diff --git a/libs/core/tests/unit_tests/language_models/chat_models/test_base.py b/libs/core/tests/unit_tests/language_models/chat_models/test_base.py index e653d35fda8..5d1d4f581a2 100644 --- a/libs/core/tests/unit_tests/language_models/chat_models/test_base.py +++ b/libs/core/tests/unit_tests/language_models/chat_models/test_base.py @@ -25,7 +25,6 @@ from langchain_core.messages import ( HumanMessage, SystemMessage, ) -from langchain_core.messages.v1 import AIMessageChunk as AIMessageChunkV1 from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult from langchain_core.outputs.llm_result import LLMResult from langchain_core.tracers import LogStreamCallbackHandler @@ -33,6 +32,7 @@ from langchain_core.tracers.base import BaseTracer from langchain_core.tracers.context import collect_runs from langchain_core.tracers.event_stream import _AstreamEventsCallbackHandler from langchain_core.tracers.schemas import Run +from langchain_core.v1.messages import AIMessageChunk as AIMessageChunkV1 from tests.unit_tests.fake.callbacks import ( BaseFakeCallbackHandler, FakeAsyncCallbackHandler, diff --git a/libs/core/tests/unit_tests/messages/test_response_metadata.py b/libs/core/tests/unit_tests/messages/test_response_metadata.py index efecf1cf2e1..44951ba6672 100644 --- a/libs/core/tests/unit_tests/messages/test_response_metadata.py +++ b/libs/core/tests/unit_tests/messages/test_response_metadata.py @@ -1,6 +1,6 @@ """Unit tests for ResponseMetadata TypedDict.""" -from langchain_core.messages.v1 import AIMessage, AIMessageChunk, ResponseMetadata +from langchain_core.v1.messages import AIMessage, AIMessageChunk, ResponseMetadata class TestResponseMetadata: 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 8794d3d2f65..aec5bc29c5e 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 @@ -8,12 +8,12 @@ from langchain_core.exceptions import OutputParserException from langchain_core.language_models import GenericFakeChatModel from langchain_core.language_models.fake_chat_models import GenericFakeChatModelV1 from langchain_core.messages import AIMessage -from langchain_core.messages.v1 import AIMessage as AIMessageV1 from langchain_core.output_parsers import ( BaseGenerationOutputParser, BaseTransformOutputParser, ) from langchain_core.outputs import ChatGeneration, Generation +from langchain_core.v1.messages import AIMessage as AIMessageV1 def test_base_generation_parser() -> None: diff --git a/libs/core/tests/unit_tests/output_parsers/test_openai_tools.py b/libs/core/tests/unit_tests/output_parsers/test_openai_tools.py index 143867cbe35..28b7f92adf1 100644 --- a/libs/core/tests/unit_tests/output_parsers/test_openai_tools.py +++ b/libs/core/tests/unit_tests/output_parsers/test_openai_tools.py @@ -10,14 +10,14 @@ from langchain_core.messages import ( BaseMessage, ToolCallChunk, ) -from langchain_core.messages.v1 import AIMessage as AIMessageV1 -from langchain_core.messages.v1 import AIMessageChunk as AIMessageChunkV1 from langchain_core.output_parsers.openai_tools import ( JsonOutputKeyToolsParser, JsonOutputToolsParser, PydanticToolsParser, ) from langchain_core.outputs import ChatGeneration +from langchain_core.v1.messages import AIMessage as AIMessageV1 +from langchain_core.v1.messages import AIMessageChunk as AIMessageChunkV1 STREAMED_MESSAGES: list = [ AIMessageChunk(content=""), diff --git a/libs/core/tests/unit_tests/runnables/__snapshots__/test_graph.ambr b/libs/core/tests/unit_tests/runnables/__snapshots__/test_graph.ambr index acce9007092..c014396ecab 100644 --- a/libs/core/tests/unit_tests/runnables/__snapshots__/test_graph.ambr +++ b/libs/core/tests/unit_tests/runnables/__snapshots__/test_graph.ambr @@ -2529,7 +2529,7 @@ 'title': 'ToolMessage', 'type': 'object', }), - 'langchain_core__messages__v1__AIMessage': dict({ + 'langchain_core__v1__messages__AIMessage': dict({ 'properties': dict({ 'content': dict({ 'items': dict({ @@ -2655,7 +2655,7 @@ 'title': 'AIMessage', 'type': 'object', }), - 'langchain_core__messages__v1__AIMessageChunk': dict({ + 'langchain_core__v1__messages__AIMessageChunk': dict({ 'properties': dict({ 'content': dict({ 'items': dict({ @@ -2781,7 +2781,7 @@ 'title': 'AIMessageChunk', 'type': 'object', }), - 'langchain_core__messages__v1__HumanMessage': dict({ + 'langchain_core__v1__messages__HumanMessage': dict({ 'properties': dict({ 'content': dict({ 'items': dict({ @@ -2869,7 +2869,7 @@ 'title': 'HumanMessage', 'type': 'object', }), - 'langchain_core__messages__v1__SystemMessage': dict({ + 'langchain_core__v1__messages__SystemMessage': dict({ 'properties': dict({ 'content': dict({ 'items': dict({ @@ -2969,7 +2969,7 @@ 'title': 'SystemMessage', 'type': 'object', }), - 'langchain_core__messages__v1__ToolMessage': dict({ + 'langchain_core__v1__messages__ToolMessage': dict({ 'properties': dict({ 'artifact': dict({ 'anyOf': list([ @@ -3123,19 +3123,19 @@ ]), }), dict({ - '$ref': '#/$defs/langchain_core__messages__v1__AIMessage', + '$ref': '#/$defs/langchain_core__v1__messages__AIMessage', }), dict({ - '$ref': '#/$defs/langchain_core__messages__v1__AIMessageChunk', + '$ref': '#/$defs/langchain_core__v1__messages__AIMessageChunk', }), dict({ - '$ref': '#/$defs/langchain_core__messages__v1__HumanMessage', + '$ref': '#/$defs/langchain_core__v1__messages__HumanMessage', }), dict({ - '$ref': '#/$defs/langchain_core__messages__v1__SystemMessage', + '$ref': '#/$defs/langchain_core__v1__messages__SystemMessage', }), dict({ - '$ref': '#/$defs/langchain_core__messages__v1__ToolMessage', + '$ref': '#/$defs/langchain_core__v1__messages__ToolMessage', }), ]), 'title': 'RunnableParallelInput', diff --git a/libs/core/tests/unit_tests/runnables/__snapshots__/test_runnable.ambr b/libs/core/tests/unit_tests/runnables/__snapshots__/test_runnable.ambr index fdc3ca0c813..0d4407d8ca4 100644 --- a/libs/core/tests/unit_tests/runnables/__snapshots__/test_runnable.ambr +++ b/libs/core/tests/unit_tests/runnables/__snapshots__/test_runnable.ambr @@ -9412,19 +9412,19 @@ ]), }), dict({ - '$ref': '#/definitions/langchain_core__messages__v1__AIMessage', + '$ref': '#/definitions/langchain_core__v1__messages__AIMessage', }), dict({ - '$ref': '#/definitions/langchain_core__messages__v1__AIMessageChunk', + '$ref': '#/definitions/langchain_core__v1__messages__AIMessageChunk', }), dict({ - '$ref': '#/definitions/langchain_core__messages__v1__HumanMessage', + '$ref': '#/definitions/langchain_core__v1__messages__HumanMessage', }), dict({ - '$ref': '#/definitions/langchain_core__messages__v1__SystemMessage', + '$ref': '#/definitions/langchain_core__v1__messages__SystemMessage', }), dict({ - '$ref': '#/definitions/langchain_core__messages__v1__ToolMessage', + '$ref': '#/definitions/langchain_core__v1__messages__ToolMessage', }), ]), 'definitions': dict({ @@ -11521,7 +11521,7 @@ 'title': 'ToolMessage', 'type': 'object', }), - 'langchain_core__messages__v1__AIMessage': dict({ + 'langchain_core__v1__messages__AIMessage': dict({ 'properties': dict({ 'content': dict({ 'items': dict({ @@ -11646,7 +11646,7 @@ 'title': 'AIMessage', 'type': 'object', }), - 'langchain_core__messages__v1__AIMessageChunk': dict({ + 'langchain_core__v1__messages__AIMessageChunk': dict({ 'properties': dict({ 'content': dict({ 'items': dict({ @@ -11771,7 +11771,7 @@ 'title': 'AIMessageChunk', 'type': 'object', }), - 'langchain_core__messages__v1__HumanMessage': dict({ + 'langchain_core__v1__messages__HumanMessage': dict({ 'properties': dict({ 'content': dict({ 'items': dict({ @@ -11858,7 +11858,7 @@ 'title': 'HumanMessage', 'type': 'object', }), - 'langchain_core__messages__v1__SystemMessage': dict({ + 'langchain_core__v1__messages__SystemMessage': dict({ 'properties': dict({ 'content': dict({ 'items': dict({ @@ -11957,7 +11957,7 @@ 'title': 'SystemMessage', 'type': 'object', }), - 'langchain_core__messages__v1__ToolMessage': dict({ + 'langchain_core__v1__messages__ToolMessage': dict({ 'properties': dict({ 'artifact': dict({ 'anyOf': list([ diff --git a/libs/core/tests/unit_tests/test_messages.py b/libs/core/tests/unit_tests/test_messages.py index 5da1ad20f5d..d59ebeee2a2 100644 --- a/libs/core/tests/unit_tests/test_messages.py +++ b/libs/core/tests/unit_tests/test_messages.py @@ -35,9 +35,9 @@ from langchain_core.messages.content_blocks import KNOWN_BLOCK_TYPES, ContentBlo from langchain_core.messages.tool import invalid_tool_call as create_invalid_tool_call from langchain_core.messages.tool import tool_call as create_tool_call from langchain_core.messages.tool import tool_call_chunk as create_tool_call_chunk -from langchain_core.messages.v1 import AIMessage as AIMessageV1 -from langchain_core.messages.v1 import AIMessageChunk as AIMessageChunkV1 from langchain_core.utils._merge import merge_lists +from langchain_core.v1.messages import AIMessage as AIMessageV1 +from langchain_core.v1.messages import AIMessageChunk as AIMessageChunkV1 def test_message_init() -> None: diff --git a/libs/core/tests/unit_tests/test_tools.py b/libs/core/tests/unit_tests/test_tools.py index cd52d37c9fa..339a4db826f 100644 --- a/libs/core/tests/unit_tests/test_tools.py +++ b/libs/core/tests/unit_tests/test_tools.py @@ -37,7 +37,6 @@ from langchain_core.callbacks.manager import ( from langchain_core.documents import Document from langchain_core.messages import ToolCall, ToolMessage from langchain_core.messages.tool import ToolOutputMixin -from langchain_core.messages.v1 import ToolMessage as ToolMessageV1 from langchain_core.retrievers import BaseRetriever from langchain_core.runnables import ( Runnable, @@ -69,6 +68,7 @@ from langchain_core.utils.pydantic import ( _create_subset_model, create_model_v2, ) +from langchain_core.v1.messages import ToolMessage as ToolMessageV1 from tests.unit_tests.fake.callbacks import FakeCallbackHandler from tests.unit_tests.pydantic_utils import _schema from tests.unit_tests.stubs import AnyStr diff --git a/libs/core/tests/unit_tests/tracers/test_async_base_tracer.py b/libs/core/tests/unit_tests/tracers/test_async_base_tracer.py index 377039cd627..98c465f900c 100644 --- a/libs/core/tests/unit_tests/tracers/test_async_base_tracer.py +++ b/libs/core/tests/unit_tests/tracers/test_async_base_tracer.py @@ -12,11 +12,11 @@ from freezegun import freeze_time from langchain_core.callbacks import AsyncCallbackManager from langchain_core.exceptions import TracerException from langchain_core.messages import HumanMessage -from langchain_core.messages.v1 import HumanMessage as HumanMessageV1 -from langchain_core.messages.v1 import MessageV1 from langchain_core.outputs import LLMResult from langchain_core.tracers.base import AsyncBaseTracer from langchain_core.tracers.schemas import Run +from langchain_core.v1.messages import HumanMessage as HumanMessageV1 +from langchain_core.v1.messages import MessageV1 if TYPE_CHECKING: from langchain_core.messages import BaseMessage diff --git a/libs/core/tests/unit_tests/tracers/test_base_tracer.py b/libs/core/tests/unit_tests/tracers/test_base_tracer.py index 193be3e8b8c..f4964e15ff8 100644 --- a/libs/core/tests/unit_tests/tracers/test_base_tracer.py +++ b/libs/core/tests/unit_tests/tracers/test_base_tracer.py @@ -15,12 +15,12 @@ from langsmith import Client, traceable from langchain_core.callbacks import CallbackManager from langchain_core.exceptions import TracerException from langchain_core.messages import HumanMessage -from langchain_core.messages.v1 import HumanMessage as HumanMessageV1 -from langchain_core.messages.v1 import MessageV1 from langchain_core.outputs import LLMResult from langchain_core.runnables import chain as as_runnable from langchain_core.tracers.base import BaseTracer from langchain_core.tracers.schemas import Run +from langchain_core.v1.messages import HumanMessage as HumanMessageV1 +from langchain_core.v1.messages import MessageV1 if TYPE_CHECKING: from langchain_core.messages import BaseMessage diff --git a/libs/langchain/langchain/agents/output_parsers/openai_functions.py b/libs/langchain/langchain/agents/output_parsers/openai_functions.py index 2c174a26c3d..a42e237b9af 100644 --- a/libs/langchain/langchain/agents/output_parsers/openai_functions.py +++ b/libs/langchain/langchain/agents/output_parsers/openai_functions.py @@ -9,8 +9,8 @@ from langchain_core.messages import ( BaseMessage, ) from langchain_core.messages.utils import convert_from_v1_message -from langchain_core.messages.v1 import AIMessage as AIMessageV1 from langchain_core.outputs import ChatGeneration, Generation +from langchain_core.v1.messages import AIMessage as AIMessageV1 from typing_extensions import override from langchain.agents.agent import AgentOutputParser diff --git a/libs/langchain/langchain/agents/output_parsers/openai_tools.py b/libs/langchain/langchain/agents/output_parsers/openai_tools.py index cb2d1091b21..eca9a186173 100644 --- a/libs/langchain/langchain/agents/output_parsers/openai_tools.py +++ b/libs/langchain/langchain/agents/output_parsers/openai_tools.py @@ -3,8 +3,8 @@ from typing import Union from langchain_core.agents import AgentAction, AgentFinish from langchain_core.messages import BaseMessage from langchain_core.messages.utils import convert_from_v1_message -from langchain_core.messages.v1 import AIMessage as AIMessageV1 from langchain_core.outputs import ChatGeneration, Generation +from langchain_core.v1.messages import AIMessage as AIMessageV1 from typing_extensions import override from langchain.agents.agent import MultiActionAgentOutputParser diff --git a/libs/langchain/langchain/agents/output_parsers/tools.py b/libs/langchain/langchain/agents/output_parsers/tools.py index 7f78960a1b6..4df63ad2010 100644 --- a/libs/langchain/langchain/agents/output_parsers/tools.py +++ b/libs/langchain/langchain/agents/output_parsers/tools.py @@ -10,8 +10,8 @@ from langchain_core.messages import ( ToolCall, ) from langchain_core.messages.utils import convert_from_v1_message -from langchain_core.messages.v1 import AIMessage as AIMessageV1 from langchain_core.outputs import ChatGeneration, Generation +from langchain_core.v1.messages import AIMessage as AIMessageV1 from typing_extensions import override from langchain.agents.agent import MultiActionAgentOutputParser diff --git a/libs/langchain/langchain/callbacks/streaming_aiter.py b/libs/langchain/langchain/callbacks/streaming_aiter.py index 2cf57d1f409..cb2bb823a7d 100644 --- a/libs/langchain/langchain/callbacks/streaming_aiter.py +++ b/libs/langchain/langchain/callbacks/streaming_aiter.py @@ -5,8 +5,8 @@ from collections.abc import AsyncIterator from typing import Any, Literal, Union, cast from langchain_core.callbacks import AsyncCallbackHandler -from langchain_core.messages.v1 import AIMessage from langchain_core.outputs import LLMResult +from langchain_core.v1.messages import AIMessage from typing_extensions import override # TODO If used by two LLM runs in parallel this won't work as expected diff --git a/libs/langchain/langchain/callbacks/streaming_aiter_final_only.py b/libs/langchain/langchain/callbacks/streaming_aiter_final_only.py index 1c37a4f43cf..4d9b755e69a 100644 --- a/libs/langchain/langchain/callbacks/streaming_aiter_final_only.py +++ b/libs/langchain/langchain/callbacks/streaming_aiter_final_only.py @@ -2,8 +2,8 @@ from __future__ import annotations from typing import Any, Optional, Union -from langchain_core.messages.v1 import AIMessage from langchain_core.outputs import LLMResult +from langchain_core.v1.messages import AIMessage from typing_extensions import override from langchain.callbacks.streaming_aiter import AsyncIteratorCallbackHandler diff --git a/libs/langchain/langchain/smith/evaluation/progress.py b/libs/langchain/langchain/smith/evaluation/progress.py index ba4e14cc573..a5242e1f8e0 100644 --- a/libs/langchain/langchain/smith/evaluation/progress.py +++ b/libs/langchain/langchain/smith/evaluation/progress.py @@ -7,8 +7,8 @@ from uuid import UUID from langchain_core.callbacks import base as base_callbacks from langchain_core.documents import Document -from langchain_core.messages.v1 import AIMessage from langchain_core.outputs import LLMResult +from langchain_core.v1.messages import AIMessage from typing_extensions import override diff --git a/libs/langchain/tests/unit_tests/callbacks/fake_callback_handler.py b/libs/langchain/tests/unit_tests/callbacks/fake_callback_handler.py index cad3d1f9cfe..cfc58c19789 100644 --- a/libs/langchain/tests/unit_tests/callbacks/fake_callback_handler.py +++ b/libs/langchain/tests/unit_tests/callbacks/fake_callback_handler.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.messages.v1 import MessageV1 +from langchain_core.v1.messages import MessageV1 from pydantic import BaseModel from typing_extensions import override diff --git a/libs/langchain/tests/unit_tests/llms/test_fake_chat_model.py b/libs/langchain/tests/unit_tests/llms/test_fake_chat_model.py index 561ac0a29a5..700e4538e2b 100644 --- a/libs/langchain/tests/unit_tests/llms/test_fake_chat_model.py +++ b/libs/langchain/tests/unit_tests/llms/test_fake_chat_model.py @@ -6,9 +6,9 @@ from uuid import UUID from langchain_core.callbacks.base import AsyncCallbackHandler from langchain_core.messages import AIMessage, AIMessageChunk, BaseMessage -from langchain_core.messages.v1 import AIMessageChunk as AIMessageChunkV1 -from langchain_core.messages.v1 import MessageV1 from langchain_core.outputs import ChatGenerationChunk, GenerationChunk +from langchain_core.v1.messages import AIMessageChunk as AIMessageChunkV1 +from langchain_core.v1.messages import MessageV1 from typing_extensions import override from tests.unit_tests.llms.fake_chat_model import GenericFakeChatModel diff --git a/libs/partners/openai/langchain_openai/chat_models/_compat.py b/libs/partners/openai/langchain_openai/chat_models/_compat.py index 6f4c09885ad..cfb36ccec0b 100644 --- a/libs/partners/openai/langchain_openai/chat_models/_compat.py +++ b/libs/partners/openai/langchain_openai/chat_models/_compat.py @@ -73,7 +73,7 @@ from typing import Any, Literal, Optional, Union, cast from langchain_core.messages import AIMessage, is_data_content_block from langchain_core.messages import content_blocks as types -from langchain_core.messages.v1 import AIMessage as AIMessageV1 +from langchain_core.v1.messages import AIMessage as AIMessageV1 _FUNCTION_CALL_IDS_MAP_KEY = "__openai_function_call_ids__" diff --git a/libs/partners/openai/langchain_openai/chat_models/base_v1.py b/libs/partners/openai/langchain_openai/chat_models/base_v1.py index dcf2e27d258..ffe4529bbae 100644 --- a/libs/partners/openai/langchain_openai/chat_models/base_v1.py +++ b/libs/partners/openai/langchain_openai/chat_models/base_v1.py @@ -38,11 +38,6 @@ from langchain_core.callbacks import ( ) from langchain_core.language_models import LanguageModelInput from langchain_core.language_models.chat_models import LangSmithParams -from langchain_core.language_models.v1.chat_models import ( - BaseChatModelV1, - agenerate_from_stream, - generate_from_stream, -) from langchain_core.messages import ( InvalidToolCall, ToolCall, @@ -55,12 +50,6 @@ from langchain_core.messages.ai import ( UsageMetadata, ) from langchain_core.messages.tool import tool_call_chunk -from langchain_core.messages.v1 import AIMessage as AIMessageV1 -from langchain_core.messages.v1 import AIMessageChunk as AIMessageChunkV1 -from langchain_core.messages.v1 import HumanMessage as HumanMessageV1 -from langchain_core.messages.v1 import MessageV1, ResponseMetadata -from langchain_core.messages.v1 import SystemMessage as SystemMessageV1 -from langchain_core.messages.v1 import ToolMessage as ToolMessageV1 from langchain_core.output_parsers import JsonOutputParser, PydanticOutputParser from langchain_core.output_parsers.openai_tools import ( JsonOutputKeyToolsParser, @@ -88,6 +77,17 @@ from langchain_core.utils.pydantic import ( is_basemodel_subclass, ) from langchain_core.utils.utils import _build_model_kwargs, from_env, secret_from_env +from langchain_core.v1.chat_models import ( + BaseChatModelV1, + agenerate_from_stream, + generate_from_stream, +) +from langchain_core.v1.messages import AIMessage as AIMessageV1 +from langchain_core.v1.messages import AIMessageChunk as AIMessageChunkV1 +from langchain_core.v1.messages import HumanMessage as HumanMessageV1 +from langchain_core.v1.messages import MessageV1, ResponseMetadata +from langchain_core.v1.messages import SystemMessage as SystemMessageV1 +from langchain_core.v1.messages import ToolMessage as ToolMessageV1 from pydantic import BaseModel, ConfigDict, Field, SecretStr, model_validator from pydantic.v1 import BaseModel as BaseModelV1 from typing_extensions import Self diff --git a/libs/partners/openai/tests/integration_tests/chat_models/test_responses_api.py b/libs/partners/openai/tests/integration_tests/chat_models/test_responses_api.py index 7613e69e337..7ba8b4a8aee 100644 --- a/libs/partners/openai/tests/integration_tests/chat_models/test_responses_api.py +++ b/libs/partners/openai/tests/integration_tests/chat_models/test_responses_api.py @@ -14,9 +14,9 @@ from langchain_core.messages import ( HumanMessage, MessageLikeRepresentation, ) -from langchain_core.messages.v1 import AIMessage as AIMessageV1 -from langchain_core.messages.v1 import AIMessageChunk as AIMessageChunkV1 -from langchain_core.messages.v1 import HumanMessage as HumanMessageV1 +from langchain_core.v1.messages import AIMessage as AIMessageV1 +from langchain_core.v1.messages import AIMessageChunk as AIMessageChunkV1 +from langchain_core.v1.messages import HumanMessage as HumanMessageV1 from pydantic import BaseModel from typing_extensions import TypedDict 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 c6ef68d142a..119bd1282ab 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 @@ -22,11 +22,11 @@ from langchain_core.messages import ( ) from langchain_core.messages import content_blocks as types from langchain_core.messages.ai import UsageMetadata -from langchain_core.messages.v1 import AIMessage as AIMessageV1 from langchain_core.outputs import ChatGeneration, ChatResult from langchain_core.runnables import RunnableLambda from langchain_core.tracers.base import BaseTracer from langchain_core.tracers.schemas import Run +from langchain_core.v1.messages import AIMessage as AIMessageV1 from openai.types.responses import ResponseOutputMessage, ResponseReasoningItem from openai.types.responses.response import IncompleteDetails, Response, ResponseUsage from openai.types.responses.response_error import ResponseError diff --git a/libs/partners/openai/tests/unit_tests/chat_models/test_responses_stream.py b/libs/partners/openai/tests/unit_tests/chat_models/test_responses_stream.py index 247892f157d..b837fa94d70 100644 --- a/libs/partners/openai/tests/unit_tests/chat_models/test_responses_stream.py +++ b/libs/partners/openai/tests/unit_tests/chat_models/test_responses_stream.py @@ -2,7 +2,7 @@ from typing import Any, Optional from unittest.mock import MagicMock, patch from langchain_core.messages import AIMessageChunk, BaseMessageChunk -from langchain_core.messages.v1 import AIMessageChunk as AIMessageChunkV1 +from langchain_core.v1.messages import AIMessageChunk as AIMessageChunkV1 from openai.types.responses import ( ResponseCompletedEvent, ResponseContentPartAddedEvent, diff --git a/libs/partners/openai/tests/unit_tests/fake/callbacks.py b/libs/partners/openai/tests/unit_tests/fake/callbacks.py index cee856d0a56..e3e695fcc6b 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.messages.v1 import MessageV1 +from langchain_core.v1.messages import MessageV1 from pydantic import BaseModel From 71f013888599a6c4e7e9789b1b8bf2c82fbc58ce Mon Sep 17 00:00:00 2001 From: ccurme Date: Tue, 5 Aug 2025 10:35:33 -0300 Subject: [PATCH 3/3] refactor(core): rename BaseChatModelV1 -> BaseChatModel in v1 namespace (#32404) --- .../langchain_core/language_models/fake_chat_models.py | 2 +- libs/core/langchain_core/v1/chat_models.py | 8 ++++---- .../openai/langchain_openai/chat_models/base_v1.py | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libs/core/langchain_core/language_models/fake_chat_models.py b/libs/core/langchain_core/language_models/fake_chat_models.py index 6d1b5347b9c..a4ecd87d627 100644 --- a/libs/core/langchain_core/language_models/fake_chat_models.py +++ b/libs/core/langchain_core/language_models/fake_chat_models.py @@ -16,7 +16,7 @@ from langchain_core.language_models.chat_models import BaseChatModel, SimpleChat from langchain_core.messages import AIMessage, AIMessageChunk, BaseMessage from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult from langchain_core.runnables import RunnableConfig -from langchain_core.v1.chat_models import BaseChatModelV1 +from langchain_core.v1.chat_models import BaseChatModel as BaseChatModelV1 from langchain_core.v1.messages import AIMessage as AIMessageV1 from langchain_core.v1.messages import AIMessageChunk as AIMessageChunkV1 from langchain_core.v1.messages import MessageV1 diff --git a/libs/core/langchain_core/v1/chat_models.py b/libs/core/langchain_core/v1/chat_models.py index 7a588e9691d..5b6da359401 100644 --- a/libs/core/langchain_core/v1/chat_models.py +++ b/libs/core/langchain_core/v1/chat_models.py @@ -189,7 +189,7 @@ def _format_ls_structured_output(ls_structured_output_format: Optional[dict]) -> return ls_structured_output_format_dict -class BaseChatModelV1(RunnableSerializable[LanguageModelInput, AIMessageV1], ABC): +class BaseChatModel(RunnableSerializable[LanguageModelInput, AIMessageV1], ABC): """Base class for chat models. Key imperative methods: @@ -383,8 +383,8 @@ class BaseChatModelV1(RunnableSerializable[LanguageModelInput, AIMessageV1], ABC **kwargs: Any, ) -> bool: """Determine if a given model call should hit the streaming API.""" - sync_not_implemented = type(self)._stream == BaseChatModelV1._stream # noqa: SLF001 - async_not_implemented = type(self)._astream == BaseChatModelV1._astream # noqa: SLF001 + sync_not_implemented = type(self)._stream == BaseChatModel._stream # noqa: SLF001 + async_not_implemented = type(self)._astream == BaseChatModel._astream # noqa: SLF001 # Check if streaming is implemented. if (not async_api) and sync_not_implemented: @@ -945,7 +945,7 @@ class BaseChatModelV1(RunnableSerializable[LanguageModelInput, AIMessageV1], ABC PydanticToolsParser, ) - if type(self).bind_tools is BaseChatModelV1.bind_tools: + if type(self).bind_tools is BaseChatModel.bind_tools: msg = "with_structured_output is not implemented for this model." raise NotImplementedError(msg) diff --git a/libs/partners/openai/langchain_openai/chat_models/base_v1.py b/libs/partners/openai/langchain_openai/chat_models/base_v1.py index ffe4529bbae..d7dca28d3e4 100644 --- a/libs/partners/openai/langchain_openai/chat_models/base_v1.py +++ b/libs/partners/openai/langchain_openai/chat_models/base_v1.py @@ -78,7 +78,7 @@ from langchain_core.utils.pydantic import ( ) from langchain_core.utils.utils import _build_model_kwargs, from_env, secret_from_env from langchain_core.v1.chat_models import ( - BaseChatModelV1, + BaseChatModel, agenerate_from_stream, generate_from_stream, ) @@ -387,7 +387,7 @@ class _AllReturnType(TypedDict): parsing_error: Optional[BaseException] -class BaseChatOpenAI(BaseChatModelV1): +class BaseChatOpenAI(BaseChatModel): client: Any = Field(default=None, exclude=True) #: :meta private: async_client: Any = Field(default=None, exclude=True) #: :meta private: root_client: Any = Field(default=None, exclude=True) #: :meta private: @@ -1631,7 +1631,7 @@ class BaseChatOpenAI(BaseChatModelV1): kwargs: Additional keyword args are passed through to the model. Returns: - A Runnable that takes same inputs as a :class:`from langchain_core.language_models.v1.chat_models import BaseChatModelV1`. + A Runnable that takes same inputs as a :class:`from langchain_core.v1.chat_models import BaseChatModel`. | If ``include_raw`` is False and ``schema`` is a Pydantic class, Runnable outputs an instance of ``schema`` (i.e., a Pydantic object). Otherwise, if ``include_raw`` is False then Runnable outputs a dict. @@ -2646,7 +2646,7 @@ class ChatOpenAI(BaseChatOpenAI): # type: ignore[override] kwargs: Additional keyword args are passed through to the model. Returns: - A Runnable that takes same inputs as a :class:`from langchain_core.language_models.v1.chat_models import BaseChatModelV1`. + A Runnable that takes same inputs as a :class:`from langchain_core.v1.chat_models import BaseChatModel`. | If ``include_raw`` is False and ``schema`` is a Pydantic class, Runnable outputs an instance of ``schema`` (i.e., a Pydantic object). Otherwise, if ``include_raw`` is False then Runnable outputs a dict.