开启左侧

【LangGraph】Integrate LangGraph into a React app:将 LangGraph 集成到你的 React 应用 示例

[复制链接]
AI小编 发表于 9 小时前 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
作者:彬彬侠
如何将 LangGraph 集成到你的 React 应用(How to integrate LangGraph into your React application)

预备知识
    LangGraph PlatformLangGraph Server
useStream() React 钩子提供了一种无缝的方式,将 LangGraph 集成到你的 React 应用中。它处理了流式传输、状态管理和分支逻辑的所有复杂性,让你可以专注于构建出色的聊天体验。
主要功能:
    消息流式传输:处理消息片段流以形成完整消息自动状态管理:管理消息、中断、加载状态和错误对话分支:从聊天历史的任意点创建替代对话路径与 UI 无关的设计:使用你自己的组件和样式
让我们探索如何在你的 React 应用中使用 useStream()。
useStream() 为创建定制的聊天体验提供了坚实的基础。对于预构建的聊天组件和界面,我们还推荐查看 CopilotKit 和 assistant-ui。
安装
  1. npminstall @langchain/langgraph-sdk @langchain/core
复制代码
示例
  1. "use client";
  2. import { useStream } from "@langchain/langgraph-sdk/react";
  3. import type { Message } from "@langchain/langgraph-sdk";
  4. export default function App() {
  5.   const thread = useStream<{ messages: Message[] }>({
  6.     apiUrl: "http://localhost:2024",
  7.     assistantId: "agent",
  8.     messagesKey: "messages",
  9.   });
  10.   return (
  11.     <div>
  12.       <div>
  13.         {thread.messages.map((message) => (
  14.           <div key={message.id}>{message.content as string}</div>
  15.         ))}
  16.       </div>
  17.       <form
  18.         onSubmit={(e) => {
  19.           e.preventDefault();
  20.           const form = e.target as HTMLFormElement;
  21.           const message = new FormData(form).get("message") as string;
  22.           form.reset();
  23.           thread.submit({ messages: [{ type: "human", content: message }] });
  24.         }}
  25.       >
  26.         <input type="text" name="message" />
  27.         {thread.isLoading ? (
  28.           <button key="stop" type="button" onClick={() => thread.stop()}>
  29.             Stop
  30.           </button>
  31.         ) : (
  32.           <button key="submit" type="submit">Send</button>
  33.         )}
  34.       </form>
  35.     </div>
  36.   );
  37. }
复制代码
自定义你的 UI

useStream() 钩子在后台处理所有复杂的状态管理,为你提供简单的接口来构建 UI。以下是你开箱即用的功能:
    线程状态管理加载和错误状态中断消息处理和更新分支支持
以下是如何有效使用这些功能的示例:
加载状态

isLoading 属性告诉你何时有活跃的流,使你能够:
    显示加载指示器在处理期间禁用输入字段显示取消按钮
  1. export default function App() {
  2.   const { isLoading, stop } = useStream<{ messages: Message[] }>({
  3.     apiUrl: "http://localhost:2024",
  4.     assistantId: "agent",
  5.     messagesKey: "messages",
  6.   });
  7.   return (
  8.     <form>
  9.       {isLoading && (
  10.         <button key="stop" type="button" onClick={() => stop()}>
  11.           Stop
  12.         </button>
  13.       )}
  14.     </form>
  15.   );
  16. }
复制代码
线程管理

通过内置的线程管理跟踪对话。你可以访问当前线程 ID,并在创建新线程时收到通知:
  1. const [threadId, setThreadId] = useState<string | null>(null);
  2. const thread = useStream<{ messages: Message[] }>({
  3.   apiUrl: "http://localhost:2024",
  4.   assistantId: "agent",
  5.   threadId: threadId,
  6.   onThreadId: setThreadId,
  7. });
复制代码
我们建议将 threadId 存储在 URL 的查询参数中,以便用户在页面刷新后可以恢复对话。
消息处理

useStream() 钩子会跟踪从服务器接收的消息片段,并将它们拼接在一起形成完整消息。完成的消息片段可以通过 messages 属性检索。
默认情况下,messagesKey 设置为 messages,新消息片段将追加到 values["messages"] 中。如果你的消息存储在不同的键下,可以更改 messagesKey 的值。
  1. import type { Message } from "@langchain/langgraph-sdk";
  2. import { useStream } from "@langchain/langgraph-sdk/react";
  3. export default function HomePage() {
  4.   const thread = useStream<{ messages: Message[] }>({
  5.     apiUrl: "http://localhost:2024",
  6.     assistantId: "agent",
  7.     messagesKey: "messages",
  8.   });
  9.   return (
  10.     <div>
  11.       {thread.messages.map((message) => (
  12.         <div key={message.id}>{message.content as string}</div>
  13.       ))}
  14.     </div>
  15.   );
  16. }
