Deep Agents 框架-前端

上篇

引言

本篇主要是了解一下前端api,了解就行,记是记不住的。用的时候再查。框架可能选择react会很好一些。vue生态可能不是太好。vue3学习成本和代码规范会比react。react纯js操作html和js脚本,看起来比较混乱。样式、html、css、js、配置、数据实际上能隔离才是最好维护的。

1 概览

构建实时可视化深度智能体工作流的前端,这些设计模式展示了如何渲染以下内容:

  • 子智能体进度:显示下级智能体的工作状态。
  • 任务规划:展示智能体的思考路径和计划步骤。
  • 流式内容:像打字机一样实时呈现生成的内容。
  • 类 IDE 的沙盒体验:提供类似代码编辑器的交互式环境。

以上功能均适用于使用 createDeepAgent 创建的智能体。

1.1 架构

深度智能体(Deep Agents)采用"协调者-工作者"架构。 主智能体(协调者)负责规划任务,并将其委派给专门的子智能体(工作者),每个子智能体都在隔离的环境中运行。在前端,useStream 能够同时呈现协调者的消息以及每个子智能体的流式状态。

复制代码
from deepagents import create_deep_agent

agent = create_deep_agent(
    model="google_genai:gemini-3.1-pro-preview",
    tools=[get_weather],
    system_prompt="You are a helpful assistant",
    subagents=[
        {
            "name": "researcher",
            "description": "Research assistant",
        }
    ],
)

在前端,连接方式与 createAgent 一样,都是使用 useStream

但深度智能体(Deep Agent)模式会使用 useStream额外特性,例如:

  • stream.subagents
  • stream.values.todos
  • filterSubagentMessages

这些特性用于渲染子智能体专属的用户界面

复制代码
import { useStream } from "@langchain/react";

function App() {
  const stream = useStream<typeof agent>({
    apiUrl: "http://localhost:2024",
    assistantId: "agent",
  });

  // Deep agent state beyond messages
  const todos = stream.values?.todos;
  const subagents = stream.subagents;
}

1.2 模式

子智能体流式传输

展示专家级子智能体的实时内容流,包含进度追踪和可折叠的卡片设计。

待办事项列表

通过从智能体状态同步的实时待办列表,来追踪智能体的执行进度。

沙盒

构建一个类似集成开发环境(IDE)的用户界面,包含文件浏览器、代码查看器和差异对比面板,并由沙盒环境提供支持。

1.3 相关模式

LangChain 的前端模式,包括 Markdown 消息工具调用人机协同 ,同样都适用于深度智能体(Deep Agents)。由于深度智能体是构建在相同的 LangGraph 运行时 之上的,因此 useStream 提供了相同的核心 API

2 模式

2.1 子智能体流式传输

当协调者智能体(Coordinator)生成专家级子智能体(如研究员、分析师、作家)时,你需要将协调者的消息每个子智能体的流式输出分开渲染。

  1. useStream 中设置 filterSubagentMessages: true,以清晰地将这两类流分离。

  2. 然后使用 getSubagentsByMessage,将每个子智能体的进度卡片 挂载到触发它的那个协调者消息下方。

2.1.1 过滤子智能体消息

如果不进行过滤,每个子智能体产生的每一个字符(Token)都会交错穿插 在协调者的消息流中,导致内容无法阅读

当设置 filterSubagentMessages: true 后:

  • stream.messages 仅包含协调者的消息。
  • 每个子智能体的内容可以通过 stream.subagentsstream.getSubagentsByMessage 单独获取。
  • 用户界面保持整洁:协调者的推理过程与专家的工作内容是分开的。

这种分离让你能够在一个地方渲染协调者的消息,并将每个子智能体的进度卡片精准地挂载到它所属的位置:即生成它的那条协调者消息下方。

2.2.2 设置 useStream

  1. 始终设置 filterSubagentMessages: true

    这会从主消息流中移除子智能体的字符(Tokens),这样你就可以独立渲染协调者的消息和子智能体的输出了。

  2. 定义一个 TypeScript 接口 ,使其与你智能体的状态模式(Schema)相匹配,并将其作为类型参数传递给 useStream,以实现对状态值的类型安全访问

在下面的示例中,请将 typeof myAgent 替换为你自己的接口名称

复制代码
import type { BaseMessage } from "@langchain/core/messages";

interface AgentState {
  messages: BaseMessage[];
}

2.1.2 提交并开启子图流式传输

