LangGraph.js 概念介绍

在图中使用共享状态存在一些设计上的权衡。例如,您可能会觉得这就像使用可怕的全局变量(尽管可以通过命名空间参数来缓解)。然而,共享类型化状态在构建 AI 工作流方面提供了许多好处,包括:

  • 每个“超级步骤”前后,数据流都可以完全检查。

  • 状态是可变的,允许用户或其他软件在超级步骤之间写入相同的状态以控制代理的方向(使用 updateState)。

  • 检查点定义明确,便于在任何存储后端中保存和恢复,甚至对整个工作流执行进行完全的版本控制。

我们将在下一节更详细地讨论检查点。

持久性

任何“智能”系统都需要内存才能运行。AI 代理也不例外,并且需要跨越一个或多个时间框架的内存:

  • 它们总是需要记住在这个任务中采取的步骤(以避免在回答给定查询时重复)。

  • 它们经常需要记住与用户的前几轮多轮对话(用于共指解析和附加上下文)。

  • 理想情况下,它们“记住”与用户之前的交互以及在给定“环境”(例如应用程序上下文)中的行为,以表现得更个性化和高效。

后一种形式的内存涵盖很多内容(个性化、优化、持续学习等),这超出了本文的范围,不过它可以轻松集成到任何 LangGraph.js 工作流中,并且我们正在积极探索以原生方式公开此功能的最佳方法。

StateGraph API 通过检查点原生支持前两种形式的内存。

检查点

检查点代表用户(或用户组或其他系统)之间(可能)的多轮交互中的线程状态。在单个运行中创建的检查点在从该状态恢复时将具有一组要执行的下一个节点。在给定运行结束时创建的检查点相同,只是没有要转换的下一个节点(图正在等待用户输入)。

检查点支持聊天记忆等功能,允许您标记并持久化系统采取的每个状态,无论是在单个运行中还是在多轮交互中。让我们探讨一下为什么这很有用:

单次运行内存

在给定的运行中,在每个步骤都创建检查点。这意味着您可以要求您的代理创造世界和平。当它失败并遇到错误时,您总是可以从保存的检查点恢复其任务。

这也允许您构建人类在环工作流,这在客户支持机器人、编程助手和其他应用程序中很常见。您可以在执行给定节点之前或之后中断图的执行,并将控制权“升级”给用户或支持人员。工作人员可能会立即响应,也可能会在一个月后响应。无论如何,您的工作流可以随时恢复,就好像没有时间过去一样。

多轮记忆

检查点在 thread_id 下保存,以支持用户和系统之间的多轮交互。对于开发人员,在配置图以添加多轮记忆支持时没有区别,因为检查点的工作方式始终相同。

如果您有一些希望在轮次之间保留的状态和一些希望视为“临时”的状态,则可以在图的最终节点中清除相关状态。

使用检查点就像调用 compile({ checkpointer: myCheckpointer }) 一样简单,然后在其可配置参数中使用 thread_id 调用它。您可以在下一节中看到更多!

配置

对于任何给定的图部署,您可能希望在运行时控制一些可配置的值。这些值与图输入不同,因为它们不应被视为状态变量。它们更像是“带外”通信。

一个常见的例子是对话的 thread_id、user_id、要使用的 LLM、检索器中要返回的文档数量等。虽然这些可以在状态中传递,但最好将它们与常规数据流分开。

示例

让我们回顾另一个示例,看看我们的多轮记忆是如何工作的!您能猜出运行此图的结果和 result2 会是什么吗?

配置

import { END, MemorySaver, START, StateGraph } from "@langchain/langgraph";
​
interface State {
  total: number;
  turn?: string;
}
​
function addF(existing: number, updated?: number) {
  return existing + (updated?? 0);
}
​
const builder = new StateGraph<State>({
  channels: {
    total: {
      value: addF,
      default: () => 0,
    },
  },
})
 .addNode("add_one", (_state) => ({ total: 1 }))
 .addEdge(START, "add_one")
 .addEdge("add_one", END);
