并非每次智能体交互都是聊天。有时智能体正在执行一个多步骤计划,而展示进度的最佳方式是一个实时更新的待办事项列表 。深度智能体待办事项列表模式直接从智能体的状态中读取一个 todos 数组,并在智能体执行计划时,根据每个项目的当前状态进行渲染。这是一个基于你用于聊天的同一个 useStream 钩子构建的进度仪表板。它表明智能体状态可以驱动任何用户界面,而不仅仅是消息气泡。
工作原理
在 LangGraph 智能体中,状态不仅限于消息。你可以定义自定义状态键 来保存任意数据。在这个例子中,是一个 todos 数组。当智能体执行其计划时,它会将每个待办事项的状态从 "pending" 更新为 "in_progress",再更新为 "completed"。useStream 钩子通过 stream.values 暴露这些自定义状态值,你的用户界面会响应式地渲染它们。
流程如下:
用户提交一个请求
智能体创建一个计划并在其状态中填充 todos
智能体开始执行每个待办事项,状态依次经历 pending → in_progress → completed
随着智能体的进展,stream.values.todos 实时更新
你的用户界面使用当前状态重新渲染待办事项列表
设置 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" > 智能体进度 </ 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,用户界面会变得杂乱。考虑仅对第一个项目使用脉冲效果。
折叠或淡化已完成的项目 。随着列表增长,已完成的项目变得不那么相关。减少它们的视觉权重,以便用户专注于仍在进行的内容。
显示进度百分比 。像“67% 完成”这样的单个数字即使从远处也能立即理解。
保持待办事项列表同步 。因为 stream.values 是响应式更新的,所以待办事项列表会自动保持最新。不要添加手动轮询或刷新逻辑。
将这些文档连接 到 Claude、VSCode 等,通过 MCP 获取实时答案。