hi @tharshan-elvex
here is the results:
having this agent invocation:
import os
from dotenv import load_dotenv
from langchain_core.messages import SystemMessage, HumanMessage
from langgraph.checkpoint.postgres import PostgresSaver, ShallowPostgresSaver
from langgraph.graph import START, StateGraph, MessagesState
from langgraph.prebuilt import tools_condition, ToolNode
load_dotenv()
def add(a: int, b: int) -> int:
"""Adds a and b.
Args:
a: first int
b: second int
"""
return a + b
def add_v2(a: int, b: int, subtract: bool) -> int:
"""Adds a and b.
Args:
a: first int
b: second int
subtract: bool
"""
return a + b if subtract == False else b - a
def multiply(a: int, b: int) -> int:
"""Multiplies a and b.
Args:
a: first int
b: second int
"""
return a * b
def divide(a: int, b: int) -> float:
"""Divide a and b.
Args:
a: first int
b: second int
"""
return a / b
tools = [add, multiply, divide]
# Define LLM with bound tools
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(model_name='claude-3-7-sonnet-latest', timeout=60000, stop=None)
llm_with_tools = llm.bind_tools(tools)
# System message
sys_msg = SystemMessage(content="You are a helpful assistant tasked with writing performing arithmetic on a set of inputs.")
# Node
def assistant(state: MessagesState):
return {"messages": [llm_with_tools.invoke([sys_msg] + state["messages"])]}
# Build graph
builder = StateGraph(MessagesState)
builder.add_node("assistant", assistant)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "assistant")
builder.add_conditional_edges(
"assistant",
# If the latest message (result) from assistant is a tool call -> tools_condition routes to tools
# If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END
tools_condition,
)
builder.add_edge("tools", "assistant")
with ShallowPostgresSaver.from_conn_string(os.getenv("POSTGRES_URI")) as memory:
config = {"configurable": {"thread_id": "unique_id"}}
# memory.setup()
checkpoint = memory.get(config)
# Compile graph
graph = builder.compile(checkpointer=memory)
graph.invoke({"messages":[HumanMessage(content="multiply by 2")]}, config)
graph.invoke({"messages":[HumanMessage(content="6")]}, config)
graph.invoke({"messages":[HumanMessage(content="add 5")]}, config)
graph.invoke({"messages":[HumanMessage(content="divide by 2")]}, config)
snapshot = graph.get_state(config)
for msg in snapshot.values.get("messages"):
msg.pretty_print()
I can see this printout:
================================ Human Message =================================
multiply by 2
================================== Ai Message ==================================
I'd be happy to help you multiply by 2, but I need to know what number you want to multiply. Could you please provide the number that you want to multiply by 2?
================================ Human Message =================================
6
================================== Ai Message ==================================
[{'text': "I'll multiply 6 by 2 for you.", 'type': 'text'}, {'id': 'toolu_01VPaD8JBaTncisctVXBb7Gj', 'input': {'a': 6, 'b': 2}, 'name': 'multiply', 'type': 'tool_use'}]
Tool Calls:
multiply (toolu_01VPaD8JBaTncisctVXBb7Gj)
Call ID: toolu_01VPaD8JBaTncisctVXBb7Gj
Args:
a: 6
b: 2
================================= Tool Message =================================
Name: multiply
12
================================== Ai Message ==================================
The result of multiplying 6 by 2 is 12.
================================ Human Message =================================
add 5
================================== Ai Message ==================================
[{'text': "I'll add 5 to the previous result of 12.", 'type': 'text'}, {'id': 'toolu_012AVvxdMs5ixYUunQSEeN5B', 'input': {'a': 12, 'b': 5}, 'name': 'add', 'type': 'tool_use'}]
Tool Calls:
add (toolu_012AVvxdMs5ixYUunQSEeN5B)
Call ID: toolu_012AVvxdMs5ixYUunQSEeN5B
Args:
a: 12
b: 5
================================= Tool Message =================================
Name: add
17
================================== Ai Message ==================================
The result of adding 5 to 12 is 17.
================================ Human Message =================================
divide by 2
================================== Ai Message ==================================
[{'text': "I'll divide the previous result of 17 by 2.", 'type': 'text'}, {'id': 'toolu_017xDHJViuLCXLHvRp3pqCCb', 'input': {'a': 17, 'b': 2}, 'name': 'divide', 'type': 'tool_use'}]
Tool Calls:
divide (toolu_017xDHJViuLCXLHvRp3pqCCb)
Call ID: toolu_017xDHJViuLCXLHvRp3pqCCb
Args:
a: 17
b: 2
================================= Tool Message =================================
Name: divide
8.5
================================== Ai Message ==================================
The result of dividing 17 by 2 is 8.5.
Now I am running this migration script (tool add → tool add_v2):
"""
Full checkpoint migration script: Rewrite tool calls from 'add' to 'add_v2'
"""
import os
from dotenv import load_dotenv
from langchain_core.messages import AIMessage, ToolMessage
from langgraph.checkpoint.postgres import ShallowPostgresSaver
from langgraph.graph import StateGraph, MessagesState, START
from langgraph.prebuilt import tools_condition, ToolNode
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import SystemMessage
load_dotenv()
# Import BOTH old and new tools
def add(a: int, b: int) -> int:
"""Adds a and b."""
return a + b
def add_v2(a: int, b: int, subtract: bool = False) -> int:
"""Adds a and b, or subtracts if subtract=True."""
return a + b if not subtract else b - a
def multiply(a: int, b: int) -> int:
"""Multiplies a and b."""
return a * b
def divide(a: int, b: int) -> float:
"""Divide a and b."""
return a / b
# Build graph with NEW tools
tools = [add_v2, multiply, divide]
llm = ChatAnthropic(model_name='claude-3-7-sonnet-latest', timeout=60000, stop=None)
llm_with_tools = llm.bind_tools(tools)
sys_msg = SystemMessage(content="You are a helpful assistant tasked with performing arithmetic.")
def assistant(state: MessagesState):
return {"messages": [llm_with_tools.invoke([sys_msg] + state["messages"])]}
builder = StateGraph(MessagesState)
builder.add_node("assistant", assistant)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "assistant")
builder.add_conditional_edges("assistant", tools_condition)
builder.add_edge("tools", "assistant")
with ShallowPostgresSaver.from_conn_string(os.getenv("POSTGRES_URI")) as memory:
memory.setup()
config = {"configurable": {"thread_id": "unique_id"}}
graph = builder.compile(checkpointer=memory)
# Step 1: Get current state
state = graph.get_state(config)
print("=== BEFORE MIGRATION ===")
print(f"Total messages: {len(state.values['messages'])}\n")
# Step 2: Transform messages - rewrite tool calls from 'add' to 'add_v2'
updated_messages = []
for msg in state.values["messages"]:
if isinstance(msg, AIMessage) and hasattr(msg, 'tool_calls') and msg.tool_calls:
# Check if this message contains 'add' tool calls
modified = False
new_tool_calls = []
new_content = msg.content
for tool_call in msg.tool_calls:
if tool_call['name'] == 'add':
# Rewrite to add_v2 with new signature
new_tool_call = tool_call.copy()
new_tool_call['name'] = 'add_v2'
new_tool_call['args']['subtract'] = False # Add new parameter
new_tool_calls.append(new_tool_call)
modified = True
print(f"✓ Migrating AIMessage tool_call: add → add_v2 (id: {msg.id})")
else:
new_tool_calls.append(tool_call)
if modified:
# Create new AIMessage with same ID to overwrite
# Update content if it mentions the old tool
if isinstance(new_content, str):
new_content = new_content.replace("add tool", "add_v2 tool")
elif isinstance(new_content, list):
new_content = [
{**item, 'name': 'add_v2'} if isinstance(item, dict) and item.get('name') == 'add' else item
for item in new_content
]
new_msg = AIMessage(
content=new_content,
id=msg.id, # ← CRITICAL: Same ID = overwrite existing message
tool_calls=new_tool_calls,
additional_kwargs=msg.additional_kwargs,
response_metadata=msg.response_metadata,
usage_metadata=getattr(msg, 'usage_metadata', None)
)
updated_messages.append(new_msg)
else:
updated_messages.append(msg)
elif isinstance(msg, ToolMessage) and msg.name == 'add':
# Rewrite ToolMessage to reference add_v2
print(f"✓ Migrating ToolMessage: add → add_v2 (id: {msg.id})")
new_msg = ToolMessage(
content=msg.content,
id=msg.id, # ← CRITICAL: Same ID = overwrite existing message
tool_call_id=msg.tool_call_id,
name='add_v2', # Updated tool name
additional_kwargs=msg.additional_kwargs,
)
updated_messages.append(new_msg)
else:
updated_messages.append(msg)
# Step 3: Update state with transformed messages
# Key insight from docs: add_messages reducer overwrites messages with same ID
print("\n=== APPLYING MIGRATION ===")
new_config = graph.update_state(config, {"messages": updated_messages})
print(f"New checkpoint created: {new_config['configurable']['checkpoint_id']}")
# Step 4: Verify migration
print("\n=== AFTER MIGRATION ===")
new_state = graph.get_state(config)
print(f"Total messages: {len(new_state.values['messages'])}")
# Check for any remaining 'add' references
add_count = 0
add_v2_count = 0
for msg in new_state.values["messages"]:
if isinstance(msg, AIMessage) and hasattr(msg, 'tool_calls') and msg.tool_calls:
for tc in msg.tool_calls:
if tc['name'] == 'add':
add_count += 1
elif tc['name'] == 'add_v2':
add_v2_count += 1
elif isinstance(msg, ToolMessage):
if msg.name == 'add':
add_count += 1
elif msg.name == 'add_v2':
add_v2_count += 1
print(f"\nTool call counts:")
print(f" 'add' references: {add_count}")
print(f" 'add_v2' references: {add_v2_count}")
if add_count == 0:
print("\n✓ Migration successful! All 'add' tool calls converted to 'add_v2'")
else:
print(f"\n⚠ Warning: {add_count} 'add' references still remain")
print("\n=== SAMPLE MESSAGES ===")
for i, msg in enumerate(new_state.values["messages"]):
if isinstance(msg, (AIMessage, ToolMessage)):
print(f"\nMessage {i}:")
msg.pretty_print()
and getting this printout:
=== BEFORE MIGRATION ===
Total messages: 14
✓ Migrating AIMessage tool_call: add → add_v2 (id: run--1038a9e8-72e4-4b1c-ac17-2806a3b09382-0)
✓ Migrating ToolMessage: add → add_v2 (id: e192120a-6fb1-40fa-add5-1baa52116eba)
=== APPLYING MIGRATION ===
New checkpoint created: 1f0a2f55-5ec6-610c-8011-b43cd9795e4a
=== AFTER MIGRATION ===
Total messages: 14
Tool call counts:
'add' references: 0
'add_v2' references: 2
✓ Migration successful! All 'add' tool calls converted to 'add_v2'
=== SAMPLE MESSAGES ===
Message 1:
================================== Ai Message ==================================
I'd be happy to help you multiply by 2, but I need to know what number you want to multiply. Could you please provide the number that you want to multiply by 2?
Message 3:
================================== Ai Message ==================================
[{'text': "I'll multiply 6 by 2 for you.", 'type': 'text'}, {'id': 'toolu_01VPaD8JBaTncisctVXBb7Gj', 'input': {'a': 6, 'b': 2}, 'name': 'multiply', 'type': 'tool_use'}]
Tool Calls:
multiply (toolu_01VPaD8JBaTncisctVXBb7Gj)
Call ID: toolu_01VPaD8JBaTncisctVXBb7Gj
Args:
a: 6
b: 2
Message 4:
================================= Tool Message =================================
Name: multiply
12
Message 5:
================================== Ai Message ==================================
The result of multiplying 6 by 2 is 12.
Message 7:
================================== Ai Message ==================================
[{'text': "I'll add 5 to the previous result of 12.", 'type': 'text'}, {'id': 'toolu_012AVvxdMs5ixYUunQSEeN5B', 'input': {'a': 12, 'b': 5}, 'name': 'add_v2', 'type': 'tool_use'}]
Tool Calls:
add_v2 (toolu_012AVvxdMs5ixYUunQSEeN5B)
Call ID: toolu_012AVvxdMs5ixYUunQSEeN5B
Args:
a: 12
b: 5
subtract: False
Message 8:
================================= Tool Message =================================
Name: add_v2
17
Message 9:
================================== Ai Message ==================================
The result of adding 5 to 12 is 17.
Message 11:
================================== Ai Message ==================================
[{'text': "I'll divide the previous result of 17 by 2.", 'type': 'text'}, {'id': 'toolu_017xDHJViuLCXLHvRp3pqCCb', 'input': {'a': 17, 'b': 2}, 'name': 'divide', 'type': 'tool_use'}]
Tool Calls:
divide (toolu_017xDHJViuLCXLHvRp3pqCCb)
Call ID: toolu_017xDHJViuLCXLHvRp3pqCCb
Args:
a: 17
b: 2
Message 12:
================================= Tool Message =================================
Name: divide
8.5
Message 13:
================================== Ai Message ==================================
The result of dividing 17 by 2 is 8.5.
So now, running this:
import os
from dotenv import load_dotenv
from langchain_core.messages import SystemMessage, HumanMessage
from langgraph.checkpoint.postgres import PostgresSaver, ShallowPostgresSaver
from langgraph.graph import START, StateGraph, MessagesState
from langgraph.prebuilt import tools_condition, ToolNode
load_dotenv()
def add(a: int, b: int) -> int:
"""Adds a and b.
Args:
a: first int
b: second int
"""
return a + b
def add_v2(a: int, b: int, subtract: bool) -> int:
"""Adds a and b.
Args:
a: first int
b: second int
subtract: bool
"""
return a + b if subtract == False else b - a
def multiply(a: int, b: int) -> int:
"""Multiplies a and b.
Args:
a: first int
b: second int
"""
return a * b
def divide(a: int, b: int) -> float:
"""Divide a and b.
Args:
a: first int
b: second int
"""
return a / b
tools = [add_v2, multiply, divide]
# Define LLM with bound tools
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(model_name='claude-3-7-sonnet-latest', timeout=60000, stop=None)
llm_with_tools = llm.bind_tools(tools)
# System message
sys_msg = SystemMessage(content="You are a helpful assistant tasked with writing performing arithmetic on a set of inputs.")
# Node
def assistant(state: MessagesState):
return {"messages": [llm_with_tools.invoke([sys_msg] + state["messages"])]}
# Build graph
builder = StateGraph(MessagesState)
builder.add_node("assistant", assistant)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "assistant")
builder.add_conditional_edges(
"assistant",
# If the latest message (result) from assistant is a tool call -> tools_condition routes to tools
# If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END
tools_condition,
)
builder.add_edge("tools", "assistant")
with ShallowPostgresSaver.from_conn_string(os.getenv("POSTGRES_URI")) as memory:
config = {"configurable": {"thread_id": "unique_id"}}
checkpoint = memory.get(config)
# Compile graph
graph = builder.compile(checkpointer=memory)
answer = graph.invoke(None, config)
for msg in answer["messages"]:
msg.pretty_print()
I can see this:
================================ Human Message =================================
multiply by 2
================================== Ai Message ==================================
I'd be happy to help you multiply by 2, but I need to know what number you want to multiply. Could you please provide the number that you want to multiply by 2?
================================ Human Message =================================
6
================================== Ai Message ==================================
[{'text': "I'll multiply 6 by 2 for you.", 'type': 'text'}, {'id': 'toolu_01VPaD8JBaTncisctVXBb7Gj', 'input': {'a': 6, 'b': 2}, 'name': 'multiply', 'type': 'tool_use'}]
Tool Calls:
multiply (toolu_01VPaD8JBaTncisctVXBb7Gj)
Call ID: toolu_01VPaD8JBaTncisctVXBb7Gj
Args:
a: 6
b: 2
================================= Tool Message =================================
Name: multiply
12
================================== Ai Message ==================================
The result of multiplying 6 by 2 is 12.
================================ Human Message =================================
add 5
================================== Ai Message ==================================
[{'text': "I'll add 5 to the previous result of 12.", 'type': 'text'}, {'id': 'toolu_012AVvxdMs5ixYUunQSEeN5B', 'input': {'a': 12, 'b': 5}, 'name': 'add_v2', 'type': 'tool_use'}]
Tool Calls:
add_v2 (toolu_012AVvxdMs5ixYUunQSEeN5B)
Call ID: toolu_012AVvxdMs5ixYUunQSEeN5B
Args:
a: 12
b: 5
subtract: False
================================= Tool Message =================================
Name: add_v2
17
================================== Ai Message ==================================
The result of adding 5 to 12 is 17.
================================ Human Message =================================
divide by 2
================================== Ai Message ==================================
[{'text': "I'll divide the previous result of 17 by 2.", 'type': 'text'}, {'id': 'toolu_017xDHJViuLCXLHvRp3pqCCb', 'input': {'a': 17, 'b': 2}, 'name': 'divide', 'type': 'tool_use'}]
Tool Calls:
divide (toolu_017xDHJViuLCXLHvRp3pqCCb)
Call ID: toolu_017xDHJViuLCXLHvRp3pqCCb
Args:
a: 17
b: 2
================================= Tool Message =================================
Name: divide
8.5
================================== Ai Message ==================================
The result of dividing 17 by 2 is 8.5.