如何创建和控制循环¶
创建带有循环的图时,我们需要一种终止执行的机制。最常见的方法是添加一个条件边,一旦达到某个终止条件,就将其路由到 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();
要控制递归限制,请在 config 中指定 "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;
}
}
设置¶
首先,安装所需的包
为 LangGraph 开发设置 LangSmith
注册 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 代理,其中节点 "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'
]
}