主题
待办列表
用与智能体状态实时同步的待办列表跟踪智能体进度
并非每次智能体交互都是聊天。有时智能体正在执行 一个多步计划,展示进度的最佳方式是实时更新的 待办列表。深度智能体待办列表模式直接从智能体的状态读取 todos 数组,在智能体推进其计划的过程中, 渲染每个条目及其当前状态。它构建于你用于聊天的相同 useStream 钩子之上,是一个进度仪表板。它表明智能体状态可以驱动任何 UI, 而不仅仅是消息气泡。
在线演示
此处为官方文档中的交互式演示(PatternEmbed),静态站点无法呈现。请前往官方原文查看。
工作原理
当你选择启用 TodoListMiddleware 时,深度智能体可以暴露一个 todos 状态通道。该 middleware 添加 write_todos 工具,并在智能体推进其计划的过程中持久化任务进度。随着智能体执行,它会将每个 待办的状态从 "pending" 更新为 "in_progress",再更新为 "completed"。 useStream 钩子通过 stream.values.todos 暴露该状态,你的 UI 会响应式地渲染它。
NOTE
任务规划是选择性启用的。没有 TodoListMiddleware,stream.values.todos 就不存在。请参阅任务规划。
流程如下:
- 用户提交请求
- 智能体创建计划并在其状态中填充
todos - 智能体开始执行,每个待办会经历
pending→in_progress→completed的转换 - 随着智能体推进,
stream.values.todos会实时更新 - 你的 UI 使用当前状态重新渲染待办列表
设置 useStream
在智能体上启用 TodoListMiddleware。
python
from deepagents import create_deep_agent
from langchain.agents.middleware import TodoListMiddleware
agent = create_deep_agent(
model="google_genai:gemini-3.6-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-5",
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()],
)然后将 useStream 指向该智能体,并 从 stream.values 读取 todos。
INFO
代码示例使用 useStream<typeof myAgent> 来实现类型安全的流状态。类型推断请参阅 Python 或 JavaScript 后端。
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 (
<div>
<TodoList todos={todos} />
{stream.messages.map((msg) => (
<Message key={msg.id} message={msg} />
))}
</div>
);
}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>
<div>
<TodoList :todos="todos" />
<Message
v-for="msg in stream.messages.value"
:key="msg.id"
:message="msg"
/>
</div>
</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>
<div>
<TodoList {todos} />
{#each stream.messages as msg (msg.id)}
<Message message={msg} />
{/each}
</div>ts
import { Component, computed } from "@angular/core";
import { injectStream } from "@langchain/angular";
const AGENT_URL = "http://localhost:2024";
@Component({
selector: "app-todo-agent",
template: `
<div>
<app-todo-list [todos]="todos()" />
@for (msg of stream.messages(); track msg.id) {
<app-message [message]="msg" />
}
</div>
`,
})
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 (
<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 Progress</h2>
<span className="text-sm text-gray-500">
{completed}/{todos.length} tasks
</span>
</div>
<ProgressBar percentage={percentage} />
<ul className="mt-4 space-y-2">
{todos.map((todo, i) => (
<TodoItem key={i} todo={todo} />
))}
</ul>
</div>
);
}进度条
可视化的进度条让用户一眼就能了解整体完成情况:
tsx
function ProgressBar({ percentage }: { percentage: number }) {
return (
<div className="space-y-1">
<div className="flex items-center justify-between text-xs text-gray-500">
<span>Progress</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>
);
}单个待办条目
每个条目都会获得状态图标、颜色编码文本,以及针对 已完成任务的删除线样式:
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}`}
>
<span className={`mt-0.5 text-lg leading-none ${style.iconClass}`}>
{style.icon}
</span>
<span className={`text-sm ${style.textClass}`}>{todo.content}</span>
</li>
);
}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 (
<div className="flex h-screen flex-col">
{todos.length > 0 && (
<div className="border-b bg-gray-50 p-4">
<TodoList todos={todos} />
</div>
)}
<main className="flex-1 overflow-y-auto p-6">
<div className="mx-auto max-w-2xl space-y-4">
{stream.messages.map((msg) => (
<Message key={msg.id} message={msg} />
))}
</div>
</main>
<ChatInput
onSubmit={(text) =>
stream.submit({ messages: [{ type: "human", content: text }] })
}
isLoading={stream.isLoading}
/>
</div>
);
}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 (
<div className="rounded-lg border bg-white p-4 shadow-sm">
<div className="flex items-center gap-2 text-sm text-gray-500">
<span className="animate-spin">⟳</span>
Agent is creating a plan...
</div>
</div>
);
}
return (
<div className="rounded-lg border bg-white p-4 shadow-sm">
{/* ... full todo list rendering */}
</div>
);
}最佳实践
- 突出显示待办列表。它是基于计划的智能体的主要进度指示器。 不要把它埋在首屏下方。
- 为状态转换添加动画。平滑的过渡让智能体感觉更 灵敏。对背景颜色、文本装饰和 不透明度使用 CSS 过渡。
- 只高亮一个
in_progress条目。智能体通常一次只处理一个 任务。如果多个条目显示为in_progress,UI 会变得嘈杂。 考虑只让第一个产生脉冲动画。 - 折叠或淡化已完成的条目。随着列表增长,已完成的条目 变得不那么相关。降低它们的视觉权重,让用户专注于仍 在发生的事情。
- 显示进度百分比。像“67% complete”这样的单个数字 即使隔着房间也能一眼看懂。
- 保持待办列表同步。由于
stream.values会响应式更新, 待办列表会自动保持最新。不要添加手动轮询或 刷新逻辑。
相关 LangChain 指南
待办列表模式是更广泛的 LangChain 结构化智能体状态渲染 方法的一种特化。这些指南涵盖了与基于计划的智能体 搭配良好的相关技术:
Structured output
使用自定义 UI 组件而非纯文本渲染任何结构化智能体状态——而不仅仅是待办。
Join & rejoin streams
在页面重新加载或标签页切换后重新连接到正在运行的计划,而不丢失进度。
Message queues
在智能体仍在处理其当前计划时排队后续任务。
Human-in-the-Loop
在智能体需要用户批准或输入时暂停并恢复计划执行。