复制代码
在底层,useStream() 钩子使用 streamMode: "messages-tuple" 从图节点内的任何 LangChain 聊天模型调用接收消息流(即单个 LLM 令牌)。了解更多关于消息流式传输的信息,请参见流式传输指南。
中断

useStream() 钩子暴露了 interrupt 属性,其中包含线程的最后一次中断。你可以使用中断来:
    在执行节点之前渲染确认 UI等待用户输入,允许代理向用户提出澄清问题
了解更多关于中断的信息,请参见如何处理中断指南。
  1. const thread = useStream<{ messages: Message[] }, { InterruptType: string }>({
  2.   apiUrl: "http://localhost:2024",
  3.   assistantId: "agent",
  4.   messagesKey: "messages",
  5. });
  6. if (thread.interrupt) {
  7.   return (
  8.     <div>
  9.       已中断!{thread.interrupt.value}
  10.       <button
  11.         type="button"
  12.         onClick={() => {
  13.           // `resume` 可以是代理接受的任何值
  14.           thread.submit(undefined, { command: { resume: true } });
  15.         }}
  16.       >
  17.         Resume
  18.       </button>
  19.     </div>
  20.   );
  21. }
复制代码
分支

对于每条消息,你可以使用 getMessagesMetadata() 获取消息首次出现的第一个检查点。然后,你可以从首次出现检查点之前的检查点创建新的运行,以在线程中创建新的分支。
分支可以通过以下方式创建:
    编辑之前的用户消息。请求重新生成之前的助手消息。
  1. "use client";
  2. import type { Message } from "@langchain/langgraph-sdk";
  3. import { useStream } from "@langchain/langgraph-sdk/react";
  4. import { useState } from "react";
  5. function BranchSwitcher({
  6.   branch,
  7.   branchOptions,
  8.   onSelect,
  9. }: {
  10.   branch: string | undefined;
  11.   branchOptions: string[] | undefined;
  12.   onSelect: (branch: string) => void;
  13. }) {
  14.   if (!branchOptions || !branch) return null;
  15.   const index = branchOptions.indexOf(branch);
  16.   return (
  17.     <div className="flex items-center gap-2">
  18.       <button
  19.         type="button"
  20.         onClick={() => {
  21.           const prevBranch = branchOptions[index - 1];
  22.           if (!prevBranch) return;
  23.           onSelect(prevBranch);
  24.         }}
  25.       >
  26.         Prev
  27.       </button>
  28.       <span>
  29.         {index + 1} / {branchOptions.length}
  30.       </span>
  31.       <button
  32.         type="button"
  33.         onClick={() => {
  34.           const nextBranch = branchOptions[index + 1];
  35.           if (!nextBranch) return;
  36.           onSelect(nextBranch);
  37.         }}
  38.       >
  39.         Next
  40.       </button>
  41.     </div>
  42.   );
  43. }
  44. function EditMessage({
  45.   message,
  46.   onEdit,
  47. }: {
  48.   message: Message;
  49.   onEdit: (message: Message) => void;
  50. }) {
  51.   const [editing, setEditing] = useState(false);
  52.   if (!editing) {
  53.     return (
  54.       <button type="button" onClick={() => setEditing(true)}>
  55.         Edit
  56.       </button>
  57.     );
  58.   }
  59.   return (
  60.     <form
  61.       onSubmit={(e) => {
  62.         e.preventDefault();
  63.         const form = e.target as HTMLFormElement;
  64.         const content = new FormData(form).get("content") as string;
  65.         form.reset();
  66.         onEdit({ type: "human", content });
  67.         setEditing(false);
  68.       }}
  69.     >
  70.       <input name="content" defaultValue={message.content as string} />
  71.       <button type="submit">Save</button>
  72.     </form>
  73.   );
  74. }
  75. export default function App() {
  76.   const thread = useStream({
  77.     apiUrl: "http://localhost:2024",
  78.     assistantId: "agent",
  79.     messagesKey: "messages",
  80.   });
  81.   return (
  82.     <div>
  83.       <div>
  84.         {thread.messages.map((message) => {
  85.           const meta = thread.getMessagesMetadata(message);
  86.           const parentCheckpoint = meta?.firstSeenState?.parent_checkpoint;
  87.           return (
  88.             <div key={message.id}>
  89.               <div>{message.content as string}</div>
  90.               {message.type === "human" && (
  91.                 <EditMessage
  92.                   message={message}
  93.                   onEdit={(message) =>
  94.                     thread.submit(
  95.                       { messages: [message] },
  96.                       { checkpoint: parentCheckpoint }
  97.                     )
  98.                   }
  99.                 />
  100.               )}
  101.               {message.type === "ai" && (
  102.                 <button
  103.                   type="button"
  104.                   onClick={() =>
  105.                     thread.submit(undefined, { checkpoint: parentCheckpoint })
  106.                   }
  107.                 >
  108.                   <span>Regenerate</span>
  109.                 </button>
  110.               )}
  111.               <BranchSwitcher
  112.                 branch={meta?.branch}
  113.                 branchOptions={meta?.branchOptions}
  114.                 onSelect={(branch) => thread.setBranch(branch)}
  115.               />
  116.             </div>
  117.           );
  118.         })}
  119.       </div>
  120.       <form
  121.         onSubmit={(e) => {
  122.           e.preventDefault();
  123.           const form = e.target as HTMLFormElement;
  124.           const message = new FormData(form).get("message") as string;
  125.           form.reset();
  126.           thread.submit({ messages: [message] });
  127.         }}
  128.       >
  129.         <input type="text" name="message" />
  130.         {thread.isLoading ? (
  131.           <button key="stop" type="button" onClick={() => thread.stop()}>
  132.             Stop
  133.           </button>
  134.         ) : (
  135.           <button key="submit" type="submit">
  136.             Send
  137.           </button>
  138.         )}
  139.       </form>
  140.     </div>
  141.   );
  142. }
