🚀 Vercel AI SDK 使用指南: 循环控制 (Loop Control)

在开发复杂的 AI Agent 时,Agent 通常需要经过"思考 -> 调用工具 -> 观察结果 -> 再思考"的多次循环才能完成任务。如何优雅地控制这个循环(例如何时停止、每一步如何动态调整参数)是构建健壮 Agent 的关键。

本文将基于 Vercel AI SDK 的最新特性,带你深入玩转 Agent 的循环控制 (Loop Control) 。为了贴合国内开发者的实际场景,本文的所有样例代码均采用阿里通义千问最新模型 (qwen-max) 进行演示!

🛠 环境与模型准备

通义千问提供了全面兼容 OpenAI 的 API 端点,因此在 Vercel AI SDK 中,我们可以直接使用 @ai-sdk/openai 来无缝接入:

Bash

bash 复制代码
npm install ai @ai-sdk/openai zod

在代码中初始化 Qwen 模型实例:

TypeScript

php 复制代码
import { createOpenAI } from '@ai-sdk/openai';

// 配置阿里百炼 (DashScope) 的兼容端点
const qwen = createOpenAI({
  baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
  apiKey: process.env.DASHSCOPE_API_KEY, 
});

// 在后续的示例中,我们将统一使用 qwen('qwen-max')

一、 核心概念:什么是 Loop Control?

在使用 ToolLoopAgent 编排 Agent 时,循环会持续进行,直到满足以下任一条件才会停止:

  1. 模型返回了除 tool-calls(工具调用)之外的完成原因。
  2. 调用的工具没有 配备 execute(执行)函数。
  3. 工具调用触发了需要人工审批的拦截。
  4. 满足了开发者定义的停止条件 (Stop Condition)

Vercel AI SDK 提供了两个极其强大的参数来控制这个内部循环:

  • stopWhen: 决定何时终止执行。
  • prepareStep : 在每一步执行,动态修改运行设置(如切换模型、控制工具白名单、精简上下文等)。

二、 停止条件 (Stop Conditions)

stopWhen 参数控制 Agent 在拿到工具执行结果后是否继续下一步。默认情况下,Agent 最多执行 20 步(即内置的 stepCountIs(20))。

1. 组合内置条件

AI SDK 提供了现成的停止条件,你可以将它们组合在数组中,满足其一即停止

TypeScript

javascript 复制代码
import { ToolLoopAgent, stepCountIs, hasToolCall } from 'ai';
// 引入上面配置的 qwen
// import { qwen } from './qwen-setup'; 

const agent = new ToolLoopAgent({
  model: qwen('qwen-max'),
  tools: {
    // 你的工具集,例如 search 等
  },
  stopWhen: [
    stepCountIs(20),          // 兜底策略:最多执行 20 步,防止死循环
    hasToolCall('someTool'),  // 业务逻辑:一旦调用了 'someTool' 工具就立刻停止
  ],
});

const result = await agent.generate({
  prompt: '请调研并分析 2026 年前端开发趋势',
});

2. 编写自定义条件 (Custom Conditions)

面对复杂的业务场景,你可以获取到历史所有步骤 steps,编写自定义的停止逻辑。例如,根据消耗的 Token 成本来"熔断" Agent:

TypeScript

javascript 复制代码
import { ToolLoopAgent, StopCondition, ToolSet } from 'ai';

const tools = { /* ... */ } satisfies ToolSet;

// 自定义条件:当 Token 估算成本超过 $0.50 时强制停止
const budgetExceeded: StopCondition<typeof tools> = ({ steps }) => {
  const totalUsage = steps.reduce(
    (acc, step) => ({
      inputTokens: acc.inputTokens + (step.usage?.inputTokens ?? 0),
      outputTokens: acc.outputTokens + (step.usage?.outputTokens ?? 0),
    }),
    { inputTokens: 0, outputTokens: 0 },
  );
  
  // 按照自定义汇率或 token 价格估算
  const costEstimate = (totalUsage.inputTokens * 0.01 + totalUsage.outputTokens * 0.03) / 1000;
  return costEstimate > 0.5; 
};

const agent = new ToolLoopAgent({
  model: qwen('qwen-max'),
  tools,
  stopWhen: budgetExceeded, 
});

三、 动态准备步骤 (Prepare Step) 神器

prepareStep 回调会在循环的每一步正式发起大模型请求之前运行。利用它,你可以实现高度智能的动态行为。

1. 动态模型切换 (Dynamic Model Selection)

前几步简单的资料检索我们可以用轻量级模型省钱提速,后面复杂的逻辑推理再无缝切换到 qwen-max

TypeScript

dart 复制代码
const agent = new ToolLoopAgent({
  model: qwen('qwen-turbo'), // 初始默认使用较快/较便宜的模型
  tools: { /* ... */ },
  prepareStep: async ({ stepNumber, messages }) => {
    // 如果循环超过 2 步,且上下文消息变长,说明任务变得复杂
    // 动态返回新的配置,无缝切换到最强的 qwen-max 模型
    if (stepNumber > 2 && messages.length > 10) {
      return {
        model: qwen('qwen-max'),
      };
    }
    // 返回空对象则继续使用当前配置
    return {};
  },
});

2. 阶段性工具分配 (Tool Selection)

在复杂 SOP(标准作业程序)中,我们可以控制 Agent 在不同阶段能使用的工具:

TypeScript

