Skip to content

并非每次智能体交互都是聊天。有时智能体正在执行一个 多步骤计划,而展示进度的最佳方式是一个实时更新待办列表。 深度智能体待办列表模式直接从智能体的状态中读取 todos 数组, 在智能体逐步执行其计划时渲染每个带有当前状态的项目。它是一个 构建在与聊天所用相同的 useStream 钩子之上的进度仪表盘。它表明 智能体状态可以为任何界面提供动力,而不仅仅是消息气泡。

import { PatternEmbed } from "/snippets/pattern-embed.jsx"

工作原理

当您启用 TodoListMiddleware 时,深度智能体可以暴露一个 todos 状态通道。 该中间件会添加 write_todos 工具,并在智能体执行其计划时持久化任务进度。 随着智能体的执行,它会将每个待办事项的状态从 "pending" 更新为 "in_progress",再更新为 "completed"useStream 钩子通过 stream.values.todos 暴露此状态,您的界面以响应式方式 渲染它。

INFO

任务规划是可选的。如果没有 TodoListMiddlewarestream.values.todos 将不存在。请参阅任务规划

流程如下:

  1. 用户提交请求
  2. 智能体创建计划并在其状态中填充 todos
  3. 智能体开始执行,每个待办事项的状态经过 pendingin_progresscompleted 的转换
  4. 随着智能体的推进,stream.values.todos 实时更新
  5. 您的界面使用当前状态重新渲染待办列表

设置 useStream

在智能体上启用 TodoListMiddleware

python
from deepagents import create_deep_agent
from langchain.agents.middleware import TodoListMiddleware

agent = create_deep_agent(
    model="google_genai:gemini-3.5-flash",
    middleware=[TodoListMiddleware()],
)
python
from deepagents import create_deep_agent
from langchain.agents.middleware import TodoListMiddleware

agent = create_deep_agent(
    model="openai:gpt-5.5",
    middleware=[TodoListMiddleware()],
)
python
from deepagents import create_deep_agent
from langchain.agents.middleware import TodoListMiddleware

agent = create_deep_agent(
    model="anthropic:claude-sonnet-4-6",
    middleware=[TodoListMiddleware()],
)
python
from deepagents import create_deep_agent
from langchain.agents.middleware import TodoListMiddleware

agent = create_deep_agent(
    model="openrouter:z-ai/glm-5.2",
    middleware=[TodoListMiddleware()],
)
python
from deepagents import create_deep_agent
from langchain.agents.middleware import TodoListMiddleware

agent = create_deep_agent(
    model="fireworks:accounts/fireworks/models/glm-5p2",
    middleware=[TodoListMiddleware()],
)
python
from deepagents import create_deep_agent
from langchain.agents.middleware import TodoListMiddleware

agent = create_deep_agent(
    model="baseten:zai-org/GLM-5.2",
    middleware=[TodoListMiddleware()],
)
python
from deepagents import create_deep_agent
from langchain.agents.middleware import TodoListMiddleware

agent = create_deep_agent(
    model="ollama:north-mini-code-1.0",
    middleware=[TodoListMiddleware()],
)
ts
import { createDeepAgent } from "deepagents";
import { todoListMiddleware } from "langchain";

const agent = await createDeepAgent({
  model: "google-genai:gemini-3.5-flash",
  middleware: [todoListMiddleware()],
});
ts
import { createDeepAgent } from "deepagents";
import { todoListMiddleware } from "langchain";

const agent = await createDeepAgent({
  model: "openai:gpt-5.5",
  middleware: [todoListMiddleware()],
});
ts
import { createDeepAgent } from "deepagents";
import { todoListMiddleware } from "langchain";

const agent = await createDeepAgent({
  model: "anthropic:claude-sonnet-4-6",
  middleware: [todoListMiddleware()],
});
ts
import { createDeepAgent } from "deepagents";
import { todoListMiddleware } from "langchain";

const agent = await createDeepAgent({
  model: "openrouter:openrouter:z-ai/glm-5.2",
  middleware: [todoListMiddleware()],
});
ts
import { createDeepAgent } from "deepagents";
import { todoListMiddleware } from "langchain";

const agent = await createDeepAgent({
  model: "fireworks:accounts/fireworks/models/glm-5p2",
  middleware: [todoListMiddleware()],
});
ts
import { createDeepAgent } from "deepagents";
import { todoListMiddleware } from "langchain";

const agent = await createDeepAgent({
  model: "baseten:zai-org/GLM-5.2",
  middleware: [todoListMiddleware()],
});
ts
import { createDeepAgent } from "deepagents";
import { todoListMiddleware } from "langchain";

const agent = await createDeepAgent({
  model: "ollama:north-mini-code-1.0",
  middleware: [todoListMiddleware()],
});

然后将 useStream 指向该智能体,并 从 stream.values 读取 todos

INFO

The code examples use useStream<typeof myAgent> for type-safe stream state. See Type inference for Python or JavaScript backends.

tsx
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 (
      <TodoList todos={todos} />
      {stream.messages.map((msg) => (
        <Message key={msg.id} message={msg} />
      ))}
  );
}
vue
<script setup lang="ts">
import { useStream } from "@langchain/vue";
import { computed } from "vue";

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

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

