如何在子图中查看和更新状态¶
一旦您添加了持久性,您就可以在任何时间点查看和更新子图的状态。这使得能够实现人工参与的交互模式,例如
- 您可以在中断期间向用户显示状态,让他们接受操作。
- 您可以回溯子图以重现或避免问题。
- 您可以修改状态,以便用户更好地控制其操作。
本指南展示了如何做到这一点。
设置¶
首先,我们需要安装所需的软件包
接下来,我们需要为 OpenAI 设置 API 密钥(我们将在本指南中使用此提供商)
设置 LangSmith 以进行 LangGraph 开发
注册 LangSmith 以快速发现问题并提高 LangGraph 项目的性能。LangSmith 允许您使用跟踪数据来调试、测试和监控使用 LangGraph 构建的 LLM 应用程序 — 阅读此处了解更多关于如何开始的信息。
定义子图¶
首先,让我们设置我们的子图。为此,我们将创建一个简单的图,它可以获取特定城市的天气。我们将在 weather_node
之前使用断点编译此图
import { z } from "zod";
import { tool } from "@langchain/core/tools";
import { ChatOpenAI } from "@langchain/openai";
import { StateGraph, MessagesAnnotation, Annotation } from "@langchain/langgraph";
const getWeather = tool(async ({ city }) => {
return `It's sunny in ${city}`;
}, {
name: "get_weather",
description: "Get the weather for a specific city",
schema: z.object({
city: z.string().describe("A city name")
})
});
const rawModel = new ChatOpenAI({ model: "gpt-4o-mini" });
const model = rawModel.withStructuredOutput(getWeather);
// Extend the base MessagesAnnotation state with another field
const SubGraphAnnotation = Annotation.Root({
...MessagesAnnotation.spec,
city: Annotation<string>,
});
const modelNode = async (state: typeof SubGraphAnnotation.State) => {
const result = await model.invoke(state.messages);
return { city: result.city };
};
const weatherNode = async (state: typeof SubGraphAnnotation.State) => {
const result = await getWeather.invoke({ city: state.city });
return {
messages: [
{
role: "assistant",
content: result,
}
]
};
};
const subgraph = new StateGraph(SubGraphAnnotation)
.addNode("modelNode", modelNode)
.addNode("weatherNode", weatherNode)
.addEdge("__start__", "modelNode")
.addEdge("modelNode", "weatherNode")
.addEdge("weatherNode", "__end__")
.compile({ interruptBefore: ["weatherNode"] });
定义父图¶
我们现在可以设置总图。如果需要获取天气,此图将首先路由到子图,否则将路由到正常的 LLM。
import { MemorySaver } from "@langchain/langgraph";
const memory = new MemorySaver();
const RouterStateAnnotation = Annotation.Root({
...MessagesAnnotation.spec,
route: Annotation<"weather" | "other">,
});
const routerModel = rawModel.withStructuredOutput(
z.object({
route: z.enum(["weather", "other"]).describe("A step that should execute next to based on the currnet input")
}),
{
name: "router"
}
);
const routerNode = async (state: typeof RouterStateAnnotation.State) => {
const systemMessage = {
role: "system",
content: "Classify the incoming query as either about weather or not.",
};
const messages = [systemMessage, ...state.messages]
const { route } = await routerModel.invoke(messages);
return { route };
}
const normalLLMNode = async (state: typeof RouterStateAnnotation.State) => {
const responseMessage = await rawModel.invoke(state.messages);
return { messages: [responseMessage] };
};
const routeAfterPrediction = async (state: typeof RouterStateAnnotation.State) => {
if (state.route === "weather") {
return "weatherGraph";
} else {
return "normalLLMNode";
}
};
const graph = new StateGraph(RouterStateAnnotation)
.addNode("routerNode", routerNode)
.addNode("normalLLMNode", normalLLMNode)
.addNode("weatherGraph", subgraph)
.addEdge("__start__", "routerNode")
.addConditionalEdges("routerNode", routeAfterPrediction)
.addEdge("normalLLMNode", "__end__")
.addEdge("weatherGraph", "__end__")
.compile({ checkpointer: memory });
这是我们刚刚创建的图的图表
让我们用一个正常的查询来测试一下,以确保它按预期工作!
const config = { configurable: { thread_id: "1" } };
const inputs = { messages: [{ role: "user", content: "hi!" }] };
const stream = await graph.stream(inputs, { ...config, streamMode: "updates" });
for await (const update of stream) {
console.log(update);
}
{ routerNode: { route: 'other' } }
{
normalLLMNode: {
messages: [
AIMessage {
"id": "chatcmpl-ABtbbiB5N3Uue85UNrFUjw5KhGaud",
"content": "Hello! How can I assist you today?",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"completionTokens": 9,
"promptTokens": 9,
"totalTokens": 18
},
"finish_reason": "stop",
"system_fingerprint": "fp_f85bea6784"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 9,
"output_tokens": 9,
"total_tokens": 18
}
}
]
}
}
从断点恢复¶
现在让我们看看断点会发生什么。让我们用一个应该路由到天气子图的查询来调用它,我们在天气子图中设置了中断节点。
const config2 = { configurable: { thread_id: "2" } };
const streamWithBreakpoint = await graph.stream({
messages: [{
role: "user",
content: "what's the weather in sf"
}]
}, { ...config2, streamMode: "updates" });
for await (const update of streamWithBreakpoint) {
console.log(update);
}
subgraphs: True
,并像这样返回子图事件
const streamWithSubgraphs = await graph.stream({
messages: [{
role: "user",
content: "what's the weather in sf"
}]
}, { configurable: { thread_id: "3" }, streamMode: "updates", subgraphs: true });
for await (const update of streamWithSubgraphs) {
console.log(update);
}
[ [], { routerNode: { route: 'weather' } } ]
[
[ 'weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0' ],
{ modelNode: { city: 'San Francisco' } }
]
weatherGraph
上暂停了
如果我们查看当前状态的待处理任务,我们可以看到我们有一个名为 weatherGraph
的任务,它对应于子图任务。
[
{
"id": "ec67e50f-d29c-5dee-8a80-08723a937de0",
"name": "weatherGraph",
"path": [
"__pregel_pull",
"weatherGraph"
],
"interrupts": [],
"state": {
"configurable": {
"thread_id": "3",
"checkpoint_ns": "weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0"
}
}
}
]
state
值,您会注意到它只是父图的配置。如果我们想实际填充子图状态,我们可以将 subgraphs: True
传递给 getState
的第二个参数,如下所示
const stateWithSubgraphs = await graph.getState({ configurable: { thread_id: "3" } }, { subgraphs: true })
JSON.stringify(stateWithSubgraphs.tasks, null, 2)
[
{
"id": "ec67e50f-d29c-5dee-8a80-08723a937de0",
"name": "weatherGraph",
"path": [
"__pregel_pull",
"weatherGraph"
],
"interrupts": [],
"state": {
"values": {
"messages": [
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"messages",
"HumanMessage"
],
"kwargs": {
"content": "what's the weather in sf",
"additional_kwargs": {},
"response_metadata": {},
"id": "094b6752-6bea-4b43-b837-c6b0bb6a4c44"
}
}
],
"city": "San Francisco"
},
"next": [
"weatherNode"
],
"tasks": [
{
"id": "2f2f8b8f-6a99-5225-8ff2-b6c49c3e9caf",
"name": "weatherNode",
"path": [
"__pregel_pull",
"weatherNode"
],
"interrupts": []
}
],
"metadata": {
"source": "loop",
"writes": {
"modelNode": {
"city": "San Francisco"
}
},
"step": 1,
"parents": {
"": "1ef7c6ba-3d36-65e0-8001-adc1f8841274"
}
},
"config": {
"configurable": {
"thread_id": "3",
"checkpoint_id": "1ef7c6ba-4503-6700-8001-61e828d1c772",
"checkpoint_ns": "weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0",
"checkpoint_map": {
"": "1ef7c6ba-3d36-65e0-8001-adc1f8841274",
"weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0": "1ef7c6ba-4503-6700-8001-61e828d1c772"
}
}
},
"createdAt": "2024-09-27T00:58:43.184Z",
"parentConfig": {
"configurable": {
"thread_id": "3",
"checkpoint_ns": "weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0",
"checkpoint_id": "1ef7c6ba-3d3b-6400-8000-fe27ae37c785"
}
}
}
}
]
要恢复执行,我们可以像往常一样调用外部图
const resumedStream = await graph.stream(null, {
configurable: { thread_id: "3" },
streamMode: "values",
subgraphs: true,
});
for await (const update of resumedStream) {
console.log(update);
}
[
[],
{
messages: [
HumanMessage {
"id": "094b6752-6bea-4b43-b837-c6b0bb6a4c44",
"content": "what's the weather in sf",
"additional_kwargs": {},
"response_metadata": {}
}
],
route: 'weather'
}
]
[
[ 'weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0' ],
{
messages: [
HumanMessage {
"id": "094b6752-6bea-4b43-b837-c6b0bb6a4c44",
"content": "what's the weather in sf",
"additional_kwargs": {},
"response_metadata": {}
}
],
city: 'San Francisco'
}
]
[
[ 'weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0' ],
{
messages: [
HumanMessage {
"id": "094b6752-6bea-4b43-b837-c6b0bb6a4c44",
"content": "what's the weather in sf",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"id": "55d7a03f-876a-4887-9418-027321e747c7",
"content": "It's sunny in San Francisco",
"additional_kwargs": {},
"response_metadata": {},
"tool_calls": [],
"invalid_tool_calls": []
}
],
city: 'San Francisco'
}
]
[
[],
{
messages: [
HumanMessage {
"id": "094b6752-6bea-4b43-b837-c6b0bb6a4c44",
"content": "what's the weather in sf",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"id": "55d7a03f-876a-4887-9418-027321e747c7",
"content": "It's sunny in San Francisco",
"additional_kwargs": {},
"response_metadata": {},
"tool_calls": [],
"invalid_tool_calls": []
}
],
route: 'weather'
}
]
从特定子图节点恢复¶
在上面的示例中,我们是从外部图重播的 - 这会自动从子图之前的任何状态(在我们的例子中是在 weatherNode
之前暂停)重播子图,但也可以从子图内部重播。为了做到这一点,我们需要从我们想要重播的确切子图状态获取配置。
我们可以通过浏览子图的状态历史记录,并选择 modelNode
之前的状态来做到这一点 - 我们可以通过过滤 .next
参数来实现。
要获取子图的状态历史记录,我们需要首先传入子图之前的父图状态
let parentGraphStateBeforeSubgraph;
const histories = await graph.getStateHistory({ configurable: { thread_id: "3" } });
for await (const historyEntry of histories) {
if (historyEntry.next[0] === "weatherGraph") {
parentGraphStateBeforeSubgraph = historyEntry;
}
}
let subgraphStateBeforeModelNode;
const subgraphHistories = await graph.getStateHistory(parentGraphStateBeforeSubgraph.tasks[0].state);
for await (const subgraphHistoryEntry of subgraphHistories) {
if (subgraphHistoryEntry.next[0] === "modelNode") {
subgraphStateBeforeModelNode = subgraphHistoryEntry;
}
}
console.log(subgraphStateBeforeModelNode);
{
values: {
messages: [
HumanMessage {
"id": "094b6752-6bea-4b43-b837-c6b0bb6a4c44",
"content": "what's the weather in sf",
"additional_kwargs": {},
"response_metadata": {}
}
]
},
next: [ 'modelNode' ],
tasks: [
{
id: '6d0d44fd-279b-56b0-8160-8f4929f9bfe6',
name: 'modelNode',
path: [Array],
interrupts: [],
state: undefined
}
],
metadata: {
source: 'loop',
writes: null,
step: 0,
parents: { '': '1ef7c6ba-3d36-65e0-8001-adc1f8841274' }
},
config: {
configurable: {
thread_id: '3',
checkpoint_ns: 'weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0',
checkpoint_id: '1ef7c6ba-3d3b-6400-8000-fe27ae37c785',
checkpoint_map: [Object]
}
},
createdAt: '2024-09-27T00:58:42.368Z',
parentConfig: {
configurable: {
thread_id: '3',
checkpoint_ns: 'weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0',
checkpoint_id: '1ef7c6ba-3d38-6cf1-ffff-b3912beb00b9'
}
}
}
我们可以通过比较 subgraphStateBeforeModelNode
的 .next
参数来确认我们已获得正确的状态。
modelNode
恢复
const resumeSubgraphStream = await graph.stream(null, {
...subgraphStateBeforeModelNode.config,
streamMode: "updates",
subgraphs: true
});
for await (const value of resumeSubgraphStream) {
console.log(value);
}
[
[ 'weatherGraph:ec67e50f-d29c-5dee-8a80-08723a937de0' ],
{ modelNode: { city: 'San Francisco' } }
]
modelNode
,并在 weatherNode
之前按预期中断。
本小节展示了如何从任何节点重播,无论它在图中嵌套多深 - 这是测试代理确定性的强大工具。
修改状态¶
更新子图的状态¶
如果我们想修改子图的状态怎么办?我们可以像我们更新正常图的状态一样做到这一点。我们只需要确保将子图的配置传递给 updateState
。让我们像以前一样运行我们的图
const graphStream = await graph.stream({
messages: [{
role: "user",
content: "what's the weather in sf"
}],
}, {
configurable: {
thread_id: "4",
}
});
for await (const update of graphStream) {
console.log(update);
}
const outerGraphState = await graph.getState({
configurable: {
thread_id: "4",
}
}, { subgraphs: true })
console.log(outerGraphState.tasks[0].state);
{
values: {
messages: [
HumanMessage {
"id": "07ed1a38-13a9-4ec2-bc88-c4f6b713ec85",
"content": "what's the weather in sf",
"additional_kwargs": {},
"response_metadata": {}
}
],
city: 'San Francisco'
},
next: [ 'weatherNode' ],
tasks: [
{
id: 'eabfbb82-6cf4-5ecd-932e-ed994ea44f23',
name: 'weatherNode',
path: [Array],
interrupts: [],
state: undefined
}
],
metadata: {
source: 'loop',
writes: { modelNode: [Object] },
step: 1,
parents: { '': '1ef7c6ba-563f-60f0-8001-4fce0e78ef56' }
},
config: {
configurable: {
thread_id: '4',
checkpoint_id: '1ef7c6ba-5c71-6f90-8001-04f60f3c8173',
checkpoint_ns: 'weatherGraph:8d8c9278-bd2a-566a-b9e1-e72286634681',
checkpoint_map: [Object]
}
},
createdAt: '2024-09-27T00:58:45.641Z',
parentConfig: {
configurable: {
thread_id: '4',
checkpoint_ns: 'weatherGraph:8d8c9278-bd2a-566a-b9e1-e72286634681',
checkpoint_id: '1ef7c6ba-5641-6800-8000-96bcde048fa6'
}
}
}
state.tasks[0].state.config
来获取它 - 由于我们在子图内部中断,因此任务的状态只是子图的状态。
import type { StateSnapshot } from "@langchain/langgraph";
await graph.updateState((outerGraphState.tasks[0].state as StateSnapshot).config, { city: "la" });
{
configurable: {
thread_id: '4',
checkpoint_ns: 'weatherGraph:8d8c9278-bd2a-566a-b9e1-e72286634681',
checkpoint_id: '1ef7c6ba-5de0-62f0-8002-3618a75d1fce',
checkpoint_map: {
'': '1ef7c6ba-563f-60f0-8001-4fce0e78ef56',
'weatherGraph:8d8c9278-bd2a-566a-b9e1-e72286634681': '1ef7c6ba-5de0-62f0-8002-3618a75d1fce'
}
}
}
const resumedStreamWithUpdatedState = await graph.stream(null, {
configurable: {
thread_id: "4",
},
streamMode: "updates",
subgraphs: true,
})
for await (const update of resumedStreamWithUpdatedState) {
console.log(JSON.stringify(update, null, 2));
}
[
[
"weatherGraph:8d8c9278-bd2a-566a-b9e1-e72286634681"
],
{
"weatherNode": {
"messages": [
{
"role": "assistant",
"content": "It's sunny in la"
}
]
}
}
]
[
[],
{
"weatherGraph": {
"messages": [
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"messages",
"HumanMessage"
],
"kwargs": {
"content": "what's the weather in sf",
"additional_kwargs": {},
"response_metadata": {},
"id": "07ed1a38-13a9-4ec2-bc88-c4f6b713ec85"
}
},
{
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"messages",
"AIMessage"
],
"kwargs": {
"content": "It's sunny in la",
"tool_calls": [],
"invalid_tool_calls": [],
"additional_kwargs": {},
"response_metadata": {},
"id": "94c29f6c-38b3-420f-a9fb-bd85548f0c03"
}
}
]
}
}
]
充当子图节点¶
除了在 weatherGraph
子图中编辑 weatherNode
之前的状态之外,我们可以更新状态的另一种方法是充当 weatherNode
本身。我们可以通过传递子图配置以及作为第三个位置参数传递的节点名称来做到这一点,这允许我们更新状态,就好像我们是指定的节点一样。
我们将在 weatherNode
之前设置中断,然后使用更新状态函数作为 weatherNode
,图本身永远不会直接调用 weatherNode
,而是由我们决定 weatherNode
的输出应该是什么。
const streamWithAsNode = await graph.stream({
messages: [{
role: "user",
content: "What's the weather in sf",
}]
}, {
configurable: {
thread_id: "14",
}
});
for await (const update of streamWithAsNode) {
console.log(update);
}
// Graph execution should stop before the weather node
console.log("interrupted!");
const interruptedState = await graph.getState({
configurable: {
thread_id: "14",
}
}, { subgraphs: true });
console.log(interruptedState);
// We update the state by passing in the message we want returned from the weather node
// and make sure to pass `"weatherNode"` to signify that we want to act as this node.
await graph.updateState((interruptedState.tasks[0].state as StateSnapshot).config, {
messages: [{
"role": "assistant",
"content": "rainy"
}]
}, "weatherNode");
const resumedStreamWithAsNode = await graph.stream(null, {
configurable: {
thread_id: "14",
},
streamMode: "updates",
subgraphs: true,
});
for await (const update of resumedStreamWithAsNode) {
console.log(update);
}
console.log(await graph.getState({
configurable: {
thread_id: "14",
}
}, { subgraphs: true }));
{ routerNode: { route: 'weather' } }
interrupted!
{
values: {
messages: [
HumanMessage {
"id": "90e9ff28-5b13-4e10-819a-31999efe303c",
"content": "What's the weather in sf",
"additional_kwargs": {},
"response_metadata": {}
}
],
route: 'weather'
},
next: [ 'weatherGraph' ],
tasks: [
{
id: 'f421fca8-de9e-5683-87ab-6ea9bb8d6275',
name: 'weatherGraph',
path: [Array],
interrupts: [],
state: [Object]
}
],
metadata: {
source: 'loop',
writes: { routerNode: [Object] },
step: 1,
parents: {}
},
config: {
configurable: {
thread_id: '14',
checkpoint_id: '1ef7c6ba-63ac-68f1-8001-5f7ada5f98e8',
checkpoint_ns: ''
}
},
createdAt: '2024-09-27T00:58:46.399Z',
parentConfig: {
configurable: {
thread_id: '14',
checkpoint_ns: '',
checkpoint_id: '1ef7c6ba-5f20-6020-8000-1ff649773a32'
}
}
}
[ [], { weatherGraph: { messages: [Array] } } ]
{
values: {
messages: [
HumanMessage {
"id": "90e9ff28-5b13-4e10-819a-31999efe303c",
"content": "What's the weather in sf",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"id": "af761e6d-9f6a-4467-9a3c-489bed3fbad7",
"content": "rainy",
"additional_kwargs": {},
"response_metadata": {},
"tool_calls": [],
"invalid_tool_calls": []
}
],
route: 'weather'
},
next: [],
tasks: [],
metadata: {
source: 'loop',
writes: { weatherGraph: [Object] },
step: 2,
parents: {}
},
config: {
configurable: {
thread_id: '14',
checkpoint_id: '1ef7c6ba-69e6-6cc0-8002-1751fc5bdd8f',
checkpoint_ns: ''
}
},
createdAt: '2024-09-27T00:58:47.052Z',
parentConfig: {
configurable: {
thread_id: '14',
checkpoint_ns: '',
checkpoint_id: '1ef7c6ba-63ac-68f1-8001-5f7ada5f98e8'
}
}
}
rainy
而不是 sunny
。
充当整个子图¶
最后,我们也可以通过充当整个子图来更新图。这与上面的情况类似,但我们不仅仅充当 weatherNode
,而是充当整个 weatherGraph
子图。这是通过传入正常的图配置以及 asNode
参数来完成的,我们在其中指定我们充当整个子图节点。
const entireSubgraphExampleStream = await graph.stream({
messages: [
{
role: "user",
content: "what's the weather in sf"
}
],
}, {
configurable: {
thread_id: "8",
},
streamMode: "updates",
subgraphs: true,
});
for await (const update of entireSubgraphExampleStream) {
console.log(update);
}
// Graph execution should stop before the weather node
console.log("interrupted!");
// We update the state by passing in the message we want returned from the weather graph.
// Note that we don't need to pass in the subgraph config, since we aren't updating the state inside the subgraph
await graph.updateState({
configurable: {
thread_id: "8",
}
}, {
messages: [{ role: "assistant", content: "rainy" }]
}, "weatherGraph");
const resumedEntireSubgraphExampleStream = await graph.stream(null, {
configurable: {
thread_id: "8",
},
streamMode: "updates",
});
for await (const update of resumedEntireSubgraphExampleStream) {
console.log(update);
}
const currentStateAfterUpdate = await graph.getState({
configurable: {
thread_id: "8",
}
});
console.log(currentStateAfterUpdate.values.messages);
[ [], { routerNode: { route: 'weather' } } ]
[
[ 'weatherGraph:db9c3bb2-5d27-5dae-a724-a8d702b33e86' ],
{ modelNode: { city: 'San Francisco' } }
]
interrupted!
[
HumanMessage {
"id": "001282b0-ca2e-443f-b6ee-8cb16c81bf59",
"content": "what's the weather in sf",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"id": "4b5d49cf-0f87-4ee8-b96f-eaa8716b9e9c",
"content": "rainy",
"additional_kwargs": {},
"response_metadata": {},
"tool_calls": [],
"invalid_tool_calls": []
}
]
双重嵌套子图¶
无论嵌套级别如何,此相同功能都将继续工作。这是一个使用双重嵌套子图执行相同操作的示例(尽管任何嵌套级别都将起作用)。我们在我们已定义的图之上添加另一个路由器。
首先,让我们重新创建我们上面一直在使用的图。这次我们将使用没有检查点的编译它,因为它本身将是一个子图!
const parentGraph = new StateGraph(RouterStateAnnotation)
.addNode("routerNode", routerNode)
.addNode("normalLLMNode", normalLLMNode)
.addNode("weatherGraph", subgraph)
.addEdge("__start__", "routerNode")
.addConditionalEdges("routerNode", routeAfterPrediction)
.addEdge("normalLLMNode", "__end__")
.addEdge("weatherGraph", "__end__")
.compile();
现在让我们声明一个“祖父”图,该图使用此图作为子图
const checkpointer = new MemorySaver();
const GrandfatherStateAnnotation = Annotation.Root({
...MessagesAnnotation.spec,
toContinue: Annotation<boolean>,
});
const grandparentRouterNode = async (_state: typeof GrandfatherStateAnnotation.State) => {
// Dummy logic that will always continue
return { toContinue: true };
};
const grandparentConditionalEdge = async (state: typeof GrandfatherStateAnnotation.State) => {
if (state.toContinue) {
return "parentGraph";
} else {
return "__end__";
}
};
const grandparentGraph = new StateGraph(GrandfatherStateAnnotation)
.addNode("routerNode", grandparentRouterNode)
.addNode("parentGraph", parentGraph)
.addEdge("__start__", "routerNode")
.addConditionalEdges("routerNode", grandparentConditionalEdge)
.addEdge("parentGraph", "__end__")
.compile({ checkpointer });
这是一个显示其外观的图表
如果我们运行到中断,我们现在可以看到所有三个图的状态快照
const grandparentConfig = {
configurable: { thread_id: "123" },
};
const grandparentGraphStream = await grandparentGraph.stream({
messages: [{
role: "user",
content: "what's the weather in SF"
}],
}, {
...grandparentConfig,
streamMode: "updates",
subgraphs: true
});
for await (const update of grandparentGraphStream) {
console.log(update);
}
[ [], { routerNode: { toContinue: true } } ]
[
[ 'parentGraph:095bb8a9-77d3-5a0c-9a23-e1390dcf36bc' ],
{ routerNode: { route: 'weather' } }
]
[
[
'parentGraph:095bb8a9-77d3-5a0c-9a23-e1390dcf36bc',
'weatherGraph:b1da376c-25a5-5aae-82da-4ff579f05d43'
],
{ modelNode: { city: 'San Francisco' } }
]
const grandparentGraphState = await grandparentGraph.getState(
grandparentConfig,
{ subgraphs: true }
);
const parentGraphState = grandparentGraphState.tasks[0].state as StateSnapshot;
const subgraphState = parentGraphState.tasks[0].state as StateSnapshot;
console.log("Grandparent State:");
console.log(grandparentGraphState.values);
console.log("---------------");
console.log("Parent Graph State:");
console.log(parentGraphState.values);
console.log("---------------");
console.log("Subgraph State:");
console.log(subgraphState.values);
Grandparent State:
{
messages: [
HumanMessage {
"id": "5788e436-a756-4ff5-899a-82117a5c59c7",
"content": "what's the weather in SF",
"additional_kwargs": {},
"response_metadata": {}
}
],
toContinue: true
}
---------------
Parent Graph State:
{
messages: [
HumanMessage {
"id": "5788e436-a756-4ff5-899a-82117a5c59c7",
"content": "what's the weather in SF",
"additional_kwargs": {},
"response_metadata": {}
}
],
route: 'weather'
}
---------------
Subgraph State:
{
messages: [
HumanMessage {
"id": "5788e436-a756-4ff5-899a-82117a5c59c7",
"content": "what's the weather in SF",
"additional_kwargs": {},
"response_metadata": {}
}
],
city: 'San Francisco'
}
await grandparentGraph.updateState(subgraphState.config, {
messages: [{
role: "assistant",
content: "rainy"
}]
}, "weatherNode");
const updatedGrandparentGraphStream = await grandparentGraph.stream(null, {
...grandparentConfig,
streamMode: "updates",
subgraphs: true,
});
for await (const update of updatedGrandparentGraphStream) {
console.log(update);
}
console.log((await grandparentGraph.getState(grandparentConfig)).values.messages)
[
[ 'parentGraph:095bb8a9-77d3-5a0c-9a23-e1390dcf36bc' ],
{ weatherGraph: { messages: [Array] } }
]
[ [], { parentGraph: { messages: [Array] } } ]
[
HumanMessage {
"id": "5788e436-a756-4ff5-899a-82117a5c59c7",
"content": "what's the weather in SF",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"id": "1c161973-9a9d-414d-b631-56791d85e2fb",
"content": "rainy",
"additional_kwargs": {},
"response_metadata": {},
"tool_calls": [],
"invalid_tool_calls": []
}
]
我们可以浏览状态历史记录,以查看祖父图的状态在每个步骤是如何更新的。
const grandparentStateHistories = await grandparentGraph.getStateHistory(grandparentConfig);
for await (const state of grandparentStateHistories) {
console.log(state);
console.log("-----");
}
{
values: {
messages: [
HumanMessage {
"id": "5788e436-a756-4ff5-899a-82117a5c59c7",
"content": "what's the weather in SF",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"id": "1c161973-9a9d-414d-b631-56791d85e2fb",
"content": "rainy",
"additional_kwargs": {},
"response_metadata": {},
"tool_calls": [],
"invalid_tool_calls": []
}
],
toContinue: true
},
next: [],
tasks: [],
metadata: {
source: 'loop',
writes: { parentGraph: [Object] },
step: 2,
parents: {}
},
config: {
configurable: {
thread_id: '123',
checkpoint_ns: '',
checkpoint_id: '1ef7c6ba-8560-67d0-8002-2e2cedd7de18'
}
},
createdAt: '2024-09-27T00:58:49.933Z',
parentConfig: {
configurable: {
thread_id: '123',
checkpoint_ns: '',
checkpoint_id: '1ef7c6ba-7977-62c0-8001-13e89cb2bbab'
}
}
}
-----
{
values: {
messages: [
HumanMessage {
"id": "5788e436-a756-4ff5-899a-82117a5c59c7",
"content": "what's the weather in SF",
"additional_kwargs": {},
"response_metadata": {}
}
],
toContinue: true
},
next: [ 'parentGraph' ],
tasks: [
{
id: '095bb8a9-77d3-5a0c-9a23-e1390dcf36bc',
name: 'parentGraph',
path: [Array],
interrupts: [],
state: [Object]
}
],
metadata: {
source: 'loop',
writes: { routerNode: [Object] },
step: 1,
parents: {}
},
config: {
configurable: {
thread_id: '123',
checkpoint_ns: '',
checkpoint_id: '1ef7c6ba-7977-62c0-8001-13e89cb2bbab'
}
},
createdAt: '2024-09-27T00:58:48.684Z',
parentConfig: {
configurable: {
thread_id: '123',
checkpoint_ns: '',
checkpoint_id: '1ef7c6ba-7972-64a0-8000-243575e3244f'
}
}
}
-----
{
values: {
messages: [
HumanMessage {
"id": "5788e436-a756-4ff5-899a-82117a5c59c7",
"content": "what's the weather in SF",
"additional_kwargs": {},
"response_metadata": {}
}
]
},
next: [ 'routerNode' ],
tasks: [
{
id: '00ed334c-47b5-5693-92b4-a5b83373e2a0',
name: 'routerNode',
path: [Array],
interrupts: [],
state: undefined
}
],
metadata: { source: 'loop', writes: null, step: 0, parents: {} },
config: {
configurable: {
thread_id: '123',
checkpoint_ns: '',
checkpoint_id: '1ef7c6ba-7972-64a0-8000-243575e3244f'
}
},
createdAt: '2024-09-27T00:58:48.682Z',
parentConfig: {
configurable: {
thread_id: '123',
checkpoint_ns: '',
checkpoint_id: '1ef7c6ba-796f-6d90-ffff-25ed0eb5bb38'
}
}
}
-----
{
values: { messages: [] },
next: [ '__start__' ],
tasks: [
{
id: 'ea62628e-881d-558d-bafc-e8b6a734e8aa',
name: '__start__',
path: [Array],
interrupts: [],
state: undefined
}
],
metadata: {
source: 'input',
writes: { __start__: [Object] },
step: -1,
parents: {}
},
config: {
configurable: {
thread_id: '123',
checkpoint_ns: '',
checkpoint_id: '1ef7c6ba-796f-6d90-ffff-25ed0eb5bb38'
}
},
createdAt: '2024-09-27T00:58:48.681Z',
parentConfig: undefined
}
-----