​
const memory = new MemorySaver();
​
const graphG = builder.compile({ checkpointer: memory });
​
let threadId = "some-thread";
​
let config = { Configurable: { thread_id: threadId } };
​
const result = await graphG.invoke({ total: 1, turn: "First Turn" }, config);
​
const result2 = await graphG.invoke({ turn: "Next Turn" }, config);
​
const result3 = await graphG.invoke({ total: 5 }, config);
​
const result4 = await graphG.invoke(
  { total: 5 },
  { Configurable: { thread_id: "new-thread-id" } }
);
​
console.log(result);
// { total: 2, turn: 'First Turn' }
console.log(result2);
// { total: 3, turn: 'Next Turn' }
console.log(result3);
// { total: 9, turn: 'Next Turn' }
console.log(result4);
// { total: 6 }

对于第一次运行,未找到检查点,因此图基于原始输入运行。总值从 1 增加到 2,并且轮次设置为“First Turn”。

对于第二次运行,用户提供了“轮次”的更新,但没有提供总值!由于我们从状态加载,上一个结果增加 1(在我们的“add_one”节点中),并且“轮次”被用户覆盖。

对于第三次运行,“轮次”保持不变,因为它是从检查点加载的,但未被用户覆盖。总值由用户提供的值递增,因为它使用 add 函数更新现有值。

对于第四次运行,我们使用新的线程 ID,未找到检查点,因此结果只是用户提供的总值加 1。

您可能会注意到,这种面向用户的行为等同于在没有检查点的情况下运行以下命令。

配置

const graphB = builder.compile();
const resultB1 = await graphB.invoke({ total: 1, turn: "First Turn" });
const resultB2 = await graphB.invoke({...result, turn: "Next Turn" });
const resultB3 = await graphB.invoke({...result2, total: result2.total + 5 });
const resultB4 = await graphB.invoke({ total: 5 });
​
console.log(resultB1);
// { total: 2, turn: 'First Turn' }
console.log(resultB2);
// { total: 3, turn: 'Next Turn' }
console.log(resultB3);
// { total: 9, turn: 'Next Turn' }
console.log(resultB4);
// { total: 6 }

自己运行它以确认等效性。用户输入和检查点加载被视为任何其他状态更新。

既然我们已经介绍了 LangGraph.js 的核心概念,通过一个端到端的示例来了解所有部分如何组合在一起可能会更有帮助。

StateGraph 单次执行数据流

作为工程师,在不知道“幕后”发生了什么之前,我们永远不会满意。在前面的部分中,我们解释了 LangGraph.js 的一些核心概念。现在是时候展示它们是如何组合在一起的了。

让我们用一个条件边扩展我们的玩具示例,并遍历两次连续的调用。

数据流

import { START, END, StateGraph, MemorySaver } from "@langchain/langgraph";
​
interface State {
  total: number;
}
​
function addG(existing: number, updated?: number) {
  return existing + (updated?? 0);
}
​
const builderH = new StateGraph<State>({
  channels: {
    total: {
      value: addG,
      default: () => 0,
    },
  },
})
 .addNode("add_one", (_state) => ({ total: 1 }))
 .addNode("double", (state) => ({ total: state.total }))
 .addEdge(START, "add_one");
​
function route(state: State) {
  if (state.total < 6) {
    return "double";
  }
  return END;
}
​
builderH.addConditionalEdges("add_one", route);
builderH.addEdge("double", "add_one");
​
const memoryH = new MemorySaver();
const graphH = builderH.compile({ checkpointer: memoryH });
const threadId = "some-thread";
const config = { Configurable: { thread_id: threadId } };
​
for await (const step of await graphH.stream(
  { total: 1 },
  {...config,
​
 streamMode: "values" }
)) {
  console.log(step);
}
// 0 checkpoint { total: 1 }
// 1 task null
// 1 task_result null
// 1 checkpoint { total: 2 }
// 2 task null
// 2 task_result null
// 2 checkpoint { total: 4 }
// 3 task null
// 3 task_result null
// 3 checkpoint { total: 5 }
// 4 task null
// 4 task_result null
// 4 checkpoint { total: 10 }
// 5 task null
// 5 task_result null
// 5 checkpoint { total: 11 }

