diff --git a/libs/langgraph/langgraph/graph/message.py b/libs/langgraph/langgraph/graph/message.py index e20c22185..9070aaa69 100644 --- a/libs/langgraph/langgraph/graph/message.py +++ b/libs/langgraph/langgraph/graph/message.py @@ -22,10 +22,11 @@ from langchain_core.messages import ( convert_to_messages, message_chunk_to_message, ) -from typing_extensions import TypedDict +from typing_extensions import TypedDict, deprecated from langgraph._internal._constants import CONF, CONFIG_KEY_SEND, NS_SEP from langgraph.graph.state import StateGraph +from langgraph.warnings import LangGraphDeprecatedSinceV10 __all__ = ( "add_messages", @@ -233,9 +234,16 @@ def add_messages( return merged +@deprecated( + "MessageGraph is deprecated in LangGraph v1.0.0, to be removed in v2.0.0. Please use StateGraph with a `messages` key instead.", + category=None, +) class MessageGraph(StateGraph): """A StateGraph where every node receives a list of messages as input and returns one or more messages as output. + !!! warning "Deprecation" + MessageGraph is deprecated in LangGraph v1.0.0, to be removed in v2.0.0. Please use StateGraph with a `messages` key instead. + MessageGraph is a subclass of StateGraph whose entire state is a single, append-only* list of messages. Each node in a MessageGraph takes a list of messages as input and returns zero or more messages as output. The `add_messages` function is used to merge the output messages from each node @@ -281,6 +289,11 @@ class MessageGraph(StateGraph): """ def __init__(self) -> None: + warnings.warn( + "MessageGraph is deprecated in LangGraph v1.0.0, to be removed in v2.0.0. Please use StateGraph with a `messages` key instead.", + category=LangGraphDeprecatedSinceV10, + stacklevel=2, + ) super().__init__(Annotated[list[AnyMessage], add_messages]) # type: ignore[arg-type] diff --git a/libs/langgraph/tests/test_deprecation.py b/libs/langgraph/tests/test_deprecation.py index a53099dc6..919a7ca27 100644 --- a/libs/langgraph/tests/test_deprecation.py +++ b/libs/langgraph/tests/test_deprecation.py @@ -12,6 +12,7 @@ from langgraph.channels.last_value import LastValue from langgraph.errors import NodeInterrupt from langgraph.func import entrypoint, task from langgraph.graph import StateGraph +from langgraph.graph.message import MessageGraph from langgraph.pregel import NodeBuilder, Pregel from langgraph.types import Interrupt, RetryPolicy from langgraph.warnings import LangGraphDeprecatedSinceV05, LangGraphDeprecatedSinceV10 @@ -332,3 +333,11 @@ def test_config_parameter_incorrect_typing() -> None: builder.add_node(async_node_with_untyped_config) assert len(w) == 0 + + +def test_message_graph_deprecation() -> None: + with pytest.warns( + LangGraphDeprecatedSinceV10, + match="MessageGraph is deprecated in LangGraph v1.0.0, to be removed in v2.0.0. Please use StateGraph with a `messages` key instead.", + ): + MessageGraph() diff --git a/libs/langgraph/tests/test_large_cases.py b/libs/langgraph/tests/test_large_cases.py index 4f7845bba..d626d05ed 100644 --- a/libs/langgraph/tests/test_large_cases.py +++ b/libs/langgraph/tests/test_large_cases.py @@ -6,7 +6,9 @@ from dataclasses import replace from typing import Annotated, Any, Literal, Optional, Union, cast import pytest +from langchain_core.messages import AIMessage, AnyMessage, ToolCall from langchain_core.runnables import RunnableConfig, RunnableMap, RunnablePick +from langchain_core.tools import tool from pytest_mock import MockerFixture from syrupy import SnapshotAssertion from typing_extensions import TypedDict @@ -18,7 +20,7 @@ from langgraph.checkpoint.base import BaseCheckpointSaver from langgraph.checkpoint.memory import InMemorySaver from langgraph.constants import END, START from langgraph.graph import StateGraph -from langgraph.graph.message import MessageGraph, MessagesState, add_messages +from langgraph.graph.message import MessagesState, add_messages from langgraph.prebuilt.chat_agent_executor import create_react_agent from langgraph.prebuilt.tool_node import ToolNode from langgraph.pregel import NodeBuilder, Pregel @@ -2441,7 +2443,7 @@ def test_message_graph( return "continue" # Define a new graph - workflow = MessageGraph() + workflow = StateGraph(state_schema=Annotated[list[AnyMessage], add_messages]) # type: ignore[arg-type] # Define the two nodes we will cycle between workflow.add_node("agent", model) @@ -2487,7 +2489,7 @@ def test_message_graph( assert json.dumps(app.get_graph().to_json(), indent=2) == snapshot assert app.get_graph().draw_mermaid(with_styles=False) == snapshot - assert app.invoke(HumanMessage(content="what is weather in sf")) == [ + assert app.invoke([HumanMessage(content="what is weather in sf")]) == [ _AnyIdHumanMessage( content="what is weather in sf", ), @@ -6435,10 +6437,6 @@ def test_weather_subgraph( from langchain_core.language_models.fake_chat_models import ( FakeMessagesListChatModel, ) - from langchain_core.messages import AIMessage, ToolCall - from langchain_core.tools import tool - - from langgraph.graph import MessagesState # setup subgraph diff --git a/libs/langgraph/tests/test_large_cases_async.py b/libs/langgraph/tests/test_large_cases_async.py index 5e7245a40..022034e66 100644 --- a/libs/langgraph/tests/test_large_cases_async.py +++ b/libs/langgraph/tests/test_large_cases_async.py @@ -11,7 +11,7 @@ from typing import ( ) import pytest -from langchain_core.messages import ToolCall +from langchain_core.messages import AnyMessage, ToolCall from langchain_core.runnables import RunnableConfig, RunnablePick from pytest_mock import MockerFixture from typing_extensions import TypedDict @@ -21,7 +21,7 @@ from langgraph.channels.last_value import LastValue from langgraph.channels.untracked_value import UntrackedValue from langgraph.checkpoint.base import BaseCheckpointSaver from langgraph.constants import END, START -from langgraph.graph.message import MessageGraph, add_messages +from langgraph.graph.message import add_messages from langgraph.graph.state import StateGraph from langgraph.prebuilt.chat_agent_executor import create_react_agent from langgraph.prebuilt.tool_node import ToolNode @@ -2117,7 +2117,7 @@ async def test_message_graph(async_checkpointer: BaseCheckpointSaver) -> None: return "continue" # Define a new graph - workflow = MessageGraph() + workflow = StateGraph(state_schema=Annotated[list[AnyMessage], add_messages]) # type: ignore[arg-type] # Define the two nodes we will cycle between workflow.add_node("agent", model) @@ -2157,7 +2157,7 @@ async def test_message_graph(async_checkpointer: BaseCheckpointSaver) -> None: # meaning you can use it as you would any other runnable app = workflow.compile() - assert await app.ainvoke(HumanMessage(content="what is weather in sf")) == [ + assert await app.ainvoke([HumanMessage(content="what is weather in sf")]) == [ _AnyIdHumanMessage( content="what is weather in sf", ), diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index cd01a3388..e69e4e2f2 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -16,6 +16,7 @@ from typing import Annotated, Any, Literal, Optional, Union, get_type_hints import pytest from langchain_core.language_models import GenericFakeChatModel +from langchain_core.messages import AnyMessage from langchain_core.runnables import ( RunnableConfig, RunnableLambda, @@ -45,7 +46,7 @@ from langgraph.config import get_stream_writer from langgraph.errors import GraphRecursionError, InvalidUpdateError, ParentCommand from langgraph.func import entrypoint, task from langgraph.graph import END, START, StateGraph -from langgraph.graph.message import MessageGraph, MessagesState, add_messages +from langgraph.graph.message import MessagesState, add_messages from langgraph.prebuilt.tool_node import ToolNode from langgraph.pregel import ( NodeBuilder, @@ -3907,7 +3908,7 @@ def test_remove_message_via_state_update( ) -> None: from langchain_core.messages import AIMessage, HumanMessage, RemoveMessage - workflow = MessageGraph() + workflow = StateGraph(state_schema=Annotated[list[AnyMessage], add_messages]) # type: ignore[arg-type] workflow.add_node( "chatbot", lambda state: [ @@ -3940,7 +3941,7 @@ def test_remove_message_via_state_update( def test_remove_message_from_node(): from langchain_core.messages import AIMessage, HumanMessage, RemoveMessage - workflow = MessageGraph() + workflow = StateGraph(state_schema=Annotated[list[AnyMessage], add_messages]) # type: ignore[arg-type] workflow.add_node( "chatbot", lambda state: [ diff --git a/libs/prebuilt/langgraph/prebuilt/tool_node.py b/libs/prebuilt/langgraph/prebuilt/tool_node.py index cc3869ca5..252f88802 100644 --- a/libs/prebuilt/langgraph/prebuilt/tool_node.py +++ b/libs/prebuilt/langgraph/prebuilt/tool_node.py @@ -790,7 +790,6 @@ def tools_condition( Args: state: The current graph state to examine for tool calls. Supported formats: - - List of messages (for MessageGraph) - Dictionary containing a messages key (for StateGraph) - BaseModel instance with a messages attribute messages_key: The key or attribute name containing the message list in the state. diff --git a/libs/prebuilt/langgraph/prebuilt/tool_validator.py b/libs/prebuilt/langgraph/prebuilt/tool_validator.py index d63e0c535..19df4233f 100644 --- a/libs/prebuilt/langgraph/prebuilt/tool_validator.py +++ b/libs/prebuilt/langgraph/prebuilt/tool_validator.py @@ -2,8 +2,7 @@ in a langchain graph. It applies a pydantic schema to tool_calls in the models' outputs, and returns a ToolMessage with the validated content. If the schema is not valid, it returns a ToolMessage with the error message. The ValidationNode can be used in a -StateGraph with a "messages" key or in a MessageGraph. If multiple tool calls are -requested, they will be run in parallel. +StateGraph with a "messages" key. If multiple tool calls are requested, they will be run in parallel. """ from typing import ( @@ -49,7 +48,7 @@ def _default_format_error( class ValidationNode(RunnableCallable): """A node that validates all tools requests from the last AIMessage. - It can be used either in StateGraph with a "messages" key or in MessageGraph. + It can be used either in StateGraph with a "messages" key. !!! note