如何创建和控制循环¶
当创建一个带有循环的图时,我们需要一种终止执行的机制。这通常通过添加一个 条件边 来完成,该条件边在达到某些终止条件后路由到 END 节点。
您还可以在调用或流式传输图时设置图的递归限制。递归限制设置了图在引发错误之前允许执行的 超步 数量。在此处阅读更多关于递归限制概念的 信息。
让我们考虑一个简单的带有循环的图,以更好地理解这些机制是如何工作的。
概述¶
当创建循环时,您可以包含一个条件边,该条件边指定终止条件
const route = async function (state: typeof StateAnnotation.State) {
if (terminationCondition(state)) {
return "__END__";
} else {
return "a";
}
}
const graph = StateGraph(State)
.addNode(a)
.addNode(b)
.addConditionalEdges("a", route)
.addEdge("b", "a")
.compile();
要控制递归限制,请在配置中指定 "recursionLimit"
。这将引发一个 GraphRecursionError
,您可以捕获并处理它
import { GraphRecursionError } from "@langchain/langgraph";
try {
await graph.invoke(inputs, { recursionLimit: 3 });
} catch (error) {
if (error instanceof GraphRecursionError) {
console.log("Recursion Error");
} else {
throw error;
}
}
设置¶
首先,让我们安装所需的软件包
设置 LangSmith 以进行 LangGraph 开发
注册 LangSmith 以快速发现问题并提高 LangGraph 项目的性能。LangSmith 允许您使用跟踪数据来调试、测试和监控使用 LangGraph 构建的 LLM 应用程序 — 在 此处 阅读更多关于如何开始使用的信息。
定义图¶
让我们定义一个带有简单循环的图。请注意,我们使用条件边来实现终止条件。
import { StateGraph, Annotation } from "@langchain/langgraph";
// Define the state with a reducer
const StateAnnotation = Annotation.Root({
aggregate: Annotation<string[]>({
reducer: (a, b) => a.concat(b),
default: () => [],
}),
});
// Define nodes
const a = async function (state: typeof StateAnnotation.State) {
console.log(`Node A sees ${state.aggregate}`);
return { aggregate: ["A"] };
}
const b = async function (state: typeof StateAnnotation.State) {
console.log(`Node B sees ${state.aggregate}`);
return { aggregate: ["B"] };
}
// Define edges
const route = async function (state: typeof StateAnnotation.State) {
if (state.aggregate.length < 7) {
return "b";
} else {
return "__end__";
}
}
// Define the graph
const graph = new StateGraph(StateAnnotation)
.addNode("a", a)
.addNode("b", b)
.addEdge("__start__", "a")
.addConditionalEdges("a", route)
.addEdge("b", "a")
.compile();
import * as tslab from "tslab";
const drawableGraph = graph.getGraph();
const image = await drawableGraph.drawMermaidPng();
const arrayBuffer = await image.arrayBuffer();
await tslab.display.png(new Uint8Array(arrayBuffer));
此架构类似于 ReAct agent,其中节点 "a"
是一个工具调用模型,节点 "b"
代表工具。
在我们的 route
条件边中,我们指定在状态中的 "aggregate"
列表通过阈值长度后应该结束。
调用该图,我们看到我们在节点 "a"
和 "b"
之间交替,直到达到终止条件。
Node A sees
Node B sees A
Node A sees A,B
Node B sees A,B,A
Node A sees A,B,A,B
Node B sees A,B,A,B,A
Node A sees A,B,A,B,A,B
{
aggregate: [
'A', 'B', 'A',
'B', 'A', 'B',
'A'
]
}
设置递归限制¶
在某些应用程序中,我们可能无法保证会达到给定的终止条件。在这些情况下,我们可以设置图的 递归限制。这将在给定数量的 超步 后引发 GraphRecursionError
。然后我们可以捕获并处理此异常
import { GraphRecursionError } from "@langchain/langgraph";
try {
await graph.invoke({ aggregate: [] }, { recursionLimit: 4 });
} catch (error) {
if (error instanceof GraphRecursionError) {
console.log("Recursion Error");
} else {
throw error;
}
}
带分支的循环¶
为了更好地理解递归限制是如何工作的,让我们考虑一个更复杂的例子。下面我们实现一个循环,但一个步骤会扇出到两个节点
import { StateGraph, Annotation } from "@langchain/langgraph";
// Define the state with a reducer
const StateAnnotationWithLoops = Annotation.Root({
aggregate: Annotation<string[]>({
reducer: (a, b) => a.concat(b),
default: () => [],
}),
});
// Define nodes
const nodeA = async function (state: typeof StateAnnotationWithLoops.State) {
console.log(`Node A sees ${state.aggregate}`);
return { aggregate: ["A"] };
}
const nodeB = async function (state: typeof StateAnnotationWithLoops.State) {
console.log(`Node B sees ${state.aggregate}`);
return { aggregate: ["B"] };
}
const nodeC = async function (state: typeof StateAnnotationWithLoops.State) {
console.log(`Node C sees ${state.aggregate}`);
return { aggregate: ["C"] };
}
const nodeD = async function (state: typeof StateAnnotationWithLoops.State) {
console.log(`Node D sees ${state.aggregate}`);
return { aggregate: ["D"] };
}
// Define edges
const loopRouter = async function (state: typeof StateAnnotationWithLoops.State) {
if (state.aggregate.length < 7) {
return "b";
} else {
return "__end__";
}
}
// Define the graph
const graphWithLoops = new StateGraph(StateAnnotationWithLoops)
.addNode("a", nodeA)
.addNode("b", nodeB)
.addNode("c", nodeC)
.addNode("d", nodeD)
.addEdge("__start__", "a")
.addConditionalEdges("a", loopRouter)
.addEdge("b", "c")
.addEdge("b", "d")
.addEdge(["c", "d"], "a")
.compile();
import * as tslab from "tslab";
const drawableGraphWithLoops = graphWithLoops.getGraph();
const imageWithLoops = await drawableGraphWithLoops.drawMermaidPng();
const arrayBufferWithLoops = await imageWithLoops.arrayBuffer();
await tslab.display.png(new Uint8Array(arrayBufferWithLoops));
这个图看起来很复杂,但可以概念化为 超步 的循环
- 节点 A
- 节点 B
- 节点 C 和 D
- 节点 A
- ...
我们有一个四个超步的循环,其中节点 C 和 D 并发执行。
像以前一样调用该图,我们看到我们完成了两个完整的“圈”,然后才达到终止条件
Node A sees
Node B sees A
Node C sees A,B
Node D sees A,B
Node A sees A,B,C,D
Node B sees A,B,C,D,A
Node C sees A,B,C,D,A,B
Node D sees A,B,C,D,A,B
Node A sees A,B,C,D,A,B,C,D
{
aggregate: [
'A', 'B', 'C',
'D', 'A', 'B',
'C', 'D', 'A'
]
}