Behaviour of checkpointer for message memory

Hi, given the following example code :

from langchain_core.messages import AIMessage
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import START, MessagesState, StateGraph


def dummy_node(state: MessagesState) -> dict:
    return {"messages": [AIMessage(content="Dummy response")]}


builder = StateGraph(MessagesState)
builder.add_node(dummy_node)
builder.add_edge(START, "dummy_node")
builder.add_edge("dummy_node", END)
graph = builder.compile(checkpointer=InMemorySaver())

config = {
    "configurable": {
        # Checkpoints are accessed by thread_id
        "thread_id": "9",
    }
}

for question in ["Dummy question 1", "Dummy question 2", "Dummy question 3"]:
    input_message = {
        "role": "user",
        "content": question,
    }
    for step in graph.stream(
        {"messages": input_message}, config, stream_mode="values"
    ):
        step["messages"][-1].pretty_print()

state = graph.get_state(config).values
state["messages"]

and the output :

================================ Human Message =================================

Dummy question 1
================================== Ai Message ==================================

Dummy response
================================ Human Message =================================

Dummy question 2
================================== Ai Message ==================================

Dummy response
================================ Human Message =================================

Dummy question 3
================================== Ai Message ==================================

Dummy response

{'role': 'user',
 'content': '[HumanMessage(content=\'[HumanMessage(content="[HumanMessage(content=\\\'Dummy question 1\\\', additional_kwargs={}, response_metadata={}, id=\\\'0be043f4-c304-4257-97a4-da35569e811a\\\'), AIMessage(content=\\\'Dummy response\\\', additional_kwargs={}, response_metadata={}, id=\\\'7329176f-957f-4583-bf5a-99dffd5e6999\\\')]", additional_kwargs={}, response_metadata={}, id=\\\'89a836dc-0f0e-41ab-a136-cdad4e83bf0c\\\'), HumanMessage(content=\\\'Dummy question 2\\\', additional_kwargs={}, response_metadata={}, id=\\\'dac14c20-b9e9-4a12-9c07-8c8a208b976a\\\'), AIMessage(content=\\\'Dummy response\\\', additional_kwargs={}, response_metadata={}, id=\\\'7b628c44-cf44-4424-8e4b-7fd589fca134\\\')]\', additional_kwargs={}, response_metadata={}, id=\'f748f55f-d557-4088-ae51-9c729b5a2507\'), HumanMessage(content=\'Dummy question 3\', additional_kwargs={}, response_metadata={}, id=\'1385d7bd-8c09-4cf5-af55-28f73bbe8148\'), AIMessage(content=\'Dummy response\', additional_kwargs={}, response_metadata={}, id=\'9bd4bba5-ab88-4f05-af3f-f8c11ec67aad\')]'}

Similarly, when we inspect the last step["messages"], previous messages are represented as string and not as a list of message objects:

{'messages': [HumanMessage(content='[HumanMessage(content="[HumanMessage(content=\'Dummy question 1\', additional_kwargs={}, response_metadata={}, id=\'0be043f4-c304-4257-97a4-da35569e811a\'), AIMessage(content=\'Dummy response\', additional_kwargs={}, response_metadata={}, id=\'7329176f-957f-4583-bf5a-99dffd5e6999\')]", additional_kwargs={}, response_metadata={}, id=\'89a836dc-0f0e-41ab-a136-cdad4e83bf0c\'), HumanMessage(content=\'Dummy question 2\', additional_kwargs={}, response_metadata={}, id=\'dac14c20-b9e9-4a12-9c07-8c8a208b976a\'), AIMessage(content=\'Dummy response\', additional_kwargs={}, response_metadata={}, id=\'7b628c44-cf44-4424-8e4b-7fd589fca134\')]', additional_kwargs={}, response_metadata={}, id='f748f55f-d557-4088-ae51-9c729b5a2507'),
  HumanMessage(content='Dummy question 3', additional_kwargs={}, response_metadata={}, id='1385d7bd-8c09-4cf5-af55-28f73bbe8148'),
  AIMessage(content='Dummy response', additional_kwargs={}, response_metadata={}, id='9bd4bba5-ab88-4f05-af3f-f8c11ec67aad')]}

I’m wondering why the messages in the state are represented as a string and not kept as a list of BaseMessage objects?
Is this due to a code mistake in my example, or is this just an internal representation and the messages are being parsed back into a list of Messages before being given to a chat model ?

Hi @baraline , I have tried the same code, got response in list.

from langchain_core.messages import AIMessage
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import START, MessagesState, StateGraph


def dummy_node(state: MessagesState) -> dict:
    return {"messages": [AIMessage(content="Dummy response")]}


builder = StateGraph(MessagesState)
builder.add_node(dummy_node)
builder.add_edge(START, "dummy_node")
builder.add_edge("dummy_node", END)
graph = builder.compile(checkpointer=InMemorySaver())

config = {
    "configurable": {
        # Checkpoints are accessed by thread_id
        "thread_id": "9",
    }
}

for question in ["Dummy question 1", "Dummy question 2", "Dummy question 3"]:
    input_message = {
        "role": "user",
        "content": question,
    }
    for step in graph.stream(
        {"messages": input_message}, config, stream_mode="values"
    ):
        step["messages"][-1].pretty_print()

