并非每次代理交互都是聊天。有时代理正在执行一个多步骤计划,而显示进度的最佳方式是一个待办事项列表,它会实时更新。深度代理待办事项列表模式直接从代理的状态中读取 todos 数组,在代理处理其计划时,根据当前状态渲染每个项目。这是一个基于与聊天相同的 useStream 钩子构建的进度仪表板。它表明代理状态可以驱动任何 UI,而不仅仅是消息气泡。
工作原理
在 LangGraph 代理中,状态不仅限于消息。您可以定义自定义状态键来保存任意数据。在这种情况下,是一个 todos 数组。当代理执行其计划时,它会将每个待办事项的状态从 "pending" 更新为 "in_progress" 再到 "completed"。useStream 钩子通过 stream.values 暴露这些自定义状态值,您的 UI 会以响应式方式渲染它们。
流程如下所示:
- 用户提交请求
- 代理创建计划并在其状态中填充
todos
- 代理开始执行每个待办事项,状态从
pending 转换为 in_progress 再到 completed
- 随着代理的进展,
stream.values.todos 实时更新
- 您的 UI 使用当前状态重新渲染待办事项列表
设置 useStream
无需特殊配置。将 useStream 指向您的代理,并从 stream.values 读取 todos。
导入您的代理并将 typeof myAgent 作为类型参数传递给 useStream,以实现对状态值的类型安全访问:
import type { myAgent } from "./agent";
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>
);
}
待办事项接口
数组中的每个待办事项都有一个简单的结构:
interface Todo {
status: "pending" | "in_progress" | "completed";
content: string;
}
| 属性 | 描述 |
|---|
status | 此任务的当前状态。选项:pending(未开始)、in_progress(代理正在处理)、completed(已完成) |
content | 任务涉及内容的可读描述 |
代理在创建计划时填充此数组,然后在执行每个步骤时更新各个项目。
构建 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">代理进度</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>
);
}
in_progress 图标使用 animate-pulse 来吸引对当前活动任务的注意。
计算进度
直接从待办事项数组派生进度指标:
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;
这些值在代理修改其状态时以响应式方式更新,保持进度条和计数器同步。
与聊天消息结合
待办事项列表与常规聊天界面协同工作。实用的布局将待办事项列表显示为持久的侧边栏或标题面板,聊天消息显示在下方:
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>
);
}
仅当 todos.length > 0 时显示待办事项列表。在代理创建计划之前,没有内容可显示。显示空组件会浪费空间。
超越待办事项的自定义状态
此模式展示了一个强大的原则:stream.values 可以暴露代理定义的任何自定义状态,而不仅仅是消息。todos 数组只是一个例子。您可以将相同的方法用于:
- 进度指标:包含数字完成数据的
stream.values.progress
- 生成的工件:包含代理正在构建的结构化文档的
stream.values.document
- 决策日志:跟踪代理所做的每个选择的
stream.values.decisions
- 资源列表:包含代理找到的链接和引用的
stream.values.sources
// 代理定义的任何自定义状态键都可访问
const document = stream.values?.document;
const sources = stream.values?.sources ?? [];
const confidence = stream.values?.confidence_score;
自定义状态键在您的 LangGraph 图的状态模式中定义。useStream 钩子会自动将它们包含在 stream.values 中,无需任何额外的客户端配置。
动画过渡
待办事项状态转换是实时发生的,平滑的动画使这些变化感觉精致而不是突兀:
function TodoItem({ todo }: { todo: Todo }) {
return (
<li
className={`
flex items-start gap-3 rounded-md border px-3 py-2
transition-all duration-300 ease-in-out
${getStatusStyles(todo.status)}
`}
>
<span
className={`
mt-0.5 text-lg leading-none transition-colors duration-300
${getIconStyles(todo.status)}
`}
>
{getStatusIcon(todo.status)}
</span>
<span
className={`
text-sm transition-all duration-300
${todo.status === "completed" ? "line-through opacity-60" : ""}
`}
>
{todo.content}
</span>
</li>
);
}
transition-all duration-300 类确保颜色变化、删除线和不透明度变化都能平滑动画。
待办事项列表模式适用于代理执行结构化计划的任何场景:
- 项目规划:代理将项目分解为任务并按顺序处理它们
- 研究工作流:每个研究问题成为一个待办事项,代理调查并完成它
- 数据处理:步骤如摄取、验证、转换和导出各自获得自己的待办事项
- 入职流程:代理逐步完成设置步骤,在配置服务时勾选每个步骤
- 报告生成:报告的各个部分成为待办事项:收集数据、分析趋势、编写摘要、格式化输出
处理空状态和加载状态
处理代理创建计划之前的初始状态:
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>
代理正在创建计划...
</div>
</div>
);
}
return (
<div className="rounded-lg border bg-white p-4 shadow-sm">
{/* ... 完整的待办事项列表渲染 */}
</div>
);
}
最佳实践
- 突出显示待办事项列表。它是基于计划的代理的主要进度指示器。不要将其隐藏在首屏以下。
- 动画状态转换。平滑的转换使代理感觉更响应。在背景颜色、文本装饰和不透明度上使用 CSS 过渡。
- 仅突出显示一个
in_progress 项目。代理通常一次处理一个任务。如果多个项目显示为 in_progress,UI 会变得嘈杂。考虑仅对第一个项目使用脉冲效果。
- 折叠或淡化已完成的项目。随着列表增长,已完成的项目变得不那么相关。减少它们的视觉权重,以便用户专注于仍在进行的内容。
- 显示进度百分比。像“67% 完成”这样的单个数字即使从房间另一头也能立即理解。
- 保持待办事项列表同步。因为
stream.values 以响应式方式更新,待办事项列表会自动保持最新。不要添加手动轮询或刷新逻辑。