并非所有的智能体交互都是聊天。有时智能体正在执行一个多步计划,而展示进度的最佳方式是待办事项列表 ,它会实时更新。deep agent todo list 模式直接从智能体的状态中读取 todos 数组,随着智能体执行计划,渲染每个项目及其当前状态。这是一个构建在你用于聊天的同一个 useStream hook 上的进度仪表板。它表明智能体状态可以为任何 UI 提供动力,而不仅仅是消息气泡。
工作原理
在 LangGraph 智能体中,状态不仅限于消息。你可以定义自定义状态键 来保存任意数据。在这种情况下,是一个 todos 数组。当智能体执行计划时,它将每个待办事项的状态从 "pending" 更新为 "in_progress" 再到 "completed"。useStream hook 通过 stream.values 暴露这些自定义状态值,你的 UI 会响应式地渲染它们。
流程如下所示:
用户提交请求
智能体创建计划并在其状态中填充 todos
智能体开始执行每个待办事项,状态 transitions 通过 pending → in_progress → completed
随着智能体进度,stream.values.todos 实时更新
你的 UI 用当前状态重新渲染待办事项列表
设置 useStream
不需要特殊配置。将 useStream 指向你的智能体并从 stream.values 读取 todos。
定义一个匹配智能体状态模式的 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(智能体正在处理)、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" > 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 >
) ;
}
进度条
视觉进度条让用户一目了然地了解整体完成情况:
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 >
) ;
}
单个待办事项
每个项目都有一个状态图标、颜色编码的文本和已完成任务的删除线样式:
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 数组派生进度指标:
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 时显示待办事项列表。在智能体创建计划之前,没有什么可显示的。显示空组件会浪费空间。
超越 todos 的自定义状态
此模式展示了一个强大的原则: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 hook 会自动将它们包含在 stream.values 中,无需任何额外的客户端配置。
动画化转换
待办事项状态转换实时发生,平滑的动画使这些变化感觉 polished 而不是突兀:
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 类确保颜色变化、删除线和透明度 shift 都平滑动画。
待办事项列表模式适合任何智能体执行结构化计划的场景:
项目规划 :智能体将项目分解为任务并按顺序处理
研究工作流 :每个研究问题成为一个待办事项,智能体调查并完成它
数据处理 :摄入、验证、转换和导出等步骤各自获得自己的待办事项
入职流程 :智能体 walkthrough 设置步骤,配置服务时检查每个步骤
报告生成 :报告的章节成为待办事项:收集数据、分析趋势、撰写摘要、格式化输出
处理空状态和加载状态
处理智能体创建计划之前的初始状态:
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" >
{ /* ... 完整待办事项列表渲染 */ }
</ div >
) ;
}
最佳实践
醒目地显示待办事项列表 。它是基于计划的智能体的主要进度指示器。不要把它埋在折叠线以下。
动画化状态转换 。平滑的转换使智能体感觉响应更快。对背景颜色、文本装饰和透明度使用 CSS 转换。
仅高亮显示一个 in_progress 项目 。智能体通常一次处理一个任务。如果多个项目显示为 in_progress,UI 会变得嘈杂。考虑仅 pulsing 第一个。
折叠或调暗已完成的项目 。随着列表增长,已完成的项目变得不那么相关。减少它们的视觉权重,以便用户关注仍在发生的事情。
显示进度百分比 。一个像 “67% complete” 这样的单一数字即使从房间对面也能立即理解。
保持待办事项列表同步 。因为 stream.values 响应式更新,待办事项列表会自动保持最新。不要添加手动轮询或刷新逻辑。
连接这些文档 到 Claude、VSCode 等 via MCP 以获得实时答案。