state = graph.get_state(config).values
state["messages"]

Response

================================ Human Message =================================

Dummy question 1
================================== Ai Message ==================================

Dummy response
================================ Human Message =================================

Dummy question 2
================================== Ai Message ==================================

Dummy response
================================ Human Message =================================

Dummy question 3
================================== Ai Message ==================================

Dummy response

[HumanMessage(content='Dummy question 1', additional_kwargs={}, response_metadata={}, id='7143a7ab-42f4-4d27-9632-00dbe746416f'),
 AIMessage(content='Dummy response', additional_kwargs={}, response_metadata={}, id='6025aede-be33-4b81-a115-33b4ec0f15c0'),
 HumanMessage(content='Dummy question 2', additional_kwargs={}, response_metadata={}, id='b72eeacd-9d9c-4131-9948-776e59e80513'),
 AIMessage(content='Dummy response', additional_kwargs={}, response_metadata={}, id='5b52db00-5a47-4349-91c2-e3b8babd0529'),
 HumanMessage(content='Dummy question 3', additional_kwargs={}, response_metadata={}, id='e5a1ea68-f7eb-458f-aa49-9bbe5f5331a4'),
 AIMessage(content='Dummy response', additional_kwargs={}, response_metadata={}, id='ed11e8ef-59d1-43f9-9a34-23ea61b4e2e6')]

Used below langgraph version

Name: langgraph
Version: 0.6.5
Summary: Building stateful, multi-actor applications with LLMs
Home-page: 
Author: 
Author-email: 
License: 
Location: /localdisk/workspace/langgraph-learning/.venv/lib64/python3.12/site-packages
Requires: langchain-core, langgraph-checkpoint, langgraph-prebuilt, langgraph-sdk, pydantic, xxhash
Required-by: langgraph-supervisor, trustcall

HI @baraline

it is because you are invoking pretty_print on the message object.
Use print(step["messages"][-1]) instead of step["messages"][-1].pretty_print() and you will see the object printed.

Under the hood, it all works fine.

1 Like
from langchain_core.messages import AIMessage
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.constants import END
from langgraph.graph import START, MessagesState, StateGraph


def dummy_node(state: MessagesState) -> dict:
    return {"messages": [AIMessage(content="Dummy response")]}


builder = StateGraph(MessagesState)
builder.add_node(dummy_node)
builder.add_edge(START, "dummy_node")
builder.add_edge("dummy_node", END)
graph = builder.compile(checkpointer=InMemorySaver())

config = {
    "configurable": {
        # Checkpoints are accessed by thread_id
        "thread_id": "9",
    }
}

for question in ["Dummy question 1", "Dummy question 2", "Dummy question 3"]:
    input_message = {
        "role": "user",
        "content": question,
    }
    for step in graph.stream(
        {"messages": input_message}, config, stream_mode="values"
    ):
        print(step["messages"][-1])

state = graph.get_state(config).values

print(f"\nState:\n\n{state}")

produces

ontent='Dummy question 1' additional_kwargs={} response_metadata={} id='a21635f7-08b5-4050-8ae5-df18438e59b8'
content='Dummy response' additional_kwargs={} response_metadata={} id='6da41984-d7c0-4700-a2de-78358e64b6d0'
content='Dummy question 2' additional_kwargs={} response_metadata={} id='8204186e-46d3-4fec-8c54-076d612f7543'
content='Dummy response' additional_kwargs={} response_metadata={} id='76d8e8ac-6d39-4dab-b63e-a2fdcb1b8d99'
content='Dummy question 3' additional_kwargs={} response_metadata={} id='3b15869e-b8be-4d97-870b-4ca18ae78cbf'
content='Dummy response' additional_kwargs={} response_metadata={} id='ab4d9214-9c98-4c73-be65-aa3d43988443'

State:

{'messages': [HumanMessage(content='Dummy question 1', additional_kwargs={}, response_metadata={}, id='a21635f7-08b5-4050-8ae5-df18438e59b8'), AIMessage(content='Dummy response', additional_kwargs={}, response_metadata={}, id='6da41984-d7c0-4700-a2de-78358e64b6d0'), HumanMessage(content='Dummy question 2', additional_kwargs={}, response_metadata={}, id='8204186e-46d3-4fec-8c54-076d612f7543'), AIMessage(content='Dummy response', additional_kwargs={}, response_metadata={}, id='76d8e8ac-6d39-4dab-b63e-a2fdcb1b8d99'), HumanMessage(content='Dummy question 3', additional_kwargs={}, response_metadata={}, id='3b15869e-b8be-4d97-870b-4ca18ae78cbf'), AIMessage(content='Dummy response', additional_kwargs={}, response_metadata={}, id='ab4d9214-9c98-4c73-be65-aa3d43988443')]}
1 Like

All work good :slight_smile:

Hi @pawel-twardziak

Just a question,

I was able to get the state[“messages”] in list format with the same code, Is there any difference in behavior depending on langgraph’s version or Am i missing something?

Hi @heisenberg-7

I am not sure I am following your question.
The list format comes from the method invocation - .pretty_print(). Everything what happens under the hood, those are objects.

Could you rephrase your question please?