复制代码
对于高级用例,你可以使用 experimental_branchTree 属性获取线程的树形表示,用于为非基于消息的图渲染分支控件。
乐观更新

你可以在向代理执行网络请求之前乐观更新客户端状态,从而为用户提供即时反馈,例如在代理看到请求之前立即显示用户消息。
  1. const stream = useStream({
  2.   apiUrl: "http://localhost:2024",
  3.   assistantId: "agent",
  4.   messagesKey: "messages",
  5. });
  6. const handleSubmit = (text: string) => {
  7.   const newMessage = { type: "human" as const, content: text };
  8.   stream.submit(
  9.     { messages: [newMessage] },
  10.     {
  11.       optimisticValues(prev) {
  12.         const prevMessages = prev.messages ?? [];
  13.         const newMessages = [...prevMessages, newMessage];
  14.         return { ...prev, messages: newMessages };
  15.       },
  16.     }
  17.   );
  18. };
复制代码
TypeScript

useStream() 钩子对 TypeScript 应用非常友好,你可以为状态指定类型,以获得更好的类型安全性和 IDE 支持。
  1. // 定义你的类型
  2. type State = {
  3.   messages: Message[];
  4.   context?: Record<string, unknown>;
  5. };
  6. // 在钩子中使用它们
  7. const thread = useStream<State>({
  8.   apiUrl: "http://localhost:2024",
  9.   assistantId: "agent",
  10.   messagesKey: "messages",
  11. });
复制代码
你还可以选择为不同场景指定类型,例如:
    ConfigurableType:config.configurable 属性的类型(默认:Record<string, unknown>)InterruptType:中断值的类型——即 interrupt(...) 函数的内容(默认:unknown)CustomEventType:自定义事件的类型(默认:unknown)UpdateType:提交函数的类型(默认:Partial<State>)
  1. const thread = useStream<
  2.   State,
  3.   {
  4.     UpdateType: {
  5.       messages: Message[] | Message;
  6.       context?: Record<string, unknown>;
  7.     };
  8.     InterruptType: string;
  9.     CustomEventType: {
  10.       type: "progress" | "debug";
  11.       payload: unknown;
  12.     };
  13.     ConfigurableType: {
  14.       model: string;
  15.     };
  16.   }
  17. >({
  18.   apiUrl: "http://localhost:2024",
  19.   assistantId: "agent",
  20.   messagesKey: "messages",
  21. });
复制代码
如果你使用 LangGraph.js,你还可以重用图的注解类型。但请确保仅导入注解模式的类型,以避免导入整个 LangGraph.js 运行时(即通过 import type { ... } 指令)。
  1. import {
  2.   Annotation,
  3.   MessagesAnnotation,
  4.   type StateType,
  5.   type UpdateType,
  6. } from "@langchain/langgraph/web";
  7. const AgentState = Annotation.Root({
  8.   ...MessagesAnnotation.spec,
  9.   context: Annotation<string>(),
  10. });
  11. const thread = useStream<
  12.   StateType<typeof AgentState.spec>,
  13.   { UpdateType: UpdateType<typeof AgentState.spec> }
  14. >({
  15.   apiUrl: "http://localhost:2024",
  16.   assistantId: "agent",
  17.   messagesKey: "messages",
  18. });
复制代码
事件处理

useStream() 钩子提供了多个回调选项,帮助你响应不同事件:
    onError:在发生错误时调用。onFinish:在流完成时调用。onUpdateEvent:在接收到更新事件时调用。onCustomEvent:在接收到自定义事件时调用。了解如何流式传输自定义事件,请参见流式传输指南。onMetadataEvent:在接收到包含运行 ID 和线程 ID 的元数据事件时调用。
Learn More
    JS/TS SDK Reference

总结

    集成机制:本指南展示了如何使用 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
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

发布主题
阅读排行更多+

Powered by Discuz! X3.4© 2001-2013 Discuz Team.( 京ICP备17022993号-3 )