首页/新闻资讯/正文详情

Agentic Loop 实战:用 AI SDK 的 generateText 构建智能 Agent 核心循环

发布时间:2026/9/26 17:26:21 来源:云帆数科 栏目:资讯中心
Agentic Loop 实战:用 AI SDK 的 generateText 构建智能 Agent 核心循环
1. 为什么你的 Agent 跑一半就停了很多人第一次用 AI SDK 的generateText接工具都会遇到一个很迷惑的现象工具明明被调用了日志里也打印出了结果但模型最后返回的文本却是「我来查询这两个城市的气温信息」这种半截话而不是真正的答案。这不是模型笨也不是工具写错了而是你只给了它一轮机会。模型的一次调用只返回一轮响应。它可以在这一轮里发起工具调用请求但它不会在服务端停下来等你把工具跑完。工具执行发生在你的代码里执行完的结果需要你手动写回messages然后再调用一次模型模型才能看到这些结果并继续推理。这个「调用模型 → 执行工具 → 回灌结果 → 再调用模型」的重复过程就是 Agentic Loop也就是 Agent 的核心循环。这篇聚焦 Agentic Loop 与 ReAct 思路在 AI SDK 的generateText上搭一个能跑通的最小 Agent 循环。你会看到循环骨架长什么样、工具怎么定义、消息怎么拼接、停止条件怎么判断以及怎么跑通一次多步推理调用。适合已经会用generateText做单轮对话、但还没让它自己转起来的开发者。读完你能自己写出一个可运行的多步 Agent并且知道每一步为什么这么写。2. 前置准备TaoToken 接入与依赖安装在写循环之前先把模型接入这步搞定。我用的是 TaoToken 提供的兼容接口它支持 OpenAI 风格的调用方式AI SDK 可以直接对接。你需要先去控制台创建一个 API Key然后把它放进环境变量。创建 Key 的入口在控制台的 API Keys 页面登录后新建一个就行。拿到 Key 之后在项目根目录建一个.env文件写入下面这行TAOTOKEN_API_KEYsk-你的key注意不要把 Key 硬编码进源码也不要把.env提交到 git。接下来安装依赖AI SDK 的核心包加上 Zod 用来做参数校验npm install ai ai-sdk/openai zod npm install -D tsx typescript types/node这里用ai-sdk/openai这个 provider通过配置baseURL指向 TaoToken 的接口地址就能复用 OpenAI 兼容协议。模型我选deepseek-chat便宜、响应快跑 demo 足够。如果你更习惯用别的模型换成对应的 model id 即可循环逻辑完全不变。配置 provider 的代码长这样import { createOpenAI } from ai-sdk/openai const taotoken createOpenAI({ apiKey: process.env.TAOTOKEN_API_KEY, baseURL: https://taotoken.net/api, }) const model taotoken(deepseek-chat)baseURL后面不要加多余的路径SDK 会自己拼/chat/completions。这一步配好之后后面所有generateText调用都用这个model变量。3. 可复制配置定义工具与循环骨架先定义一个天气工具用来演示多步推理。工具不接真实接口直接从一张假数据表取值这样你能专注看循环怎么转而不是被网络请求干扰。import { tool } from ai import { z } from zod const weatherData: Recordstring, string { 上海: 上海: 20°C, 深圳: 深圳: 28°C, } const weather tool({ description: 查询指定城市当前气温, inputSchema: z.object({ city: z.string().describe(城市名), }), execute: async ({ city }) { const value weatherData[city] ?? ${city}: 未知 console.log([天气工具结果] ${value}) return value }, })description告诉模型这个工具能干什么inputSchema约束参数格式execute是真正执行的业务逻辑。模型只负责决定「要不要调、调哪个、传什么参数」执行永远在你的代码里。现在写第一版调用故意不加停止条件看看会发生什么import { generateText } from ai const result await generateText({ model, prompt: 上海和深圳谁更热, tools: { weather }, }) console.log([模型输出] ${result.text})跑起来你会看到终端打印两行工具结果然后模型输出一句「我来查询这两个城市的气温信息」。两个城市的气温都查到了但模型没回答谁更热。原因就是前面说的第一次模型调用返回工具请求后就结束了SDK 执行完工具、把结果整理成消息但你的代码没有再调用一次模型断点就在这里。修复只需要加一行stopWhenimport { generateText, stepCountIs } from ai const result await generateText({ model, prompt: 上海和深圳谁更热, tools: { weather }, stopWhen: stepCountIs(3), }) console.log([模型输出] ${result.text})stepCountIs(3)允许这次任务最多走三步。两次天气查询发生在第一步里第一步结束后还没到上限generateText内部的循环就拿着工具结果发起第二次模型调用模型这时才看到两份气温回答「深圳更热」。一行配置跑通了任务但循环本身还藏在 SDK 里面。想看清它得自己手写一台。4. 手写主循环看清 Agent 为什么自己往下走手写循环之前先理解一件事模型不会记住上一次请求。每一轮你都要把完整的消息记录重新发过去。messages就是一份不断加长的对话记录每条消息的role标注这句话是谁说的user是用户assistant是模型tool是工具结果。下面这段是循环骨架十几行每一行都对应一个明确动作const messages: any[] [ { role: user, content: 上海和深圳谁更热 }, ] let round 0 while (true) { if (round 10) break const msg (await callLLM(messages)).message messages.push(msg) if (!msg.tool_calls) break for (const tc of msg.tool_calls) { const { city } JSON.parse(tc.function.arguments) const content weatherData[city] ?? ${city}: 未知 messages.push({ role: tool, tool_call_id: tc.id, content, }) } }callLLM(messages)是你自己封装的函数把完整消息和工具定义发给模型拿回一轮原始响应。第一轮响应里有两条天气工具调用代码保存这条模型回复执行两次查询把结果写进messages。程序走到while末尾又回到开头第二次模型调用就这样启动了。模型在第二次调用里读到气温回答「深圳更热」响应里没有tool_callsif (!msg.tool_calls) break退出循环。tool_call_id要原样使用模型给出的tc.id这样接口才能把每个结果配回对应的那次查询。两个break分工不同if (!msg.tool_calls) break是自然停止模型自己说完了if (round 10) break是强制停止防止模型一直要求调工具转个不停。强制停出来的时候messages最后一条还是没消化完的工具结果用户拿到的是半成品。把三个阶段标到循环里就是 ReAct 的节奏while (true) { const msg (await callLLM(messages)).message // Reason模型判断下一步 messages.push(msg) if (!msg.tool_calls) break for (const tc of msg.tool_calls) { // Act模型发起的工具调用 const { city } JSON.parse(tc.function.arguments) const result weatherData[city] ?? ${city}: 未知 // Observe工具带回新信息 messages.push({ role: tool, tool_call_id: tc.id, content: result }) } }Reason 判断下一步、Act 返回tool_calls、代码执行工具得到 Observe回灌进messages后再转一圈三阶段闭环。这套「想一步、做一步、看一步」的节奏就叫 ReAct它是一种思路不是某个库。你手写的这台while就是它。5. 验证请求跑通一次多步推理调用把上面的骨架补全成可运行文件验证一次完整的多步推理。先写callLLM用 fetch 直接调接口方便你看清原始返回async function callLLM(messages: any[]) { const res await fetch(https://taotoken.net/api/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${process.env.TAOTOKEN_API_KEY}, }, body: JSON.stringify({ model: deepseek-chat, messages, tools: [ { type: function, function: { name: weather, description: 查询指定城市当前气温, parameters: { type: object, properties: { city: { type: string, description: 城市名 } }, required: [city], }, }, }, ], }), }) return res.json() }然后跑主循环在每轮打印关键信息let round 0 while (true) { if (round 10) break const msg (await callLLM(messages)).message console.log([第 ${round} 轮] finish_reason${msg.finish_reason ?? stop}) messages.push(msg) if (!msg.tool_calls) break for (const tc of msg.tool_calls) { const { city } JSON.parse(tc.function.arguments) const content weatherData[city] ?? ${city}: 未知 console.log([工具执行] ${content}) messages.push({ role: tool, tool_call_id: tc.id, content }) } } console.log([最终答案] ${messages[messages.length - 1].content})用tsx loop.ts跑起来终端应该打出这样的过程第 1 轮finish_reason为tool_calls返回两个工具调用回灌两条tool结果第 2 轮finish_reason为stop返回「深圳更热」自然停止。最终答案就是「深圳更热」。如果你看到的是这个结果说明循环跑通了多步推理成立。6. 本篇常见错排查工具结果回灌了但模型还是没回答。最常见的原因是role写错了。工具结果必须是role: tool并且带上tool_call_id不能写成role: user或role: assistant。接口靠tool_call_id把结果配回对应的调用缺了它模型就看不到结果。循环转不停一直要求调工具。检查你的强制停止条件有没有生效。if (round 10) break里的round要在每次循环开头自增别写在for里面。另外确认break的位置在callLLM之后、执行工具之前否则会多跑一轮。generateText加了stopWhen还是只跑一步。确认stopWhen的值是stepCountIs(n)而不是数字。stepCountIs需要从ai包导入写成stopWhen: 3是无效的。另外tools里的工具必须带execute没有execute的工具 SDK 不会自动执行循环也就不会继续。报错tool_call_id不匹配。检查你是不是自己生成了 id。tool_call_id必须原样使用模型返回的tc.id不要自己拼一个。模型返回的 id 是接口用来配对结果的唯一凭据。模型返回的arguments解析失败。有些模型返回的arguments是空字符串或非法 JSON。加一层保护JSON.parse(tc.function.arguments || {})避免整个循环因为一次解析异常崩掉。7. 从手写到 SDK循环的开关和上限在你手里手写是为了看懂看懂之后就可以交给 SDK。generateText接的是一次任务不是一次调用。没有工具的时候一次任务刚好等于一次生成带上execute之后干完一个任务可能需要好几轮往返那台while就装进了它内部。你手写的while骨架对应一次generateText调用你手写的callLLM()对应 SDK 内部每个 step 对模型发的那一次请求。对照关系很清楚parse校验、遍历调度、造消息回灌、循环判停这一整圈 SDK 全接管了你只交出execute这个业务函数。stopWhen默认只跑 1 步是因为每转一轮都在花 token花多少得由开发者自己拿主意。写下stopWhen就等于同意它自主转、上限自己定。如果你要长期跑编码类或 Agent 类任务建议把模型接入和额度管理放到 Coding Plan 里统一处理避免每次调试都手动换 Key。接入文档里有完整的参数说明和示例遇到接口层面的问题可以先查文档。想先验证模型对话效果可以直接在模型对话页面试几轮确认模型和参数没问题再写进代码。循环可以不亲手写但它的开关和上限一直在你的代码里。stopWhen你不写它默认只跑一步就停你写了多少步它就最多转多少步。Agent 的自主性不是模型自带的魔法是你代码里那台循环造出来的。

相关推荐

OpenClaw核心揭秘:Agentic Loop如何驱动AI持续思考与行动?TaoToken统一Key接入配置实战
OpenClaw核心揭秘:Agentic Loop如何驱动AI持续思考与行动?TaoToken统一Key接入配置实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 17:26:21

LLM大模型-术语解释-增强技术类
LLM大模型-术语解释-增强技术类

一、什么是 Prompt 工程 Prompt 工程 通过设计输入(提示词),引导 LLM 输出你想要的结果。 模型参数不变,变的是你给它的"前文"。前文不同,预测方向就不同。 1、Prompt 的核心组成2、创建流程 ① 明确目标 →… · 2026/9/26 17:26:15

Longhorn v1.6.0 版本深度解读:V2 数据引擎进阶、快照空间管理与跨平台部署
Longhorn v1.6.0 版本深度解读:V2 数据引擎进阶、快照空间管理与跨平台部署

云原生存储高可用容器编排 【免费下载链接】longhorn Cloud-Native distributed storage built on and for Kubernetes 项目地址: https://gitcode.com/gh_mirrors/lo/longhorn 点击查看 免费下载 本指南以 Longhorn v1.6.0 官方发布说明(CHANGELOG/CHA… · 2026/9/26 17:26:08

调用 open_ai 报 IndexError: list index out of range?先检查这份 config.toml 骨架
调用 open_ai 报 IndexError: list index out of range?先检查这份 config.toml 骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 18:00:58

Opencode网页端手机卡死排查指南:内存、长连接与模型配置优化
Opencode网页端手机卡死排查指南:内存、长连接与模型配置优化

1. 先把"卡死"这件事拆清楚:你的手机到底死在哪一步Opencode网页端手机版卡死,这问题我在群里看到不下十次了。多数人第一反应是"工具不行",但实际排查下来,大部分锅要分给三拨:手机浏览器的内存管… · 2026/9/26 18:00:58

Next.js全栈开发实战:从环境搭建到部署上线的完整指南
Next.js全栈开发实战:从环境搭建到部署上线的完整指南

如果你最近在投简历或者准备做个人项目,一定绕不开“Next.js全栈开发”这个词。别把它想得多高深,本质上它就是把React前端、Node后端、数据库操作打包进一个框架里,让你用一套技术栈把网页从数据库一路写到浏览器。这篇博文就是我从零开始用… · 2026/9/26 18:00:58

Next.js全栈开发实战:从App Router到数据库认证一体化
Next.js全栈开发实战:从App Router到数据库认证一体化

从 2016 年开始用 React 写前端,到后来因为项目需要开始碰 Node、数据库、部署,我最大的感受是:全栈开发从来不缺框架,缺的是把“端到端”这件事做成一套工程方案的工具。直到我把整个产品用 Next.js 全栈开发重写了一遍&#xff… · 2026/9/26 18:00:58

网盘直链解析原理与实操:百度网盘不限速下载技术指南
网盘直链解析原理与实操:百度网盘不限速下载技术指南

1. 网盘直链解析这件事,到底在解决什么问题网盘下载速度这件事,几乎每个用过的人都有一肚子话想说。你上传一个文件很快,分享一个链接也很方便,但轮到下载的时候,尤其是大文件,那个进度条走得让人怀疑人生。… · 2026/9/26 18:00:58

四足机器人建模与步态规划:用 TaoToken 统一 Key 打通仿真配置链路
四足机器人建模与步态规划:用 TaoToken 统一 Key 打通仿真配置链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 18:00:51

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码