🚀 Vercel AI SDK 使用指南: 子代理 (Subagents)

在构建复杂的 AI Agent 系统时,我们经常会遇到一个棘手的问题:上下文爆炸

如果让主 Agent 直接去阅读长篇文档、检索引擎或者分析代码库,大量的中间信息不仅会迅速消耗 Context Window,还会导致模型"精神涣散",降低最终回答的质量。相比于在 LangGraph 等框架中手动编排复杂的节点图和状态流转,Vercel AI SDK 提供了一种更直观、原生的高级抽象:子代理 (Subagents)

今天我们就来详细拆解如何在 Vercel AI SDK 中使用子代理,并以阿里千问最新模型 (qwen-max) 为例进行实战演示。

什么是子代理?

一句话总结:子代理是一个可以被"父代理"作为工具 (Tool) 调用的独立 Agent。

子代理会在自己隔离的上下文中自主运行,执行具体的脏活累活。等任务完成后,它只将提炼后的结果返回给主代理。

适用场景分析

✅ 推荐使用子代理的场景 ❌ 避免使用子代理的场景
任务需要吞吐大量的 Token(如文件阅读、信息搜索) 任务非常简单且聚焦
需要并行处理相互独立的研究任务 简单的线性顺序处理即可完成
希望按特定能力隔离工具箱,避免主节点工具泛滥 当前工具集完全可以安全共存,且不越界

基础实战:不带流式输出的 Subagent

最简单的子代理模式不需要任何黑魔法。主代理只需要拥有一个在 execute 函数中调用另一个 Agent 的 Tool。

由于阿里千问 (Qwen) 已经完美兼容 OpenAI 的接口规范,我们可以直接使用 @ai-sdk/openai 配合 DashScope 的 Endpoint 来驱动我们的 Agent。

TypeScript

php 复制代码
import { ToolLoopAgent, tool } from 'ai';
import { z } from 'zod';
import { createOpenAI } from '@ai-sdk/openai';

// 配置阿里千问大模型
const dashscope = createOpenAI({
  baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
  apiKey: process.env.DASHSCOPE_API_KEY,
});
const qwenModel = dashscope('qwen-max');

// 1. 定义一个专门用于"深度调研"的子代理
const researchSubagent = new ToolLoopAgent({
  model: qwenModel,
  instructions: `你是一个专业的学术和数据分析代理。
  请自主完成调研任务。
  IMPORTANT: 当你完成所有步骤后,请在最终回复中写下条理清晰的研究总结。`,
  tools: {
    // 假设这些是你写好的基础工具
    read: readFileTool,  
    search: searchTool,  
  },
});

// 2. 创建一个工具,将工作委托给子代理
const researchTool = tool({
  description: '深入研究一个主题或问题。',
  inputSchema: z.object({
    task: z.string().describe('需要完成的调研任务描述'),
  }),
  // 传入 abortSignal 以便在用户取消请求时中断子代理
  execute: async ({ task }, { abortSignal }) => {
    const result = await researchSubagent.generate({
      prompt: task,
      abortSignal, 
    });
    return result.text; // 将总结文本返回给主代理
  },
});

// 3. 主代理:专注于核心调度
export const mainAgent = new ToolLoopAgent({
  model: qwenModel,
  instructions: '你是一个全能的私人助理。遇到需要深度查阅或调研的问题时,请委派给 researchTool。',
  tools: {
    research: researchTool,
  },
});

在这个模式下,主节点发起调用后会一直等待,直到子代理吐出最终文本。

(💡 Tip: 如果主流程被取消,abortSignal 会抛出 AbortError。为了防止后续对话出现不完整的工具调用记录,可以在解析历史记录时使用 convertToModelMessages(messages, { ignoreIncompleteToolCalls: true }))


进阶:流式进度展示 (Streaming Progress)

在真实的生产环境中,子代理干活可能需要好几十秒。为了用户体验,我们必须把子代理"正在搜索XXX"、"正在阅读XXX"的中间状态实时推送到前端 UI。

Vercel AI SDK 通过 async function* (异步生成器) 和 readUIMessageStream 优雅地解决了这个问题:

TypeScript

php 复制代码
import { readUIMessageStream, tool } from 'ai';

const streamingResearchTool = tool({
  description: '深入研究一个主题并返回流式进度。',
  inputSchema: z.object({
    task: z.string(),
  }),
  // 1. 将 execute 改为 async function*
  execute: async function* ({ task }, { abortSignal }) {
    // 2. 调用子代理的 stream 方法
    const result = await researchSubagent.stream({
      prompt: task,
      abortSignal,
    });

    // 3. 拦截并 yield 出完整的、不断累加的 UI 消息状态
    for await (const message of readUIMessageStream({
      stream: result.toUIMessageStream(),
    })) {
      yield message; 
    }
  },
});

核心秘籍:精准控制主模型"所见内容"

子代理之所以能解决"上下文爆炸",关键在于前端 UI 和主模型看到的内容是不一样的

用户在 UI 上需要看到子代理调用的每一个工具、每一次思考,但主代理只需要看最后的那句"总结"。我们可以利用 toModelOutput 完美实现这种信息差:

TypeScript

javascript 复制代码
const advancedResearchTool = tool({
  // ...前面的 inputSchema 和 execute 逻辑保持不变...
  
  toModelOutput: ({ output: message }) => {
    // 从子代理的一大堆流式输出中,只提取最后一段文本作为总结
    const lastTextPart = message?.parts.findLast(p => p.type === 'text');
    return {
      type: 'text',
      value: lastTextPart?.text ?? '任务已完成。',
    };
  },
});

这样一来,子代理可能在背后消耗了 10 万 Token 来翻阅资料,但主代理的 Context Window 里仅仅增加了几百 Token 的总结。极大地保持了主节点逻辑的清晰!


在 UI 层优雅渲染

对于熟悉 React 生态的开发者来说(尤其是习惯了组件化思维或者刚从 Vue 3 迁移到 React 的朋友),结合 @ai-sdk/reactuseChat Hook 来捕获这套复杂状态简直如丝般顺滑。

通过检查 part.statepart.preliminary,我们可以精准渲染出主代理的文本与子代理的动态进度:

TypeScript

ini 复制代码
'use client';

import { useChat } from '@ai-sdk/react';
import type { InferAgentUIMessage } from 'ai';
// 假设你导出了 mainAgent
import type { MainAgentMessage } from '@/lib/agents'; 

export function Chat() {
  const { messages } = useChat<MainAgentMessage>();

  return (
    <div className="flex flex-col gap-4">
      {messages.map(message =>
        message.parts.map((part, i) => {
          // 渲染主代理对话
          if (part.type === 'text') {
            return <p key={i} className="text-gray-800">{part.text}</p>;
          }
          
          // 渲染子代理 (Research Tool) 状态
          if (part.type === 'tool-research') {
            return (
              <div key={i} className="bg-gray-50 p-4 rounded-lg">
                {part.state !== 'input-streaming' && (
                  <div className="font-bold">🔎 正在调研: {part.input.task}</div>
                )}
                
                {/* 捕获并渲染子代理返回的流式 / 最终结果 */}
                {part.state === 'output-available' && (
                  <div className="mt-2 border-l-2 border-blue-400 pl-2">
                    {part.output.parts.map((nestedPart, j) => {
                      if (nestedPart.type === 'text') {
                        return <p key={j}>{nestedPart.text}</p>;
                      }
                      return null;
                    })}
                  </div>
                )}
              </div>
            );
          }
          return null;
        })
      )}
    </div>
  );
}

⚠️ 避坑指南

在使用 Subagents 时,有几个细节需要特别留意:

  1. 不支持人工审批 :子代理内部的工具不能配置 needsApproval,所有的工具必须能够自动化静默执行。
  2. 默认上下文隔离 :子代理的每次调用都是一个白纸般的全新上下文。如果你确实需要让它知道主聊天的历史,需要手动在 execute 中拼接 messages(但这往往会削弱子代理"卸载上下文"的核心优势,建议谨慎使用)。

结语

子代理 (Subagents) 是将 AI 应用从"玩具"推向"企业级系统"的关键一环。它以极低的认知成本,赋予了单一 Agent 分治、并行和长文本处理的拓展能力。

如果你觉得有帮助,欢迎在评论区交流你在开发复杂 Agent 系统时遇到的痛点!👏

相关推荐
ZaneAI2 小时前
🚀 Vercel AI SDK 使用指南:Call Options 动态配置 Agent
typescript·agent
量子位3 小时前
我把Agent拉进群聊,它竟然开始带队干活?全球首个AI社交通用平台来了!
人工智能·agent
量子位3 小时前
1美金时薪雇个全栈替身,MiniMax M2.5让打工人也能体验当老板的感觉
agent·ai编程
后端小肥肠5 小时前
从n8n到Claude Skills:轻松搞定小红书热门美食手账,3分钟出图,小白也能会!
人工智能·aigc·agent
无名修道院6 小时前
AI大模型-LangChain
langchain·agent·ai大模型
技术狂人1687 小时前
告别“复读机“AI:用Agent Skills打造你的专属编程副驾
人工智能·职场和发展·agent·skills
OPEN-Source8 小时前
给 Agent 安装技能:工具抽象、自动选工具与安全边界
人工智能·python·agent·rag·deepseek
带娃的IT创业者8 小时前
解密OpenClaw系列04-OpenClaw技术架构
macos·架构·cocoa·agent·ai agent·openclaw
3秒一个大9 小时前
深入解析 React 回到顶部(BackToTop)组件的实现与设计
前端·react.js·typescript