当提交消息时,请启用子图流式传输 ,并设置一个合适的递归限制 。深度智能体的工作流通常涉及多层嵌套的子图,因此较高的递归限制可以防止任务过早终止

复制代码
stream.submit(
  { messages: [{ type: "human", content: text }] },
  { streamSubgraphs: true }
);

DeepAgents 设置的默认递归限制为 10,000 ,这对于大多数多专家协作的场景来说已经足够了。如果需要,你可以通过 config.recursion_limit 来覆盖这个默认值。

2.1.3 子智能体流式接口

每个子智能体都会暴露出一个 SubagentStreamInterface,其中包含关于该子智能体任务的元数据,具体包括:

  • 任务:具体在做什么。

  • 状态:当前进展如何。

  • 计时:耗时多久。

    interface SubagentStreamInterface {
    id: string;
    status: "pending" | "running" | "complete" | "error";
    messages: BaseMessage[];
    result: string | undefined;
    toolCall: {
    id: string;
    name: string;
    args: {
    description: string;
    subagent_type: string;
    [key: string]: unknown;
    };
    };
    startedAt: number | undefined;
    completedAt: number | undefined;
    }

属性 (Property) 描述 (Description)
id 此子智能体实例的唯一标识符
status 生命周期状态:pending(等待中) → running(运行中) → complete(完成)或 error(错误)
messages 子智能体自己的消息流,实时更新
result 最终输出的文本,仅在状态为 complete 时可用
toolCall 生成此子智能体的工具调用对象,包含任务元数据
toolCall.args.description 协调者分配给此子智能体的任务描述
toolCall.args.subagent_type 专家的类型或名称(例如 "researcher"、"analyst")
startedAt 子智能体开始执行的时间戳
completedAt 子智能体结束执行的时间戳

2.1.4 将子智能体与消息关联

getSubagentsByMessage 方法会返回由某条特定 AI 消息生成的子智能体。这让你能够直接在触发它们的协调者消息下方渲染子智能体卡片:

const turnSubagents = stream.getSubagentsByMessage(msg.id);

这会返回一个 SubagentStreamInterface 对象数组。如果该消息没有生成任何子智能体,则返回一个空数组。

2.1.5构建子代理卡片

每个子代理卡片显示专家的姓名、任务描述、流式内容或最终结果,以及时间信息。

html 复制代码
import { AIMessage } from "@langchain/core/messages";

function SubagentCard({
  subagent,
}: {
  subagent: SubagentStreamInterface;
}) {
  const [expanded, setExpanded] = useState(true);

  const title =
    subagent.toolCall?.args?.subagent_type ?? `Agent ${subagent.id}`;
  const description = subagent.toolCall?.args?.description ?? "";

  const lastAIMessage = subagent.messages
    .filter(AIMessage.isInstance)
    .at(-1);

  const displayContent =
    subagent.status === "complete"
      ? subagent.result
      : typeof lastAIMessage?.content === "string"
        ? lastAIMessage.content
        : "";

  const elapsed = getElapsedTime(subagent.startedAt, subagent.completedAt);

  return (
    <div className="rounded-lg border bg-white shadow-sm">
      <button
        onClick={() => setExpanded(!expanded)}
        className="flex w-full items-center justify-between p-4"
      >
        <div className="flex items-center gap-3">
          <StatusIcon status={subagent.status} />
          <div>
            <h4 className="font-semibold capitalize">{title}</h4>
            <p className="text-xs text-gray-500">{description}</p>
          </div>
        </div>
        <div className="flex items-center gap-2">
          {elapsed && (
            <span className="text-xs text-gray-400">{elapsed}</span>
          )}
          <StatusBadge status={subagent.status} />
        </div>
      </button>

      {expanded && displayContent && (
        <div className="border-t px-4 py-3">
          <div className="prose prose-sm max-w-none line-clamp-6">
            {displayContent}
            {subagent.status === "running" && (
              <span className="inline-block h-4 w-1 animate-pulse bg-blue-500" />
            )}
          </div>
        </div>
      )}
    </div>
  );
}

function getElapsedTime(
  startedAt: number | undefined,
  completedAt: number | undefined
): string | null {
  if (!startedAt) return null;
  const end = completedAt ?? Date.now();
  const seconds = Math.round((end - startedAt) / 1000);
  if (seconds < 60) return `${seconds}s`;
  return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
}

上面这个代码看起来是有点丑陋,特别是

程序设计艺术、优雅代码价值慢慢的磨灭了。现在推行的AI生成代码,更加恶化了这一现象。

2.1.5状态图标和徽章

一致的视觉指示器可帮助用户一目了然地识别子代理的状态:

html 复制代码
function StatusIcon({ status }: { status: SubagentStreamInterface["status"] }) {
  switch (status) {
    case "pending":
      return <span className="text-gray-400">○</span>;
    case "running":
      return <span className="animate-spin text-blue-500">◉</span>;
    case "complete":
      return <span className="text-green-500">✓</span>;
    case "error":
      return <span className="text-red-500">✕</span>;
  }
}

function StatusBadge({ status }: { status: SubagentStreamInterface["status"] }) {
  const styles = {
    pending: "bg-gray-100 text-gray-600",
    running: "bg-blue-100 text-blue-700",
    complete: "bg-green-100 text-green-700",
    error: "bg-red-100 text-red-700",
  };

  return (
    <span className={`rounded-full px-2 py-0.5 text-xs font-medium ${styles[status]}`}>
      {status}
    </span>
  );
}

你看上面代码要是这样写多简洁明了,估计是现在动态类型语言望尘莫及的。历史包袱可不好甩掉,几乎所有使用比较广泛的语言都在想方设法模拟对象对象和类型,不是原生的,搞得五花八门,乌七八糟,严重造成了视觉和记忆上的冲击,美其名为灵活性、简洁( •̀ ω •́ )✧。

html 复制代码
string StatusIcon(status: SubagentStreamInterface["status"]) {
  switch (status) {
    case "pending":
      return <span className="text-gray-400">○</span>;
    case "running":
      return <span className="animate-spin text-blue-500">◉</span>;
    case "complete":
      return <span className="text-green-500">✓</span>;
    case "error":
      return <span className="text-red-500">✕</span>;
  }
}

2.1.6进度追踪

显示进度条和计数器,以便用户了解已完成多少个子代理:

html 复制代码
function SubagentProgress({
  subagents,
}: {
  subagents: SubagentStreamInterface[];
}) {
  const completed = subagents.filter((s) => s.status === "complete").length;
  const total = subagents.length;
  const percentage = total > 0 ? Math.round((completed / total) * 100) : 0;

  return (
    <div className="space-y-1">
      <div className="flex items-center justify-between text-xs text-gray-500">
        <span>Subagent progress</span>
        <span>
          {completed}/{total} complete
        </span>
      </div>
      <div className="h-2 overflow-hidden rounded-full bg-gray-200">
        <div
          className="h-full rounded-full bg-blue-500 transition-all duration-300"
          style={{ width: `${percentage}%` }}
        />
      </div>
    </div>
  );
}

2.1.7渲染带子代理卡片的消息

关键的布局模式是先渲染每条协调器消息,如果该消息生成了子代理,则立即在其下方渲染它们的卡片:

html 复制代码
function MessageWithSubagents({
  message,
  subagents,
}: {
  message: BaseMessage;
  subagents: SubagentStreamInterface[];
}) {
  if (message.type === "human") {
    return <HumanMessage content={message.content} />;
  }

  return (
    <div className="space-y-3">
      {message.content && (
        <div className="prose prose-sm max-w-none">
          {message.content}
        </div>
      )}

      {subagents.length > 0 && (
        <div className="ml-4 space-y-3 border-l-2 border-blue-200 pl-4">
          <SubagentProgress subagents={subagents} />
          {subagents.map((subagent) => (
            <SubagentCard key={subagent.id} subagent={subagent} />
          ))}
        </div>
      )}
    </div>
  );
}

2.1.8综合指示器

在所有子代理完成后,协调器需要时间来综合它们的结果并生成最终响应。在此阶段显示一个清晰的指示器:

html 复制代码
function SynthesisIndicator({
  subagents,
  isLoading,
}: {
  subagents: SubagentStreamInterface[];
  isLoading: boolean;
}) {
  const allComplete =
    subagents.length > 0 &&
    subagents.every((s) => s.status === "complete" || s.status === "error");

  if (!allComplete || !isLoading) return null;

  return (
    <div className="flex items-center gap-2 rounded-lg bg-purple-50 px-4 py-2 text-sm text-purple-700">
      <span className="animate-spin">⟳</span>
      Synthesizing results from {subagents.length} subagent
      {subagents.length !== 1 ? "s" : ""}...
    </div>
  );
}

对于复杂的多专家工作流,综合阶段可能需要几秒钟时间。一个清晰的"正在综合结果..."指示器可以防止用户误以为代理已卡住。

2.1.9 调试未过滤的输出

在开发过程中,你可以临时将 filterSubagentMessages 设置为 false,以便在主消息流中查看所有子代理的原始交错输出。这对于验证子代理的令牌(tokens)是否正确流动非常有用,但不应在生产环境的用户界面中使用。

2.1.10 使用场景

当你的智能体工作流涉及以下情况时,深度智能体子智能体卡片是合适的选择:

  • 深度研究:协调员分派研究人员调查问题的不同方面,然后综合他们的发现。
  • 多专家分析:领域专家(如法律、金融、技术)各自贡献他们的观点。
  • 复杂任务分解:规划者将一个大任务分解为子任务,并将每个子任务分配给专业工作者。
  • 代码审查流程:不同的智能体分别处理安全审查、风格检查、性能分析和文档审查

2.1.11 访问完整的子代理映射

除了按消息查找,你还可以通过 stream.subagents 一次性访问所有子代理:

html 复制代码
const allSubagents = [...stream.subagents.values()];
const running = allSubagents.filter((s) => s.status === "running");
const completed = allSubagents.filter((s) => s.status === "complete");
const errors = allSubagents.filter((s) => s.status === "error");

这对于构建全局进度指示器或仪表板非常有用,可以汇总所有子代理的活动,而不管它们是由哪条协调器消息生成的

2.1.12最佳实践

  • 始终设置 filterSubagentMessages: true:未过滤的流会导致协调器和子代理的令牌(tokens)混杂在一起,难以阅读。
  • 显示任务描述toolCall.args.description 字段准确地告诉用户每个子代理被要求做什么。请务必醒目地显示此信息。
  • 使用可折叠卡片:在包含 5 个以上子代理的工作流中,自动折叠已完成的卡片,以便用户专注于正在进行的工作。
  • 显示时间数据:展示每个子代理的耗时有助于用户了解性能特征并识别瓶颈。
  • 设置适当的递归限制:具有嵌套子图的深度智能体工作流需要比默认值 25 更高的限制。建议从 100 开始。
  • 单独处理子代理错误:一个子代理失败不应导致整个界面崩溃。应在该子代理的卡片中显示错误,同时让其他代理继续运行

2.2 待办事项列表

并非所有的智能体交互都是聊天。有时,智能体在执行一个多步骤计划,而展示进度的最佳方式是实时更新待办列表。深度智能体待办列表模式直接从智能体状态中读取待办事项数组,并在智能体执行计划时渲染每个项目的当前状态。这是一个基于你用于聊天的 useStream 钩子构建的进度仪表板。它表明智能体状态可以为任何用户界面提供支持,而不仅仅是消息气泡。

2.2.1 工作原理

2.2.2 设置上游流

2.2.3 代办任务接口

2.2.4 构建代办列表组件

2.2.5 进度条

2.2.6 独立的待办事项

2.2.7 计算进度

2.2.8 整合聊天消息

2.2.9 待办事项之外的自定义状态

2.2.10 动画过渡效果

2.2.11 使用场景

2.2.12 处理空置和加载装填

2.2.13 最佳实践

2.3 沙箱

2.3.1 架构

2.3.2 沙箱生命周期

2.3.3 设置智能体

2.3.4 添加文件浏览API

2.3.5 构建前端

2.3.6 第三方面板布局

2.3.7 使用场景

2.3.8 最佳实践

相关推荐
微软技术分享4 小时前
本地部署千问 2.5-1.5B-GGUF + LangChain 封装学习
数据库·学习·langchain
FrontAI4 小时前
深入浅出 LangGraph —— 第11章:子图:构建模块化Agent
人工智能·langchain·ai agent·langgraph
暗不需求5 小时前
深入理解 LangChain:AI 应用开发框架的工程化实践
前端·langchain
BU摆烂会噶5 小时前
【LangGraph】持久化实现的三大能力——人机交互
数据库·人工智能·python·langchain·人机交互
qq_283720055 小时前
Python+LangChain 入门到实战全教程+ 企业级案例
人工智能·langchain·#大模型·#llm·#rag·#ai 应用开发·#智能体
茉莉玫瑰花茶6 小时前
LangChain 核心组件 [ 3 ]
langchain
Joseph Cooper6 小时前
AI Agent 框架选型:LangChain、LlamaIndex、Anthropic SDK 和 Codex/Claude Code 怎么选
人工智能·langchain·llamaindex·claudecode·anthropic sdk·codex sdk
深海鱼在掘金7 小时前
深入浅出 LangChain —— 第六章:记忆与状态管理
人工智能·langchain·agent