Remove __start__ node from stream output

This commit is contained in:
Nuno Campos
2024-04-01 16:05:12 -07:00
parent 3e4c94289a
commit 2736d72989
7 changed files with 72 additions and 112 deletions
+12 -14
View File
@@ -186,31 +186,29 @@
"Pineapples on pizza\n",
"## source:\n",
"## branch_1:\n",
"*eyes light up with excitement* Ooh, pineapples on pizza - now we're talking! This is one of my all-time favorite topics to discuss. Let me tell you, pineapple pizza is the ultimate culinary masterpiece, a harmonious fusion of sweet and savory that tantalizes the taste buds in the most delightful way.\n",
"*eyes light up with excitement* Oh my goodness, pineapples on pizza?! That is quite possibly one of the most amazing and delectable culinary pairings known to humanity! \n",
"\n",
"First off, the combination of the juicy, tropical pineapple and the rich, melty cheese is simply divine. The tart sweetness of the pineapple perfectly complements the creamy, salty goodness of the mozzarella. It's a flavor explosion in your mouth!\n",
"Let me tell you, the sweet, juicy pineapple combined with the savory, cheesy goodness of the pizza crust is an absolute flavor explosion in your mouth. The contrasting textures - the soft, tangy pineapple and the crisp, bready pizza - complement each other so perfectly. It's like a symphony of taste!\n",
"\n",
"And let's not forget the textural contrast - the soft, tender pineapple chunks paired with the crisp, chewy pizza crust? Perfection! It's like a party in your mouth. \n",
"And the health benefits are just an added bonus. Pineapples are packed with vitamin C, manganese, and other essential nutrients. So you can indulge in your pizza guilt-free, knowing you're also getting a healthy dose of vitamins and minerals.\n",
"\n",
"Plus, pineapple is loaded with nutrients like vitamin C, manganese, and fiber, so you can feel good about indulging in this delectable treat. It's the ultimate in guilt-free indulgence.\n",
"Pineapple pizza is truly a work of art, an innovative culinary triumph that deserves all the praise and celebration in the world. Anyone who turns their nose up at it is simply not appreciating the sheer genius and deliciousness that is pineapple on pizza. It's a flavor combination that will make your taste buds sing with joy!\n",
"\n",
"Trust me, once you try pineapple pizza, you'll be hooked. It's the perfect balance of flavors and textures that will have you coming back for more, again and again. Pineapple pizza is a true culinary masterpiece, and anyone who says otherwise simply hasn't experienced its greatness. It's a must-try for any true pizza lover!\n",
"So I say, bring on the pineapple pizzas! Load 'em up, pile on those sweet, juicy chunks, and let's dive in and savor every bite. It's a flavor experience that is truly unparalleled. Pineapple on pizza forever!\n",
"## sink:\n",
"This is a tough one, as both arguments make compelling points. However, I would argue that the first argument in favor of pineapple on pizza is the stronger and more persuasive case.\n",
"This is a tough one, as both sides make compelling arguments. However, I find the argument against pineapples on pizza to be the more compelling one overall.\n",
"\n",
"The key strengths of the pro-pineapple argument are:\n",
"The main points against pineapple pizza seem to be:\n",
"\n",
"1. It eloquently describes the delightful flavor and texture pairing of the sweet pineapple and savory cheese/crust. The \"flavor explosion\" and \"party in your mouth\" descriptions are very evocative.\n",
"1. It goes against the traditional, authentic Italian pizza experience and disrespects the sanctity of this classic dish.\n",
"\n",
"2. It highlights the nutritional benefits of pineapple, making it a more guilt-free indulgence.\n",
"2. The flavors and textures of pineapple clash horribly with the savory, crisp elements of a proper pizza. The combination is seen as an abhorrent culinary atrocity.\n",
"\n",
"3. The argument is passionate and enthusiastic, painting pineapple pizza as a \"culinary masterpiece\" that any true pizza lover must experience.\n",
"3. Pineapple is a sweet, tropical fruit that belongs in desserts and drinks, not as a pizza topping.\n",
"\n",
"In contrast, the anti-pineapple argument, while forceful in its language, relies more on subjective distaste rather than objective reasoning. Calling it an \"abomination\" and \"affront to good taste\" is emotive rather than persuasive.\n",
"These arguments hit on strongly-held beliefs about what constitutes \"good taste\" and the importance of respecting traditional culinary norms and practices. The opposing argument, while passionate, relies more on subjective enjoyment of the flavor combination rather than more objective criteria.\n",
"\n",
"The texture and flavor arguments, while valid, don't fully counter the pro-pineapple case. Reasonable people can disagree on whether the combination works.\n",
"\n",
"Overall, the first argument paints a more compelling picture of pineapple pizza's merits, making a stronger case for its inclusion as a legitimate pizza topping. The enthusiasm and vivid descriptions give it the edge over the more indignant and dismissive anti-pineapple stance.\n"
"While I understand the appeal of the sweet-savory contrast, the anti-pineapple argument seems to have a stronger philosophical and cultural foundation. Ultimately, it comes down to whether one values tradition and authenticity over personal taste preferences. For me, the anti-pineapple position is the more compelling one.\n"
]
}
],
+20 -36
View File
@@ -12,7 +12,6 @@ from typing import (
Iterator,
Literal,
Mapping,
NamedTuple,
Optional,
Sequence,
Type,
@@ -36,6 +35,7 @@ from langchain_core.runnables.config import (
get_async_callback_manager_for_config,
get_callback_manager_for_config,
get_executor_for_config,
merge_configs,
patch_config,
)
from langchain_core.runnables.utils import (
@@ -77,6 +77,11 @@ from langgraph.pregel.io import (
)
from langgraph.pregel.log import logger
from langgraph.pregel.read import ChannelInvoke
from langgraph.pregel.types import (
PregelExecutableTask,
PregelTaskDescription,
StateSnapshot,
)
from langgraph.pregel.validate import validate_graph, validate_keys
from langgraph.pregel.write import ChannelWrite, ChannelWriteEntry
@@ -167,17 +172,6 @@ class Channel:
StreamMode = Literal["values", "updates"]
class StateSnapshot(NamedTuple):
values: dict[str, Any] | Any
"""Current values of channels"""
next: tuple[str]
"""Nodes to execute in the next step, if any"""
config: RunnableConfig
"""Config used to fetch this snapshot"""
parent_config: Optional[RunnableConfig] = None
"""Config used to fetch the parent snapshot, if any"""
class Pregel(
RunnableSerializable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]
):
@@ -424,9 +418,9 @@ class Pregel(
if not writers:
raise InvalidUpdateError(f"Node {as_node} has no writers")
task = PregelExecutableTask(
RunnableSequence(*writers) if len(writers) > 1 else writers[0],
values,
as_node,
values,
RunnableSequence(*writers) if len(writers) > 1 else writers[0],
deque(),
)
# execute task
@@ -481,9 +475,9 @@ class Pregel(
if not writers:
raise InvalidUpdateError(f"Node {as_node} has no writers")
task = PregelExecutableTask(
RunnableSequence(*writers) if len(writers) > 1 else writers[0],
values,
as_node,
values,
RunnableSequence(*writers) if len(writers) > 1 else writers[0],
deque(),
)
# execute task
@@ -652,7 +646,7 @@ class Pregel(
proc,
input,
patch_config(
config,
merge_configs(config, proc_config),
run_name=name,
callbacks=run_manager.get_child(f"graph:step:{step}"),
configurable={
@@ -664,7 +658,7 @@ class Pregel(
},
),
)
for proc, input, name, writes in next_tasks
for name, input, proc, writes, proc_config in next_tasks
]
futures = [
@@ -685,7 +679,7 @@ class Pregel(
# combine pending writes from all tasks
pending_writes = deque[tuple[str, Any]]()
for _, _, _, writes in next_tasks:
for _, _, _, writes, _ in next_tasks:
pending_writes.extend(writes)
# apply writes to channels
@@ -857,7 +851,7 @@ class Pregel(
proc,
input,
patch_config(
config,
merge_configs(config, proc_config),
run_name=name,
callbacks=run_manager.get_child(f"graph:step:{step}"),
configurable={
@@ -869,7 +863,7 @@ class Pregel(
},
),
)
for proc, input, name, writes in next_tasks
for name, input, proc, writes, proc_config in next_tasks
]
futures = (
@@ -897,7 +891,7 @@ class Pregel(
# combine pending writes from all tasks
pending_writes = deque[tuple[str, Any]]()
for _, _, _, writes in next_tasks:
for _, _, _, writes, _ in next_tasks:
pending_writes.extend(writes)
# apply writes to channels
@@ -1052,7 +1046,7 @@ def _should_interrupt(
for chan in snapshot_channels
)
# and any channel written to is in interrupt_nodes list
and any(node for _, _, node, _ in tasks if node in interrupt_nodes)
and any(node for node, _, _, _, _ in tasks if node in interrupt_nodes)
)
@@ -1108,18 +1102,6 @@ def _apply_writes(
channels[chan].update([])
class PregelTaskDescription(NamedTuple):
name: str
input: Any
class PregelExecutableTask(NamedTuple):
proc: Runnable
input: Any
name: str
writes: deque[tuple[str, Any]]
@overload
def _prepare_next_tasks(
checkpoint: Checkpoint,
@@ -1200,7 +1182,9 @@ def _prepare_next_tasks(
if for_execution:
if node := proc.get_node():
tasks.append(PregelExecutableTask(node, val, name, deque()))
tasks.append(
PregelExecutableTask(name, val, node, deque(), proc.config)
)
else:
tasks.append(PregelTaskDescription(name, val))
return checkpoint, tasks
+3 -6
View File
@@ -1,23 +1,20 @@
from collections import deque
from pprint import pformat
from typing import Any, Iterator, Mapping
from langchain_core.runnables import Runnable
from langchain_core.utils.input import get_bolded_text, get_colored_text
from langgraph.channels.base import BaseChannel, EmptyChannelError
from langgraph.pregel.types import PregelExecutableTask
def print_step_start(
step: int, next_tasks: list[tuple[Runnable, Any, str, deque[tuple[str, Any]]]]
) -> None:
def print_step_start(step: int, next_tasks: list[PregelExecutableTask]) -> None:
n_tasks = len(next_tasks)
print(
f"{get_colored_text('[langgraph/step]', color='blue')} "
+ get_bolded_text(
f"Starting step {step} with {n_tasks} task{'s' if n_tasks > 1 else ''}. Next tasks:\n"
)
+ "\n".join(f"- {name}({pformat(val)})" for _, val, name, _ in next_tasks)
+ "\n".join(f"- {name}({pformat(val)})" for name, val, _, _, _ in next_tasks)
)
+9 -6
View File
@@ -1,10 +1,8 @@
from collections import deque
from typing import Any, Iterator, Mapping, Optional, Sequence, Union
from langchain_core.runnables import Runnable
from langgraph.channels.base import BaseChannel, EmptyChannelError
from langgraph.pregel.log import logger
from langgraph.pregel.types import PregelExecutableTask
def read_channel(
@@ -79,13 +77,18 @@ def map_output_values(
def map_output_updates(
output_channels: Union[str, Sequence[str]],
next_tasks: list[tuple[Runnable, Any, str, deque[tuple[str, Any]]]],
tasks: list[PregelExecutableTask],
) -> Optional[dict[str, Union[Any, dict[str, Any]]]]:
"""Map pending writes (a sequence of tuples (channel, value)) to output chunk."""
output_tasks = [
t
for t in tasks
if not t.config or "langsmith:hidden" not in t.config.get("tags")
]
if isinstance(output_channels, str):
if updated := {
node: value
for _, _, node, writes in next_tasks
for node, _, _, writes, _ in output_tasks
for chan, value in writes
if chan == output_channels
}:
@@ -93,7 +96,7 @@ def map_output_updates(
else:
if updated := {
node: {chan: value for chan, value in writes if chan in output_channels}
for _, _, node, writes in next_tasks
for node, _, _, writes, _ in output_tasks
if any(chan in output_channels for chan, _ in writes)
}:
return updated
+28
View File
@@ -0,0 +1,28 @@
from collections import deque
from typing import Any, NamedTuple, Optional
from langchain_core.runnables import Runnable, RunnableConfig
class PregelTaskDescription(NamedTuple):
name: str
input: Any
class PregelExecutableTask(NamedTuple):
name: str
input: Any
proc: Runnable
writes: deque[tuple[str, Any]]
config: Optional[RunnableConfig] = None
class StateSnapshot(NamedTuple):
values: dict[str, Any] | Any
"""Current values of channels"""
next: tuple[str]
"""Nodes to execute in the next step, if any"""
config: RunnableConfig
"""Config used to fetch this snapshot"""
parent_config: Optional[RunnableConfig] = None
"""Config used to fetch the parent snapshot, if any"""
-29
View File
@@ -1455,7 +1455,6 @@ def test_conditional_graph_state(snapshot: SnapshotAssertion) -> None:
}
assert [*app.stream({"input": "what is weather in sf"})] == [
{"__start__": {"input": "what is weather in sf"}},
{
"agent": {
"agent_outcome": AgentAction(
@@ -1519,7 +1518,6 @@ def test_conditional_graph_state(snapshot: SnapshotAssertion) -> None:
assert [
c for c in app_w_interrupt.stream({"input": "what is weather in sf"}, config)
] == [
{"__start__": {"input": "what is weather in sf"}},
{
"agent": {
"agent_outcome": AgentAction(
@@ -1637,7 +1635,6 @@ def test_conditional_graph_state(snapshot: SnapshotAssertion) -> None:
assert [
c for c in app_w_interrupt.stream({"input": "what is weather in sf"}, config)
] == [
{"__start__": {"input": "what is weather in sf"}},
{
"agent": {
"agent_outcome": AgentAction(
@@ -1787,7 +1784,6 @@ def test_conditional_entrypoint_graph_state(snapshot: SnapshotAssertion) -> None
}
assert [*app.stream({"input": "what is weather in sf"})] == [
{"__start__": {"input": "what is weather in sf"}},
{"right": {"output": "what is weather in sf->right"}},
]
@@ -1913,7 +1909,6 @@ def test_prebuilt_tool_chat(snapshot: SnapshotAssertion) -> None:
assert [
*app.stream({"messages": [HumanMessage(content="what is weather in sf")]})
] == [
{"__start__": {"messages": [HumanMessage(content="what is weather in sf")]}},
{
"agent": {
"messages": [
@@ -2061,7 +2056,6 @@ def test_prebuilt_chat(snapshot: SnapshotAssertion) -> None:
assert [
*app.stream({"messages": [HumanMessage(content="what is weather in sf")]})
] == [
{"__start__": {"messages": [HumanMessage(content="what is weather in sf")]}},
{
"agent": {
"messages": [
@@ -2287,14 +2281,6 @@ def test_message_graph(
]
assert [*app.stream([HumanMessage(content="what is weather in sf")])] == [
{
"__start__": [
HumanMessage(
content="what is weather in sf",
id="00000000-0000-4000-8000-000000000034",
)
]
},
{
"agent": AIMessage(
content="",
@@ -2341,7 +2327,6 @@ def test_message_graph(
HumanMessage(content="what is weather in sf"), config
)
] == [
{"__start__": HumanMessage(content="what is weather in sf", id=AnyStr())},
{
"agent": AIMessage(
content="",
@@ -2489,12 +2474,6 @@ def test_message_graph(
HumanMessage(content="what is weather in sf"), config
)
] == [
{
"__start__": HumanMessage(
content="what is weather in sf",
id=AnyStr(),
)
},
{
"agent": AIMessage(
content="",
@@ -2681,7 +2660,6 @@ def test_in_one_fan_out_out_one_graph_state() -> None:
}
assert [*app.stream({"query": "what is weather in sf"})] == [
{"__start__": {"query": "what is weather in sf"}},
{"rewrite_query": {"query": "query: what is weather in sf"}},
{
"retriever_two": {"docs": ["doc3", "doc4"]},
@@ -2788,7 +2766,6 @@ def test_in_one_fan_out_state_graph_waiting_edge() -> None:
}
assert [*app.stream({"query": "what is weather in sf"})] == [
{"__start__": {"query": "what is weather in sf"}},
{"rewrite_query": {"query": "query: what is weather in sf"}},
{
"analyzer_one": {"query": "analyzed: query: what is weather in sf"},
@@ -2806,7 +2783,6 @@ def test_in_one_fan_out_state_graph_waiting_edge() -> None:
assert [
c for c in app_w_interrupt.stream({"query": "what is weather in sf"}, config)
] == [
{"__start__": {"query": "what is weather in sf"}},
{"rewrite_query": {"query": "query: what is weather in sf"}},
{
"analyzer_one": {"query": "analyzed: query: what is weather in sf"},
@@ -2912,7 +2888,6 @@ def test_in_one_fan_out_state_graph_waiting_edge_via_branch() -> None:
}
assert [*app.stream({"query": "what is weather in sf"})] == [
{"__start__": {"query": "what is weather in sf"}},
{"rewrite_query": {"query": "query: what is weather in sf"}},
{
"analyzer_one": {"query": "analyzed: query: what is weather in sf"},
@@ -2930,7 +2905,6 @@ def test_in_one_fan_out_state_graph_waiting_edge_via_branch() -> None:
assert [
c for c in app_w_interrupt.stream({"query": "what is weather in sf"}, config)
] == [
{"__start__": {"query": "what is weather in sf"}},
{"rewrite_query": {"query": "query: what is weather in sf"}},
{
"analyzer_one": {"query": "analyzed: query: what is weather in sf"},
@@ -3002,7 +2976,6 @@ def test_in_one_fan_out_state_graph_waiting_edge_plus_regular() -> None:
}
assert [*app.stream({"query": "what is weather in sf"})] == [
{"__start__": {"query": "what is weather in sf"}},
{"rewrite_query": {"query": "query: what is weather in sf"}},
{
"analyzer_one": {"query": "analyzed: query: what is weather in sf"},
@@ -3021,7 +2994,6 @@ def test_in_one_fan_out_state_graph_waiting_edge_plus_regular() -> None:
assert [
c for c in app_w_interrupt.stream({"query": "what is weather in sf"}, config)
] == [
{"__start__": {"query": "what is weather in sf"}},
{"rewrite_query": {"query": "query: what is weather in sf"}},
{
"analyzer_one": {"query": "analyzed: query: what is weather in sf"},
@@ -3101,7 +3073,6 @@ def test_in_one_fan_out_state_graph_waiting_edge_multiple() -> None:
}
assert [*app.stream({"query": "what is weather in sf"})] == [
{"__start__": {"query": "what is weather in sf"}},
{"rewrite_query": {"query": "query: what is weather in sf"}},
{
"analyzer_one": {"query": "analyzed: query: what is weather in sf"},
-21
View File
@@ -1455,7 +1455,6 @@ async def test_conditional_graph_state() -> None:
}
assert [c async for c in app.astream({"input": "what is weather in sf"})] == [
{"__start__": {"input": "what is weather in sf"}},
{
"agent": {
"agent_outcome": AgentAction(
@@ -1522,7 +1521,6 @@ async def test_conditional_graph_state() -> None:
{"input": "what is weather in sf"}, config
)
] == [
{"__start__": {"input": "what is weather in sf"}},
{
"agent": {
"agent_outcome": AgentAction(
@@ -1643,7 +1641,6 @@ async def test_conditional_graph_state() -> None:
{"input": "what is weather in sf"}, config
)
] == [
{"__start__": {"input": "what is weather in sf"}},
{
"agent": {
"agent_outcome": AgentAction(
@@ -1824,7 +1821,6 @@ async def test_conditional_entrypoint_graph_state() -> None:
}
assert [c async for c in app.astream({"input": "what is weather in sf"})] == [
{"__start__": {"input": "what is weather in sf"}},
{"right": {"output": "what is weather in sf->right"}},
]
@@ -1948,7 +1944,6 @@ async def test_prebuilt_tool_chat() -> None:
{"messages": [HumanMessage(content="what is weather in sf")]}
)
] == [
{"__start__": {"messages": [HumanMessage(content="what is weather in sf")]}},
{
"agent": {
"messages": [
@@ -2094,7 +2089,6 @@ async def test_prebuilt_chat() -> None:
{"messages": [HumanMessage(content="what is weather in sf")]}
)
] == [
{"__start__": {"messages": [HumanMessage(content="what is weather in sf")]}},
{
"agent": {
"messages": [
@@ -2280,7 +2274,6 @@ async def test_message_graph() -> None:
assert [
c async for c in app.astream([HumanMessage(content="what is weather in sf")])
] == [
{"__start__": [HumanMessage(content="what is weather in sf", id=AnyStr())]},
{
"agent": AIMessage(
content="",
@@ -2323,12 +2316,6 @@ async def test_message_graph() -> None:
HumanMessage(content="what is weather in sf"), config
)
] == [
{
"__start__": HumanMessage(
content="what is weather in sf",
id=AnyStr(),
)
},
{
"agent": AIMessage(
content="",
@@ -2515,7 +2502,6 @@ async def test_in_one_fan_out_out_one_graph_state() -> None:
}
assert [c async for c in app.astream({"query": "what is weather in sf"})] == [
{"__start__": {"query": "what is weather in sf"}},
{"rewrite_query": {"query": "query: what is weather in sf"}},
{
"retriever_two": {"docs": ["doc3", "doc4"]},
@@ -2579,7 +2565,6 @@ async def test_in_one_fan_out_state_graph_waiting_edge() -> None:
}
assert [c async for c in app.astream({"query": "what is weather in sf"})] == [
{"__start__": {"query": "what is weather in sf"}},
{"rewrite_query": {"query": "query: what is weather in sf"}},
{
"analyzer_one": {"query": "analyzed: query: what is weather in sf"},
@@ -2600,7 +2585,6 @@ async def test_in_one_fan_out_state_graph_waiting_edge() -> None:
{"query": "what is weather in sf"}, config
)
] == [
{"__start__": {"query": "what is weather in sf"}},
{"rewrite_query": {"query": "query: what is weather in sf"}},
{
"analyzer_one": {"query": "analyzed: query: what is weather in sf"},
@@ -2706,7 +2690,6 @@ async def test_in_one_fan_out_state_graph_waiting_edge_via_branch() -> None:
}
assert [c async for c in app.astream({"query": "what is weather in sf"})] == [
{"__start__": {"query": "what is weather in sf"}},
{"rewrite_query": {"query": "query: what is weather in sf"}},
{
"analyzer_one": {"query": "analyzed: query: what is weather in sf"},
@@ -2727,7 +2710,6 @@ async def test_in_one_fan_out_state_graph_waiting_edge_via_branch() -> None:
{"query": "what is weather in sf"}, config
)
] == [
{"__start__": {"query": "what is weather in sf"}},
{"rewrite_query": {"query": "query: what is weather in sf"}},
{
"analyzer_one": {"query": "analyzed: query: what is weather in sf"},
@@ -2799,7 +2781,6 @@ async def test_in_one_fan_out_state_graph_waiting_edge_plus_regular() -> None:
}
assert [c async for c in app.astream({"query": "what is weather in sf"})] == [
{"__start__": {"query": "what is weather in sf"}},
{"rewrite_query": {"query": "query: what is weather in sf"}},
{
"analyzer_one": {"query": "analyzed: query: what is weather in sf"},
@@ -2821,7 +2802,6 @@ async def test_in_one_fan_out_state_graph_waiting_edge_plus_regular() -> None:
{"query": "what is weather in sf"}, config
)
] == [
{"__start__": {"query": "what is weather in sf"}},
{"rewrite_query": {"query": "query: what is weather in sf"}},
{
"analyzer_one": {"query": "analyzed: query: what is weather in sf"},
@@ -2901,7 +2881,6 @@ async def test_in_one_fan_out_state_graph_waiting_edge_multiple() -> None:
}
assert [c async for c in app.astream({"query": "what is weather in sf"})] == [
{"__start__": {"query": "what is weather in sf"}},
{"rewrite_query": {"query": "query: what is weather in sf"}},
{
"analyzer_one": {"query": "analyzed: query: what is weather in sf"},