const todos = computed(() => stream.values.value?.todos ?? []);
</script>

<template>
    <TodoList :todos="todos" />
    <Message
      v-for="msg in stream.messages.value"
      :key="msg.id"
      :message="msg"
    />
</template>
svelte
<script lang="ts">
  import { useStream } from "@langchain/svelte";

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

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

  const todos = $derived(stream.values?.todos ?? []);
</script>

  <TodoList {todos} />
  {#each stream.messages as msg (msg.id)}
    <Message message={msg} />
  {/each}
ts
import { Component, computed } from "@angular/core";
import { injectStream } from "@langchain/angular";

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

@Component({
  selector: "app-todo-agent",
  template: `
      <app-todo-list [todos]="todos()" />
      @for (msg of stream.messages(); track msg.id) {
        <app-message [message]="msg" />
      }
  `,
})
export class TodoAgentComponent {
  stream = injectStream<typeof myAgent>({
    apiUrl: AGENT_URL,
    assistantId: "deep_agent_todo_list",
  });

  todos = computed(() => this.stream.values()?.todos ?? []);
}

构建 TodoList 组件

待办列表使用状态图标、颜色编码和反映当前状态的可视化 样式渲染每个项目:

tsx
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 (
        <h2 className="text-lg font-semibold">Agent Progress</h2>
          {completed}/{todos.length} tasks

      <ProgressBar percentage={percentage} />

        {todos.map((todo, i) => (
          <TodoItem key={i} todo={todo} />
        ))}
  );
}

进度条

可视化进度条让用户一眼就能了解整体完成情况:

tsx
function ProgressBar({ percentage }: { percentage: number }) {
  return (
        Progress
        {percentage}%
        <div
          className="h-full rounded-full bg-green-500 transition-all duration-500"
          style={{ width: `${percentage}%` }}
        />
  );
}

单个待办事项

每个项目都会获得状态图标、颜色编码的文本,以及针对 已完成任务的删除线样式:

tsx
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}`}
    >
        {style.icon}
      {todo.content}
  );
}

in_progress 图标使用 animate-pulse 来引起对当前 活跃任务的注意。

计算进度

直接从 todos 数组派生进度指标:

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

const completed = todos.filter((t) => t.status === "completed").length;
const inProgress = todos.filter((t) => t.status === "in_progress").length;
const pending = todos.filter((t) => t.status === "pending").length;
const percentage = todos.length
  ? Math.round((completed / todos.length) * 100)
  : 0;

当智能体修改其状态时,这些值会响应式更新,使 进度条和计数器保持同步。

与聊天消息结合

待办列表与常规聊天界面并行工作。一种实用的布局 是将待办列表显示为持久化侧边栏或头部面板,聊天消息 位于其下方:

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

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

  return (
      {todos.length > 0 && (
          <TodoList todos={todos} />
      )}

      <main className="flex-1 overflow-y-auto p-6">
          {stream.messages.map((msg) => (
            <Message key={msg.id} message={msg} />
          ))}
      </main>

      <ChatInput
        onSubmit={(text) =>
          stream.submit({ messages: [{ type: "human", content: text }] })
        }
        isLoading={stream.isLoading}
      />
  );
}

TIP

只在 todos.length > 0 时显示待办列表。在智能体创建其 计划之前,没有内容可显示。显示一个空的组件会浪费空间。

使用场景

待办列表模式适用于智能体执行结构化计划的任何场景:

  • 项目规划:智能体将项目拆分为任务,并按顺序逐个 完成
  • 研究工作流:每个研究问题都成为一个待办事项,由智能体 调查并完成
  • 数据处理:摄取、验证、转换和导出等步骤 各自拥有自己的待办事项
  • 引导流程:智能体逐步完成设置步骤,在配置服务时 逐项勾选
  • 报告生成:报告的各部分成为待办事项:收集数据、 分析趋势、撰写摘要、格式化输出

处理空状态和加载状态

处理智能体创建计划之前的初始状态:

tsx
function TodoList({ todos, isLoading }: { todos: Todo[]; isLoading: boolean }) {
  if (todos.length === 0 && !isLoading) {
    return null;
  }

  if (todos.length === 0 && isLoading) {
    return (

          Agent is creating a plan...
    );
  }

  return (
      {/* ... full todo list rendering */}
  );
}

最佳实践

  • 突出显示待办列表。它是基于计划的智能体的主要进度指示器。 不要把它藏在首屏之下。
  • 为状态转换添加动画。平滑的转换让智能体感觉 响应更快。在背景颜色、文本装饰和 不透明度上使用 CSS 过渡。
  • 只突出一个 in_progress 项目。智能体通常一次只处理一个任务。 如果多个项目显示为 in_progress,界面会变得杂乱。 可以考虑只让第一个脉冲闪烁。
  • 折叠或淡化已完成的项目。随着列表变长,已完成的项目 变得不那么相关。降低它们的视觉权重,让用户专注于 仍在进行的内容。
  • 显示进度百分比。像 “67% 已完成” 这样的单个数字 即使隔着房间也能立刻理解。
  • 保持待办列表同步。因为 stream.values 会响应式更新, 待办列表会自动保持最新状态。不要添加手动轮询或 刷新逻辑。