Claude Code + GLM4.5 使用经验分享——以魔改LangChain-Deepagents-UI项目为例子

最近智谱zai推出了ClaudeCode的GLM4.5的套餐,其中Lite版仅需要每个月20元,非常便宜,本次我们就来试试通过ClaudeCode + GLM4.5来实现一个小项目。该项目fork自LangChain的DeepAgents-UI项目。

环境配置

IDE:采用windsurf+claude code(模型为GLM4.5)

后端 :之前实现的个人版的DeepAgent(github.com/TBice123123...) 采用Langgraph官方的API 风格进行后端API提供。 环境配置如下:

bash 复制代码
git clone https://github.com/TBice123123/deep-agent-langgraph.git

进入到主目录

bash 复制代码
cd deep-agent-langgraph

安装依赖

bash 复制代码
uv sync --dev

启动api server

bash 复制代码
uv run langgraph dev --host=localhost

前端:LangChain的DeepAgents-UI项目 环境配置如下:

bash 复制代码
git clone https://github.com/langchain-ai/deep-agents-ui.git

进入到主目录

bash 复制代码
cd deep-agents-ui

安装依赖

bash 复制代码
pnpm install

启动前端

bash 复制代码
pnpm dev

一切完成好后,开始第一步,我们先让claude code读取Readme文件,然后开始分析项目。

CC给的答复是:

接下来我们让它帮我们

接下来我们启动项目看看,发个你好,结果跨域了

bash 复制代码
Access to fetch at 'http://127.0.0.1:2024/threads/search' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'http://127.0.0.1' that is not equal to the supplied origin. Have the server send the header with a valid value.

于是我们把报错丢给cc,让它帮我们解决

然后我们允许其修改内容。

能够正常的回复:

当然我们可以问问他为啥要这样做

接下来我们完成一个简单的任务,实现一个温州的旅游指南吧。 于是我们遇到了下一个问题,子智能体的没有办法输出。

这大概是前端没有处理子智能体(子图)返回的内容。 因为如下图所示

确实没有显示任何子图的输出。

我们将这个问题问一下CC。 首先让它读取项目帮我们看看前端是怎么读取SSE流的,由于这个内容太长,就直接复制cc的回答了

bash 复制代码
的,下面开始读取文件,告诉我这个是怎么读取后端返
  回的SSE的?

