并非每个 Agent 交互都是聊天。有时 Agent 正在执行多步骤计划,显示进度的最佳方式是待办事项列表,它会实时更新。深度 Agent 待办事项列表模式直接从 Agent 的状态中读取 todos 数组,在 Agent 执行计划时渲染每个项目及其当前状态。这是一个基于您用于聊天的相同 useStream 钩子构建的进度仪表板。它表明 Agent 状态可以为任何 UI 提供支持,而不仅仅是消息气泡。

工作原理

在 LangGraph Agent 中,状态不限于消息。您可以定义自定义状态键来保存任意数据。在这种情况下,是一个 todos 数组。当 Agent 执行计划时,它会更新每个待办事项的状态从 "pending""in_progress" 再到 "completed"useStream 钩子通过 stream.values 暴露这些自定义状态值,您的 UI 会响应式渲染它们。 流程如下:
  1. 用户提交请求
  2. Agent 创建一个计划并在其状态中填充 todos
  3. Agent 开始执行每个待办事项,过渡状态从 pendingin_progresscompleted
  4. stream.values.todos 在 Agent 进展时实时更新
  5. 您的 UI 使用当前状态重新渲染待办事项列表

设置 useStream

无需特殊配置。将 useStream 指向您的 Agent 并从 stream.values 读取 todos 定义一个与 Agent 状态模式匹配的 TypeScript 接口,并将其作为类型参数传递给 useStream,以便类型安全地访问状态值,包括像 todos 这样的自定义状态键。在下面的示例中,将 typeof myAgent 替换为您的接口名称:
import type { BaseMessage } from "@langchain/core/messages";

interface TodoItem {
  title: string;
  status: "pending" | "in_progress" | "completed";
  description?: string;
}

interface AgentState {
  messages: BaseMessage[];
  todos: TodoItem[];
}
import { useStream } from "@langchain/react";

const AGENT_URL = "http://localhost:2024";

export function TodoAgent() {
  const stream = useStream<typeof myAgent>({
    apiUrl: AGENT_URL,
    assistantId: "deep_agent_todo_list",
  });

  const todos = stream.values?.todos ?? [];

  return (
    <div>
      <TodoList todos={todos} />
      {stream.messages.map((msg) => (
        <Message key={msg.id} message={msg} />
      ))}
    </div>
  );
}

Todo 接口

数组中的每个待办事项都有简单的结构:
interface Todo {
  status: "pending" | "in_progress" | "completed";
  content: string;
}
属性描述
status此任务的当前状态。选项:pending(未开始)、in_progress(Agent 正在处理)、completed(已完成)
content任务内容的人类可读描述
Agent 在创建计划时填充此数组,然后在执行每个步骤时更新各个项目。

构建 TodoList 组件

待办事项列表使用状态图标、颜色编码和反映当前状态的可视样式渲染每个项目:
function TodoList({ todos }: { todos: Todo[] }) {
  const completed = todos.filter((t) => t.status === "completed").length;
  const percentage = todos.length
    ? Math.round((completed / todos.length) * 100)
    : 0;

  return (
    <div className="rounded-lg border bg-white p-4 shadow-sm">
      <div className="mb-4 flex items-center justify-between">
        <h2 className="text-lg font-semibold">Agent 进度</h2>
        <span className="text-sm text-gray-500">
          {completed}/{todos.length} 个任务
        </span>
      </div>

      <ProgressBar percentage={percentage} />

      <ul className="mt-4 space-y-2">
        {todos.map((todo, i) => (
          <TodoItem key={i} todo={todo} />
        ))}
      </ul>
    </div>
  );
}

进度条

可视化进度条为用户提供整体完成情况的一目了然摘要:
function ProgressBar({ percentage }: { percentage: number }) {
  return (
    <div className="space-y-1">
      <div className="flex items-center justify-between text-xs text-gray-500">
        <span>进度</span>
        <span>{percentage}%</span>
      </div>
      <div className="h-2 overflow-hidden rounded-full bg-gray-200">
        <div
          className="h-full rounded-full bg-green-500 transition-all duration-500"
          style={{ width: `${percentage}%` }}
        />
      </div>
    </div>
  );
}

单个待办事项

每个项目获得状态图标、颜色编码文本和已完成任务的删除线样式:
function TodoItem({ todo }: { todo: Todo }) {
  const config = {
    pending: {
      icon: "○",
      textClass: "text-gray-600",
      bgClass: "bg-gray-50",
      iconClass: "text-gray-400",
    },
    in_progress: {
      icon: "◉",
      textClass: "text-amber-800",
      bgClass: "bg-amber-50 border-amber-200",
      iconClass: "text-amber-500 animate-pulse",
    },
    completed: {
      icon: "✓",
      textClass: "text-green-800 line-through",
      bgClass: "bg-green-50 border-green-200",
      iconClass: "text-green-500",
    },
  };

  const style = config[todo.status];

  return (
    <li
      className={`flex items-start gap-3 rounded-md border px-3 py-2 ${style.bgClass}`}
    >
      <span className={`mt-0.5 text-lg leading-none ${style.iconClass}`}>
        {style.icon}
      </span>
      <span className={`text-sm ${style.textClass}`}>{todo.content}</span>
    </li>
  );
}