要跟踪此运行,请查看 LangSmith 链接。我们将逐步解释执行过程:

  1. 首先,图查找检查点。未找到,因此状态以总值为 0 初始化。

  2. 接下来,图将用户输入作为状态更新应用。缩减器将输入(1)添加到现有值(0)。在此超级步骤结束时,总值为(1)。

  3. 之后,调用“add_one”节点,返回 1。

  4. 接下来,缩减器将此更新应用于现有总值(1)。状态现在为 2。

  5. 然后,调用条件边“route”。由于值小于 6,我们继续到“double”节点。

  6. “double”节点获取现有状态(2)并返回。然后调用缩减器将其添加到现有状态。状态现在为 4。

  7. 图然后循环回到“add_one”(5),检查条件边,并由于小于 6 继续。加倍后,总值为(10)。

  8. 固定边循环回到“add_one”(11),检查条件边,由于大于 6,程序终止。

对于第二次运行,我们将使用相同的配置:

数据流

const resultH2 = await graphH.invoke({ total: -2, turn: "First Turn" }, config);
console.log(resultH2);
// { total: 10 }

要跟踪此运行,请查看 LangSmith 链接。我们将逐步解释执行过程:

  1. 首先,图查找检查点。将其加载到内存中作为初始状态。总值为之前的(11)。

  2. 接下来,它应用用户输入更新。add 缩减器将总值从 11 更新为 -9。

  3. 之后,调用“add_one”节点,返回 1。

  4. 使用缩减器应用此更新,将值提高到 10。

  5. 接下来,触发“route”条件边。由于值大于 6,我们终止程序,结束时为(11)。

结论

就是这样!我们已经探索了 LangGraph.js 的核心概念,并了解了如何使用它来创建可靠、容错的代理系统。通过将代理建模为状态机,LangGraph.js 为组合可扩展和可控的 AI 工作流提供了强大的抽象。

使用 LangGraph.js 时,请记住这些关键思想:

  • 节点执行工作;边决定控制流。

  • 缩减器精确定义了在每个步骤如何应用状态更新。

  • 检查点支持单次运行和多轮交互中的内存。

  • 中断允许您为人类在环工作流暂停、检索和更新图的状态。

  • 可配置参数允许独立于常规数据流的运行时控制。

凭借这些原则,您可以充分利用 LangGraph.js 的强大功能来构建高级 AI 代理系统。

  • 14
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
自动控制节水灌溉技术的高低代表着农业现代化的发展状况,灌溉系统自动化水平较低是制约我国高效农业发展的主要原因。本文就此问题研究了单片机控制的滴灌节水灌溉系统,该系统可对不同土壤的湿度进行监控,并按照作物对土壤湿度的要求进行适时、适量灌水,其核心是单片机和PC机构成的控制部分,主要对土壤湿度与灌水量之间的关系、灌溉控制技术及设备系统的硬件、软件编程各个部分进行了深入的研究。 单片机控制部分采用上下位机的形式。下位机硬件部分选用AT89C51单片机为核心,主要由土壤湿度传感器,信号处理电路,显示电路,输出控制电路,故障报警电路等组成,软件选用汇编语言编程。上位机选用586型以上PC机,通过MAX232芯片实现同下位机的电平转换功能,上下位机之间通过串行通信方式进行数据的双向传输,软件选用VB高级编程语言以建立友好的人机界面。系统主要具有以下功能:可在PC机提供的人机对话界面上设置作物要求的土壤湿度相关参数;单片机可将土壤湿度传感器检测到的土壤湿度模拟量转换成数字量,显示于LED显示器上,同时单片机可采用串行通信方式将此湿度值传输到PC机上;PC机通过其内设程序计算出所需的灌水量和灌水时间,且显示于界面上,并将有关的灌水信息反馈给单片机,若需灌水,则单片机系统启动鸣音报警,发出灌水信号,并经放大驱动设备,开启电磁阀进行倒计时定时灌水,若不需灌水,即PC机上显示的灌水量和灌水时间均为0,系统不进行灌水。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

幻想多巴胺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值