● Task(Search for SSE implementation)
  ⎿  Done (22 tool uses · 32.0k tokens · 1m 33.0s

● 这个应用不是使用传统的SSE(Server-Sent
  Events),而是使用LangGraph
  SDK的内置流式传输功能。

  核心实现方式:

  1. 使用LangGraph SDK的useStream Hook

  // src\app\hooks\useChat.ts
  const stream = useStream<StateType>({
    assistantId: agentId,
    client: createClient(accessToken || ""),
    reconnectOnMount: true,
    threadId: threadId ?? null,
    onUpdateEvent: handleUpdateEvent,
    onThreadId: setThreadId,
    defaultHeaders: {
      "x-auth-scheme": "langsmith",
    },
  });

  2. 客户端配置

  // src\lib\client.ts
  export function createClient(accessToken:
  string) {
    const deployment = getDeployment();
    return new Client({
      apiUrl: deployment?.deploymentUrl || "",
      apiKey: accessToken,
      defaultHeaders: {
        "x-auth-scheme": "langsmith",
      },
    });
  }

  3. 消息发送和流式处理

  const sendMessage = useCallback(
    (message: string) => {
      const humanMessage: Message = {
        id: uuidv4(),
        type: "human",
        content: message,
      };
      stream.submit(
        { messages: [humanMessage] },
        {
          optimisticValues(prev) {
            const prevMessages = prev.messages ??
  [];
            const newMessages = [...prevMessages,
  humanMessage];
            return { ...prev, messages:
  newMessages };
          },
          config: {
            recursion_limit: 100,
          },
        },
      );
    },
    [stream],
  );

  4. 事件处理

  const handleUpdateEvent = useCallback(
    (data: { [node: string]: Partial<StateType> })
   => {
      Object.entries(data).forEach(([_, nodeData])
   => {
        if (nodeData?.todos) {
          onTodosUpdate(nodeData.todos);
        }
        if (nodeData?.files) {
          onFilesUpdate(nodeData.files);
        }
      });
    },
    [onTodosUpdate, onFilesUpdate],
  );

  关键区别:

  - 传统SSE:使用EventSource或手动fetch流式处理
  - LangGraph SDK:使用封装好的useStream
  Hook,SDK内部处理底层通信

  SDK自动管理连接、重连、消息格式化和状态同步,开
  发者只需关注业务逻辑。

我们追随CC的思路看一下这部分代码 然后很尴尬的一点,我没找到第四点位于的地方,于是我又问CC。

bash 复制代码
第四点handleUpdateEvent函数在第35-47行:

  const handleUpdateEvent = useCallback(
    (data: { [node: string]: Partial<StateType> })
   => {
      Object.entries(data).forEach(([_, nodeData])
   => {
        if (nodeData?.todos) {
          onTodosUpdate(nodeData.todos);
        }
        if (nodeData?.files) {
          onFilesUpdate(nodeData.files);
        }
      });
    },
    [onTodosUpdate, onFilesUpdate],
  );

  这个函数在useStream配置中使用(第54行),用于处
  理后端返回的更新事件,提取todos和files数据并更新
  UI。

接下里我们让CC打印后端返回的所以SSE流。

虽然是打印了但是明显不是我们想要的

于是CC又改了一下:

不出意外它这次幻觉了。当然这个原因可能是它对langgraph server不了解,于是我们使用context7 mcp让其获取更多的上下文。 经过一些试错,它终于勉强的添加对了位置,但是名称写错了(最终是windsurf tab纠正了,有一定可能是因为它把python sdk的搞混了。)(不过已经很不错,换成我找估计要好久,这个没有花太多时间,大概2-3轮就基本上解决了)

子图的内容终于输出了

接下来我们让它修改左侧的Tasks和Files没有办法显示的问题 它的回答如下:

bash 复制代码
● 现在我明白了!我看到在 page.tsx 中,ChatInterface
  组件接收了 onTodosUpdate 和 onFilesUpdate
  回调函数,这些函数应该会被 SSE 数据触发。让我查看
  ChatInterface 组件是如何使用这些回调的:

● Read(src\app\components\ChatInterface\ChatInterface.tsx)
  ⎿  Read 50 lines (ctrl+r to expand)

● Read(src\app\components\ChatInterface\ChatInterface.tsx)
  ⎿  Read 30 lines (ctrl+r to expand)

● 现在我理解了数据流。让我创建一个计划来分析并修复 Tasks
  和 Files 的更新问题:

● 现在我来分析 SSE 数据结构和处理逻辑。让我重新检查
  useChat.ts 中的 handleUpdateEvent 函数:

● Read(src\app\hooks\useChat.ts)
  ⎿  Read 20 lines (ctrl+r to expand)

● 现在让我查看官方示例中如何处理这些数据更新:

● context7 - get-library-docs (MCP)(context7CompatibleLibrary
                                   ID:
                                   "/langchain-ai/langgraph",
                                    topic: "useStream
                                   onUpdateEvent todos files
                                   example", tokens: 10000)
  ⎿ ⚠ Large MCP response (~13.6k tokens), this can fill
  ⎿  ========================
     CODE SNIPPETS
     ========================
     ... +2121 lines (ctrl+r to expand)

● 基于我的分析,问题在于数据处理逻辑。现在让我修复
  useChat.ts 中的 handleUpdateEvent 函数,使其能正确处理
  SSE 数据并更新 Tasks 和 Files:

● Update(src\app\hooks\useChat.ts)
  ⎿  Updated src\app\hooks\useChat.ts with 30 additions
     and 3 removals
       35      const handleUpdateEvent = useCallback(
       36        (data: { [node: string]:
            Partial<StateType> }) => {
       37          console.log("SSE流数据:", data);
       38 -        Object.entries(data).forEach(([_,
          - nodeData]) => {
       39 -          if (nodeData?.todos) {
       38 +
       39 +        // 遍历所有节点数据
       40 +
          + Object.entries(data).forEach(([nodeName,
          + nodeData]) => {
       41 +          console.log(`节点 ${nodeName}
          + 数据:`, nodeData);
       42 +
       43 +          // 处理 todos 数据
       44 +          if (nodeData?.todos &&
          + Array.isArray(nodeData.todos)) {
       45 +            console.log(`发现 todos
          + 数据,数量: ${nodeData.todos.length}`);
       46              onTodosUpdate(nodeData.todos);
       47            }
       48 -          if (nodeData?.files) {
       48 +
       49 +          // 处理 files 数据
       50 +          if (nodeData?.files && typeof
          + nodeData.files === 'object') {
       51 +            console.log(`发现 files
          + 数据,键数量:
          + ${Object.keys(nodeData.files).length}`);
       52              onFilesUpdate(nodeData.files);
       53            }
       54 +
       55 +          // 处理子图数据 -
          + 子图数据可能嵌套在更深的结构中
       56 +          if (nodeData && typeof nodeData
          + === 'object') {
       57 +            // 检查是否有嵌套的子图数据结构
       58 +
          + Object.values(nodeData).forEach((value:
          + any) => {
       59 +              if (value && typeof value ===
          +  'object') {
       60 +                // 检查是否有 todos 或
          + files
       61 +                if (value?.todos &&
          + Array.isArray(value.todos)) {
       62 +                  console.log(`发现嵌套
          + todos 数据,数量: ${value.todos.length}`);
       63 +
          + onTodosUpdate(value.todos);
       64 +                }
       65 +                if (value?.files && typeof
          + value.files === 'object') {
       66 +                  console.log(`发现嵌套
          + files 数据,键数量:
          + ${Object.keys(value.files).length}`);
       67 +
          + onFilesUpdate(value.files);
       68 +                }
       69 +              }
       70 +            });
       71 +          }
       72          });
       73        },
       74        [onTodosUpdate, onFilesUpdate],

● 已经修复了数据处理逻辑!现在 handleUpdateEvent
  函数能够:

  1. 详细日志记录:记录 SSE
  流数据和每个节点的数据,便于调试
  2. 类型安全检查:确保 todos 是数组类型,files
  是对象类型
  3. 嵌套数据处理:支持处理子图数据中可能存在的嵌套结构
  4. 全面的数据扫描:遍历所有可能的嵌套结构来查找 todos
  和 files 数据

  主要改进点:

  - 添加了详细的 console.log 来帮助调试 SSE 数据结构
  - 增加了类型检查 (Array.isArray 和 typeof) 防止错误更新
  - 支持递归查找嵌套的 todos 和 files 数据
  - 保持了原有的子图流式传输配置 (streamSubgraphs: true)

  现在 Tasks 和 Files 选项卡应该能够正确显示从 SSE
  返回的数据了。你可以在浏览器控制台中查看详细的日志,了
  解数据是如何被处理和更新的。

将其给的代码应用后,控制台可以正常捕获tools的数据

我们把这个有用的内容给到CC并提示它只有tools节点才会更新todo 顺利更新

当然这里有点问题不过这个不是cc的问题是因为我这里和官方不一样完成的是done。

bash 复制代码
> 可以实现todo的更新,但是还是有问题,这里可能是我的问题我的实现和官
  方不太一样,我的todo完成的时候是done,官方的是compeleted。请帮我改
  一下前端代码?

● Read(src\app\components\TasksFilesSidebar\TasksFilesSidebar.tsx)
  ⎿  Read 173 lines (ctrl+r to expand)

● Update(src\app\components\TasksFilesSidebar\TasksFilesSidebar.tsx)
  ⎿  Updated
     src\app\components\TasksFilesSidebar\TasksFilesSidebar.tsx
     with 2 additions and 2 removals
       29      ({ todos, files, onFileClick, collapsed,
            onToggleCollapse }) => {
       30        const getStatusIcon = useCallback((status:
            TodoItem["status"]) => {
       31          switch (status) {
       32 -          case "completed":
       32 +          case "done":
       33              return <CheckCircle size={16}
            className={styles.completedIcon} />;
       34            case "in_progress":
       35              return <Clock size={16}
            className={styles.progressIcon} />;
     ...
       42          return {
       43            pending: todos.filter((t) => t.status ===
            "pending"),
       44            in_progress: todos.filter((t) => t.status
            === "in_progress"),
       45 -          completed: todos.filter((t) => t.status ===
          -   "completed"),
       45 +          completed: todos.filter((t) => t.status ===
          +   "done"),
       46          };
       47        }, [todos]);
       48

● 已经修改完成。现在前端的 TasksFilesSidebar 组件会正确处理你的
  "done" 状态:

  1. getStatusIcon 函数现在会将 "done" 状态显示为完成图标
  2. groupedTodos 现在会将状态为 "done" 的 todo 项目归类到已完成组

  这样你的应用就能正确显示 todo 项目的完成状态了。

我们惊喜的发现它确实改的是正确的。

bash 复制代码
看到 write_note 节点输出的数据结构了。现在需要修改 useChat.ts
  来处理 write_note 节点中的文件数据。

● Update(src\app\hooks\useChat.ts
        )
  ⎿  Updated
     src\app\hooks\useChat.ts
      with 12 additions and 1
      removal
       36        (data: {
            [node:
            string]: Parti
            al<StateType>
            }) => {
       37
            console.log("S
            SE流数据:",
            data);
       38
       39 -        // 专门
          -  处理 tools
          -  节点的数据
       39 +        //
          +  处理 tools
          +  节点的数据
       40          if
            (data.tools) {
       41            conso
            le.log("tools
            节点数据:",
            data.tools);
       42
     ...
       52
            onFilesUpdate(
            data.tools.fil
            es);
       53            }
       54          }
       55 +
       56 +        // 处理
          +  write_note
          + 节点的数据
       57 +        if (dat
          + a.write_note)
          + {
       58 +
          + console.log("w
          + rite_note
          + 节点数据:",
          + data.write_not
          + e);
       59 +
       60 +          //
          + 处理 note 数据
       61 +          if
          + (data.write_no
          + te?.note &&
          + typeof data.wr
          + ite_note.note
          + === 'object')
          + {
       62 +            con
          + sole.log(`发现
          +  note
          + 数据,键数量:
          + ${Object.keys(
          + data.write_not
          + e.note).length
          + }`);
       63 +
          + onFilesUpdate(
          + data.write_not
          + e.note);
       64 +          }
       65 +        }
       66        },
       67        [onTodosU
            pdate, onFiles
            Update],
       68      );

● 已经修改完成。现在代码会同时处理:

  1. tools 节点:处理 todo 数组和 files 对象
  2. write_note 节点:处理 note
  对象(包含温州旅游攻略和江心屿上午行程等文件)

  根据你提供的控制台输出,write_note 节点的 note
  对象包含了两个文件:
  - 温州旅游攻略
  - 温州江心屿上午行程

  这些文件现在应该会正确显示在 Files 标签页中。

这一次cc改的也是对的。

跟官方的效果基本上一样。

至此官方的deepagent-ui魔改完成。

相关推荐
三翼鸟数字化技术团队13 小时前
大模型应用开发框架 LangChain
langchain·llm
RainbowSea17 小时前
6. LangChain4j + 多模态视觉理解详细说明
langchain·llm·ai编程
RainbowSea17 小时前
6. LangChain4j + 流式输出详细说明
langchain·llm·ai编程
玲小珑20 小时前
LangChain.js 完全开发手册(五)Runnable 接口与任务编排系统
前端·langchain·ai编程
AI Echoes1 天前
LangGraph 重要注意事项和常见问题
人工智能·python·langchain·agent
coder_pig1 天前
👦抠腚男孩的AI学习之旅 | 5、玩转 LangChain (一)
langchain·aigc·ai编程
AI Echoes1 天前
一款为开发者而生的开源全栈LLMOps平台
人工智能·python·langchain·agent
ILUUSION_S2 天前
ReactAgent接入MCP服务工具
python·langchain