Merge pull request #262 from langchain-ai/nc/1apr/message-graph-coerce

MessageGraph now accepts same shorthand message formats as langchain-core
This commit is contained in:
Nuno Campos
2024-04-01 18:47:10 -07:00
committed by GitHub
2 changed files with 44 additions and 15 deletions
+8 -4
View File
@@ -1,7 +1,11 @@
import uuid
from typing import Annotated, Union
from langchain_core.messages import AnyMessage, message_chunk_to_message
from langchain_core.messages import (
AnyMessage,
convert_to_messages,
message_chunk_to_message,
)
from langgraph.graph.state import StateGraph
@@ -14,6 +18,9 @@ def add_messages(left: Messages, right: Messages) -> Messages:
left = [left]
if not isinstance(right, list):
right = [right]
# coerce to message
left = [message_chunk_to_message(m) for m in convert_to_messages(left)]
right = [message_chunk_to_message(m) for m in convert_to_messages(right)]
# assign missing ids
for m in left:
if m.id is None:
@@ -21,9 +28,6 @@ def add_messages(left: Messages, right: Messages) -> Messages:
for m in right:
if m.id is None:
m.id = str(uuid.uuid4())
# coerce to message
left = [message_chunk_to_message(m) for m in left]
right = [message_chunk_to_message(m) for m in right]
# merge
left_idx_by_id = {m.id: i for i, m in enumerate(left)}
merged = left.copy()
+36 -11
View File
@@ -2357,10 +2357,7 @@ def test_message_graph(
config = {"configurable": {"thread_id": "1"}}
assert [
c
for c in app_w_interrupt.stream(
HumanMessage(content="what is weather in sf"), config
)
c for c in app_w_interrupt.stream(("human", "what is weather in sf"), config)
] == [
{
"agent": AIMessage(
@@ -2466,7 +2463,7 @@ def test_message_graph(
app_w_interrupt.update_state(
config,
AIMessage(content="answer", id="ai2"),
AIMessage(content="answer", id="ai2"), # replace existing message
)
# replaces message even if object identity is different, as long as id is the same
@@ -2504,12 +2501,7 @@ def test_message_graph(
config = {"configurable": {"thread_id": "2"}}
model.i = 0 # reset the llm
assert [
c
for c in app_w_interrupt.stream(
HumanMessage(content="what is weather in sf"), config
)
] == [
assert [c for c in app_w_interrupt.stream("what is weather in sf", config)] == [
{
"agent": AIMessage(
content="",
@@ -2651,6 +2643,39 @@ def test_message_graph(
config=app_w_interrupt.checkpointer.get_tuple(config).config,
)
# add an extra message as if it came from "action" node
app_w_interrupt.update_state(config, ("ai", "an extra message"), as_node="action")
# extra message is coerced BaseMessge and appended
# now the next node is "agent" per the graph edges
assert app_w_interrupt.get_state(config) == StateSnapshot(
values=[
HumanMessage(
content="what is weather in sf",
id=AnyStr(),
),
AIMessage(
content="",
additional_kwargs={
"function_call": {
"name": "search_api",
"arguments": '"a different query"',
}
},
id="ai1",
),
FunctionMessage(
content="result for a different query",
name="search_api",
id=AnyStr(),
),
AIMessage(content="answer", id="ai2"),
AIMessage(content="an extra message", id=AnyStr()),
],
next=("agent",),
config=app_w_interrupt.checkpointer.get_tuple(config).config,
)
def test_in_one_fan_out_out_one_graph_state() -> None:
def sorted_add(x: list[str], y: list[str]) -> list[str]: