作者:彬彬侠
如何将 LangGraph 集成到你的 React 应用(How to integrate LangGraph into your React application)
预备知识
LangGraph PlatformLangGraph Server
useStream() React 钩子提供了一种无缝的方式,将 LangGraph 集成到你的 React 应用中。它处理了流式传输、状态管理和分支逻辑的所有复杂性,让你可以专注于构建出色的聊天体验。
主要功能:
消息流式传输:处理消息片段流以形成完整消息自动状态管理:管理消息、中断、加载状态和错误对话分支:从聊天历史的任意点创建替代对话路径与 UI 无关的设计:使用你自己的组件和样式
让我们探索如何在你的 React 应用中使用 useStream()。
useStream() 为创建定制的聊天体验提供了坚实的基础。对于预构建的聊天组件和界面,我们还推荐查看 CopilotKit 和 assistant-ui。
安装
- npminstall @langchain/langgraph-sdk @langchain/core
复制代码 示例
- "use client";
- import { useStream } from "@langchain/langgraph-sdk/react";
- import type { Message } from "@langchain/langgraph-sdk";
- export default function App() {
- const thread = useStream<{ messages: Message[] }>({
- apiUrl: "http://localhost:2024",
- assistantId: "agent",
- messagesKey: "messages",
- });
- return (
- <div>
- <div>
- {thread.messages.map((message) => (
- <div key={message.id}>{message.content as string}</div>
- ))}
- </div>
- <form
- onSubmit={(e) => {
- e.preventDefault();
- const form = e.target as HTMLFormElement;
- const message = new FormData(form).get("message") as string;
- form.reset();
- thread.submit({ messages: [{ type: "human", content: message }] });
- }}
- >
- <input type="text" name="message" />
- {thread.isLoading ? (
- <button key="stop" type="button" onClick={() => thread.stop()}>
- Stop
- </button>
- ) : (
- <button key="submit" type="submit">Send</button>
- )}
- </form>
- </div>
- );
- }
复制代码 自定义你的 UI
useStream() 钩子在后台处理所有复杂的状态管理,为你提供简单的接口来构建 UI。以下是你开箱即用的功能:
线程状态管理加载和错误状态中断消息处理和更新分支支持
以下是如何有效使用这些功能的示例:
加载状态
isLoading 属性告诉你何时有活跃的流,使你能够:
- export default function App() {
- const { isLoading, stop } = useStream<{ messages: Message[] }>({
- apiUrl: "http://localhost:2024",
- assistantId: "agent",
- messagesKey: "messages",
- });
- return (
- <form>
- {isLoading && (
- <button key="stop" type="button" onClick={() => stop()}>
- Stop
- </button>
- )}
- </form>
- );
- }
复制代码 线程管理
通过内置的线程管理跟踪对话。你可以访问当前线程 ID,并在创建新线程时收到通知:- const [threadId, setThreadId] = useState<string | null>(null);
- const thread = useStream<{ messages: Message[] }>({
- apiUrl: "http://localhost:2024",
- assistantId: "agent",
- threadId: threadId,
- onThreadId: setThreadId,
- });
复制代码 我们建议将 threadId 存储在 URL 的查询参数中,以便用户在页面刷新后可以恢复对话。
消息处理
useStream() 钩子会跟踪从服务器接收的消息片段,并将它们拼接在一起形成完整消息。完成的消息片段可以通过 messages 属性检索。
默认情况下,messagesKey 设置为 messages,新消息片段将追加到 values["messages"] 中。如果你的消息存储在不同的键下,可以更改 messagesKey 的值。- import type { Message } from "@langchain/langgraph-sdk";
- import { useStream } from "@langchain/langgraph-sdk/react";
- export default function HomePage() {
- const thread = useStream<{ messages: Message[] }>({
- apiUrl: "http://localhost:2024",
- assistantId: "agent",
- messagesKey: "messages",
- });
- return (
- <div>
- {thread.messages.map((message) => (
- <div key={message.id}>{message.content as string}</div>
- ))}
- </div>
- );
- }
复制代码 在底层,useStream() 钩子使用 streamMode: "messages-tuple" 从图节点内的任何 LangChain 聊天模型调用接收消息流(即单个 LLM 令牌)。了解更多关于消息流式传输的信息,请参见流式传输指南。
中断
useStream() 钩子暴露了 interrupt 属性,其中包含线程的最后一次中断。你可以使用中断来:
在执行节点之前渲染确认 UI等待用户输入,允许代理向用户提出澄清问题
了解更多关于中断的信息,请参见如何处理中断指南。- const thread = useStream<{ messages: Message[] }, { InterruptType: string }>({
- apiUrl: "http://localhost:2024",
- assistantId: "agent",
- messagesKey: "messages",
- });
- if (thread.interrupt) {
- return (
- <div>
- 已中断!{thread.interrupt.value}
- <button
- type="button"
- onClick={() => {
- // `resume` 可以是代理接受的任何值
- thread.submit(undefined, { command: { resume: true } });
- }}
- >
- Resume
- </button>
- </div>
- );
- }
复制代码 分支
对于每条消息,你可以使用 getMessagesMetadata() 获取消息首次出现的第一个检查点。然后,你可以从首次出现检查点之前的检查点创建新的运行,以在线程中创建新的分支。
分支可以通过以下方式创建:
- "use client";
- import type { Message } from "@langchain/langgraph-sdk";
- import { useStream } from "@langchain/langgraph-sdk/react";
- import { useState } from "react";
- function BranchSwitcher({
- branch,
- branchOptions,
- onSelect,
- }: {
- branch: string | undefined;
- branchOptions: string[] | undefined;
- onSelect: (branch: string) => void;
- }) {
- if (!branchOptions || !branch) return null;
- const index = branchOptions.indexOf(branch);
- return (
- <div className="flex items-center gap-2">
- <button
- type="button"
- onClick={() => {
- const prevBranch = branchOptions[index - 1];
- if (!prevBranch) return;
- onSelect(prevBranch);
- }}
- >
- Prev
- </button>
- <span>
- {index + 1} / {branchOptions.length}
- </span>
- <button
- type="button"
- onClick={() => {
- const nextBranch = branchOptions[index + 1];
- if (!nextBranch) return;
- onSelect(nextBranch);
- }}
- >
- Next
- </button>
- </div>
- );
- }
- function EditMessage({
- message,
- onEdit,
- }: {
- message: Message;
- onEdit: (message: Message) => void;
- }) {
- const [editing, setEditing] = useState(false);
- if (!editing) {
- return (
- <button type="button" onClick={() => setEditing(true)}>
- Edit
- </button>
- );
- }
- return (
- <form
- onSubmit={(e) => {
- e.preventDefault();
- const form = e.target as HTMLFormElement;
- const content = new FormData(form).get("content") as string;
- form.reset();
- onEdit({ type: "human", content });
- setEditing(false);
- }}
- >
- <input name="content" defaultValue={message.content as string} />
- <button type="submit">Save</button>
- </form>
- );
- }
- export default function App() {
- const thread = useStream({
- apiUrl: "http://localhost:2024",
- assistantId: "agent",
- messagesKey: "messages",
- });
- return (
- <div>
- <div>
- {thread.messages.map((message) => {
- const meta = thread.getMessagesMetadata(message);
- const parentCheckpoint = meta?.firstSeenState?.parent_checkpoint;
- return (
- <div key={message.id}>
- <div>{message.content as string}</div>
- {message.type === "human" && (
- <EditMessage
- message={message}
- onEdit={(message) =>
- thread.submit(
- { messages: [message] },
- { checkpoint: parentCheckpoint }
- )
- }
- />
- )}
- {message.type === "ai" && (
- <button
- type="button"
- onClick={() =>
- thread.submit(undefined, { checkpoint: parentCheckpoint })
- }
- >
- <span>Regenerate</span>
- </button>
- )}
- <BranchSwitcher
- branch={meta?.branch}
- branchOptions={meta?.branchOptions}
- onSelect={(branch) => thread.setBranch(branch)}
- />
- </div>
- );
- })}
- </div>
- <form
- onSubmit={(e) => {
- e.preventDefault();
- const form = e.target as HTMLFormElement;
- const message = new FormData(form).get("message") as string;
- form.reset();
- thread.submit({ messages: [message] });
- }}
- >
- <input type="text" name="message" />
- {thread.isLoading ? (
- <button key="stop" type="button" onClick={() => thread.stop()}>
- Stop
- </button>
- ) : (
- <button key="submit" type="submit">
- Send
- </button>
- )}
- </form>
- </div>
- );
- }
复制代码 对于高级用例,你可以使用 experimental_branchTree 属性获取线程的树形表示,用于为非基于消息的图渲染分支控件。
乐观更新
你可以在向代理执行网络请求之前乐观更新客户端状态,从而为用户提供即时反馈,例如在代理看到请求之前立即显示用户消息。- const stream = useStream({
- apiUrl: "http://localhost:2024",
- assistantId: "agent",
- messagesKey: "messages",
- });
- const handleSubmit = (text: string) => {
- const newMessage = { type: "human" as const, content: text };
- stream.submit(
- { messages: [newMessage] },
- {
- optimisticValues(prev) {
- const prevMessages = prev.messages ?? [];
- const newMessages = [...prevMessages, newMessage];
- return { ...prev, messages: newMessages };
- },
- }
- );
- };
复制代码 TypeScript
useStream() 钩子对 TypeScript 应用非常友好,你可以为状态指定类型,以获得更好的类型安全性和 IDE 支持。- // 定义你的类型
- type State = {
- messages: Message[];
- context?: Record<string, unknown>;
- };
- // 在钩子中使用它们
- const thread = useStream<State>({
- apiUrl: "http://localhost:2024",
- assistantId: "agent",
- messagesKey: "messages",
- });
复制代码 你还可以选择为不同场景指定类型,例如:
ConfigurableType:config.configurable 属性的类型(默认:Record<string, unknown>)InterruptType:中断值的类型——即 interrupt(...) 函数的内容(默认:unknown)CustomEventType:自定义事件的类型(默认:unknown)UpdateType:提交函数的类型(默认:Partial<State>)
- const thread = useStream<
- State,
- {
- UpdateType: {
- messages: Message[] | Message;
- context?: Record<string, unknown>;
- };
- InterruptType: string;
- CustomEventType: {
- type: "progress" | "debug";
- payload: unknown;
- };
- ConfigurableType: {
- model: string;
- };
- }
- >({
- apiUrl: "http://localhost:2024",
- assistantId: "agent",
- messagesKey: "messages",
- });
复制代码 如果你使用 LangGraph.js,你还可以重用图的注解类型。但请确保仅导入注解模式的类型,以避免导入整个 LangGraph.js 运行时(即通过 import type { ... } 指令)。- import {
- Annotation,
- MessagesAnnotation,
- type StateType,
- type UpdateType,
- } from "@langchain/langgraph/web";
- const AgentState = Annotation.Root({
- ...MessagesAnnotation.spec,
- context: Annotation<string>(),
- });
- const thread = useStream<
- StateType<typeof AgentState.spec>,
- { UpdateType: UpdateType<typeof AgentState.spec> }
- >({
- apiUrl: "http://localhost:2024",
- assistantId: "agent",
- messagesKey: "messages",
- });
复制代码 事件处理
useStream() 钩子提供了多个回调选项,帮助你响应不同事件:
onError:在发生错误时调用。onFinish:在流完成时调用。onUpdateEvent:在接收到更新事件时调用。onCustomEvent:在接收到自定义事件时调用。了解如何流式传输自定义事件,请参见流式传输指南。onMetadataEvent:在接收到包含运行 ID 和线程 ID 的元数据事件时调用。
Learn More
总结
集成机制:本指南展示了如何使用 useStream() React 钩子将 LangGraph 集成到 React 应用中,处理流式传输、状态管理、分支逻辑等复杂性,提供灵活的聊天体验构建方式。
- 核心功能:
安装:通过 npm install @langchain/langgraph-sdk @langchain/core 安装依赖。
- 基本用法:
使用 useStream() 钩子连接 LangGraph 部署(通过 apiUrl 和 assistantId)。渲染消息列表(thread.messages)并通过 thread.submit() 发送用户输入。管理加载状态(isLoading)和停止流(thread.stop())。
- 高级功能:
线程管理:通过 threadId 和 onThreadId 跟踪和恢复对话,建议存储在 URL 查询参数中。消息处理:自动拼接消息片段(通过 messagesKey 指定存储键),支持 streamMode: "messages-tuple"。中断:使用 thread.interrupt 处理中断,渲染确认 UI 或等待用户输入。分支:通过 getMessagesMetadata() 和 thread.submit() 创建对话分支,支持编辑用户消息或重新生成助手消息。乐观更新:通过 optimisticValues 在请求前更新客户端状态,提供即时反馈。TypeScript 支持:定义状态和配置类型(如 State、InterruptType),支持类型安全和 IDE 提示。事件处理:通过回调(如 onError、onFinish、onCustomEvent)响应错误、完成、自定义事件等。
- 使用建议:
UI 定制:利用 useStream() 的 UI 无关设计,结合自定义组件和样式(如 CopilotKit 或 assistant-ui)构建个性化界面。线程持久化:存储 threadId 以支持会话恢复,尤其在页面刷新后。中断和分支:结合中断和分支逻辑,支持人机交互和对话回溯。性能优化:使用乐观更新减少用户等待时间,通过 onError 提供清晰的错误反馈。TypeScript:定义详细类型(如 State、ConfigurableType),提高代码可维护性,特别是在复杂图中重用 LangGraph.js 注解。生产部署:确保 apiUrl 指向正确的 LangGraph 部署,验证网络可达性和 API 密钥(如 LANGSMITH_API_KEY 用于跟踪)。
- 注意事项:
依赖 @langchain/langgraph-sdk 和 @langchain/core,确保版本兼容。需要运行 LangGraph 服务器(默认 http://localhost:2024),并配置正确的 assistantId。如果需要更复杂的 UI(如多模态输入)或高级事件处理(如自定义事件流),可扩展 useStream() 配置。
参考资料:
https://langchain-ai.github.io/langgraph/cloud/how-tos/use_stream_react/
原文地址:https://blog.csdn.net/u013172930/article/details/148141472 |