kotlin 复制代码
  prepareStep: async ({ stepNumber }) => {
    // 第一阶段:检索资料 (第 0-2 步)
    if (stepNumber <= 2) {
      return {
        activeTools: ['search'],    // 只开放搜索工具
        toolChoice: 'required',     // 强制要求大模型必须调用工具
      };
    }
    
    // 第二阶段:数据分析 (第 3-5 步)
    if (stepNumber <= 5) {
      return { activeTools: ['analyze'] };
    }
    
    // 第三阶段:总结汇报 (第 6 步以上)
    return {
      activeTools: ['summarize'],
      toolChoice: { type: 'tool', toolName: 'summarize' }, // 强制大模型调用特定的总结工具
    };
  }

3. 防止上下文撑爆 (Context Management)

长时间运行的 Agent 容易超出模型支持的最大 Token 长度。我们可以在这里动态裁剪传递给大模型的消息:

TypeScript

kotlin 复制代码
  prepareStep: async ({ messages }) => {
    // 如果对话超过 20 条,进行滑动窗口裁剪
    if (messages.length > 20) {
      return {
        messages: [
          messages[0], // 永远保留第一条 System Prompt/User Instruction
          ...messages.slice(-10), // 只保留最近的 10 条交互记录
        ],
      };
    }
    return {};
  }

四、 强制工具调用 (Forced Tool Calling) 模式

很多时候,我们不希望 Agent 用自然语言"水字数",而是必须通过工具输出标准化的 JSON 结构结果。

解法: 结合 toolChoice: 'required',并设计一个不包含 execute 函数的 done 虚拟工具。

💡 原理: 因为模型被强制要求调用工具,而当它调用了没有执行函数的 done 工具时,AI SDK 内部因为无代码可执行,会触发"循环中断"条件,从而完美终止 Agent,并将结构化参数原封不动地返回给你!

TypeScript

php 复制代码
import { ToolLoopAgent, tool } from 'ai';
import { z } from 'zod';

const agent = new ToolLoopAgent({
  model: qwen('qwen-max'),
  tools: {
    search: searchTool,
    analyze: analyzeTool,
    done: tool({
      description: '当你完成所有工作后,调用此工具输出最终结果',
      inputSchema: z.object({
        answer: z.string().describe('最终的分析报告'),
        confidence: z.number().describe('可信度评分 0-1'),
      }),
      // 注意核心技巧:这里绝对不写 execute 函数!
    }),
  },
  toolChoice: 'required', // 强制每一步都不能输出废话,必须用工具
});

const result = await agent.generate({
  prompt: '请综合分析这份数据,并在完成后使用 done 工具提交最终报告。',
});

// 从 staticToolCalls (未被执行的工具调用列表) 中提取出最终答案
const finalToolCall = result.staticToolCalls[0]; 
if (finalToolCall?.toolName === 'done') {
  console.log("最终报告:", finalToolCall.input.answer);
  console.log("可信度:", finalToolCall.input.confidence);
}

五、 手动循环控制 (Manual Loop Control)

如果你发现 ToolLoopAgent 的声明式 API 依然无法满足极为复杂的定制需求,你可以退回到 AI SDK 的底层,使用 generateText 手动写一个 while 循环。这为你提供了 100% 的底层控制流掌控力:

TypeScript

arduino 复制代码
import { generateText, ModelMessage } from 'ai';

const messages: ModelMessage[] = [{ role: 'user', content: '分析并解决这个系统 Bug...' }];
let step = 0;
const maxSteps = 10;

while (step < maxSteps) {
  const result = await generateText({
    model: qwen('qwen-max'),
    messages,
    tools: {
      // 在这里挂载你的工具
    },
  });

  // 将当前这一步的交互结果(包含 assistant 回复、工具调用结果等)追加到上下文池
  messages.push(...result.response.messages);

  // 如果模型直接生成了最终结论文本(并没有继续调用工具),则认为整个思考链条已闭环
  if (result.text) {
    break; 
  }

  step++;
}

console.log("任务完成,最终输出:", messages[messages.length - 1].content);

结语

Vercel AI SDK 的 Loop Control 为构建复杂的 Agent 赋予了极大的灵活性和工程化能力:

  1. stopWhen 让你可以精准定义退出条件,避免死循环和天价 API 账单。
  2. prepareStep 是实现 Agent "动态进化"的关键,可按需切换模型配置和分配工具白名单。
  3. 创新的虚拟 done 工具模式,是确保业务系统获得结构化数据的绝佳实践。

搭配国内顶尖的通义千问 qwen-max 模型强大的指令遵循能力,无论在落地效果还是接口兼容性上,都能为开发者提供丝滑的体验!

如果在开发 Agent 的路上对本文有所启发,欢迎点赞、收藏并在评论区交流你的最佳实践!👇

相关推荐
梦里寻码1 小时前
深入解析 SmartChat 的 RAG 架构设计 — 如何用 pgvector + 本地嵌入打造企业级智能客服
前端·agent
jDPvYjdteF1 小时前
用Matlab开启高光谱数据处理之旅
后端
jay神1 小时前
基于SpringBoot的英语自主学习系统
java·spring boot·后端·学习·毕业设计
qinaoaini2 小时前
Spring 简介
java·后端·spring
Cache技术分享2 小时前
322. Java Stream API - 使用 Finisher 对 Collector 结果进行后处理
前端·后端
何中应2 小时前
记录一次pom.xml依赖顺序产生的错误
后端·maven
dfyx9992 小时前
SpringBoot教程(三十二) SpringBoot集成Skywalking链路跟踪
spring boot·后端·skywalking
风的归宿552 小时前
一次openresty的网关性能优化之旅
后端
_Pluto_2 小时前
RabbitMQ的消息安全性
后端