跳到内容

如何转换子图的输入和输出

你的子图状态有可能完全独立于父图状态,即两者之间没有重叠的通道(键)。例如,你可能有一个 supervisor 智能体,它需要借助多个 ReAct 智能体来生成报告。ReAct 智能体子图可能会跟踪消息列表,而 supervisor 只需要用户输入和最终报告在其状态中,并且不需要跟踪消息。

在这种情况下,你需要在调用子图之前转换其输入,然后在返回之前转换其输出。本指南将展示如何做到这一点。

设置

首先,让我们安装所需的软件包

npm install @langchain/langgraph @langchain/core

设置 LangSmith 用于 LangGraph 开发

注册 LangSmith 以快速发现问题并提高 LangGraph 项目的性能。LangSmith 允许你使用跟踪数据来调试、测试和监控使用 LangGraph 构建的 LLM 应用程序 — 在此处阅读更多关于如何开始的信息。


定义图和子图

让我们定义 3 个图:- 一个父图 - 一个将被父图调用的子图 - 一个将被子图调用的孙子图

定义孙子图

import { StateGraph, START, Annotation } from "@langchain/langgraph";

const GrandChildAnnotation = Annotation.Root({
    myGrandchildKey: Annotation<string>,
})

const grandchild1 = (state: typeof GrandChildAnnotation.State) => {
    // NOTE: child or parent keys will not be accessible here
    return {
        myGrandchildKey: state.myGrandchildKey + ", how are you"
    }
}

const grandchild = new StateGraph(GrandChildAnnotation)
    .addNode("grandchild1", grandchild1)
    .addEdge(START, "grandchild1")

const grandchildGraph = grandchild.compile();

await grandchildGraph.invoke({ myGrandchildKey: "hi Bob" })
{ myGrandchildKey: 'hi Bob, how are you' }

定义子图

import { StateGraph, START, Annotation } from "@langchain/langgraph";

const ChildAnnotation = Annotation.Root({
    myChildKey: Annotation<string>,
});

const callGrandchildGraph = async (state: typeof ChildAnnotation.State) => {
    // NOTE: parent or grandchild keys won't be accessible here
    // we're transforming the state from the child state channels (`myChildKey`)
    // to the grandchild state channels (`myGrandchildKey`)
    const grandchildGraphInput = { myGrandchildKey: state.myChildKey };
    // we're transforming the state from the grandchild state channels (`myGrandchildKey`)
    // back to the child state channels (`myChildKey`)
    const grandchildGraphOutput = await grandchildGraph.invoke(grandchildGraphInput);
    return {
        myChildKey: grandchildGraphOutput.myGrandchildKey + " today?"
    };
};

const child = new StateGraph(ChildAnnotation)
    // NOTE: we're passing a function here instead of just compiled graph (`childGraph`)
    .addNode("child1", callGrandchildGraph)
    .addEdge(START, "child1");

const childGraph = child.compile();

await childGraph.invoke({ myChildKey: "hi Bob" })
{ myChildKey: 'hi Bob, how are you today?' }

注意

我们将 grandchildGraph 调用包装在一个单独的函数 (callGrandchildGraph) 中,该函数在调用孙子图之前转换输入状态,然后将孙子图的输出转换回子图状态。如果你直接将 grandchildGraph 传递给 .addNode 而不进行转换,LangGraph 将会引发错误,因为子图和孙子图状态之间没有共享的状态通道(键)。


请注意,子图和孙子图有它们自己的、独立的状态,这些状态不与父图共享。

定义父图

import { StateGraph, START, END, Annotation } from "@langchain/langgraph";

const ParentAnnotation = Annotation.Root({
    myKey: Annotation<string>,
});

const parent1 = (state: typeof ParentAnnotation.State) => {
    // NOTE: child or grandchild keys won't be accessible here
    return { myKey: "hi " + state.myKey };
};

const parent2 = (state: typeof ParentAnnotation.State) => {
    return { myKey: state.myKey + " bye!" };
};

const callChildGraph = async (state: typeof ParentAnnotation.State) => {
    // we're transforming the state from the parent state channels (`myKey`)
    // to the child state channels (`myChildKey`)
    const childGraphInput = { myChildKey: state.myKey };
    // we're transforming the state from the child state channels (`myChildKey`)
    // back to the parent state channels (`myKey`)
    const childGraphOutput = await childGraph.invoke(childGraphInput);
    return { myKey: childGraphOutput.myChildKey };
};

const parent = new StateGraph(ParentAnnotation)
    .addNode("parent1", parent1)
    // NOTE: we're passing a function here instead of just a compiled graph (`childGraph`)
    .addNode("child", callChildGraph)
    .addNode("parent2", parent2)
    .addEdge(START, "parent1")
    .addEdge("parent1", "child")
    .addEdge("child", "parent2")
    .addEdge("parent2", END);

const parentGraph = parent.compile();

注意

我们将 childGraph 调用包装在一个单独的函数 (callChildGraph) 中,该函数在调用子图之前转换输入状态,然后将子图的输出转换回父图状态。如果你直接将 childGraph 传递给 .addNode 而不进行转换,LangGraph 将会引发错误,因为父图和子图状态之间没有共享的状态通道(键)。


让我们运行父图,并确保它正确地调用了子图和孙子图。

await parentGraph.invoke({ myKey: "Bob" })
{ myKey: 'hi Bob, how are you today? bye!' }
完美!父图正确地调用了子图和孙子图(我们知道这一点是因为“,你好吗”和“今天?”被添加到了我们原始的 “myKey” 状态值中)。