资讯动态

AI SDK 6 实战:Agent 与多模态如何组合落地

发布时间:2026/10/20 20:46:49 来源:尧图企业网站定制
把 Agent 和多模态放在同一篇教程里讲其实不是巧合。这两个能力在 Vercel AI SDK 6 里是互相成就的Agent 让你的应用能主动调用工具、完成任务闭环多模态让你的应用能理解真实世界里图片、音频、文档这些非结构化信息。两者一旦组合起来你就有能力去接很多以前看起来“不现实”的需求——比如用户拍张照片Agent 就能自己判断该查什么、做什么、然后给你一个完整结论。先说清楚这篇适合谁你已经把本系列前两部分过了一遍至少用过generateText和streamText能写简单的 Next.js 接口对 TypeScript 不排斥。如果你只想要 API 文档那官方文档已经足够但如果你想少走弯路这篇文章会把该踩的坑提前给你排掉。我最近在两个项目里同时把 AI SDK 6 拿来做 Agent 加多模态的落地一个偏内部工具一个偏客服场景。实战下来有几个印象非常深的地方后面会全部展开。第三部分和前两部分最大的变化是从一次性问答变成了多轮、可观察、可干预的自主决策流程。这个转变是你从“会调 API”到“能交付完整 AI 功能”的分水岭。1. 老读者直接看这里第三部分到底补了什么1.1 前两篇的终点刚好是第三篇的起点这一系列的第一篇核心是把生成能力跑通generateText、streamText、provider 切换都是围绕“怎么把模型输出稳定地拿到手”。第二篇进入 Chat 和 RAG解决了“多轮对话怎么管”“外部资料怎么进上下文”的问题。但如果你把这两篇的能力拼起来会发现一个明显的缺口所有流程都是你写死的。你告诉模型“先搜资料再回答问题”模型就照做但如果你想让模型自己判断“这单需要查库存、查物流、查退款政策而且查完物流还要根据结果决定要不要继续查退款”代码就没法预先把所有分支写完了。这正是 Agent 要解决的把决策权交给模型让模型在循环里动态选择调用哪个工具、以及是否继续。多模态也一样。前两篇里的消息都是纯文本但真实项目里用户发过来的是截图、录音、PDF。第三篇要补的就是让应用从“只能读文”升级到“能看图听音”。1.2 AI SDK 6 在 Agent 和多模态上的几个关键变化AI SDK 6 相比前代不是简单加几个 API而是把 Agent 和多模态放到了框架的一等位置。我实际用下来几个变化对开发体验影响很大。第一工具调用不再是generateText的附属能力而是被 Agent 框架整体吸收。你可以声明一个 Agent给它名字、指令、工具集然后直接让它去跑一个任务。这个“任务”内部的多轮工具调用循环SDK 帮你托管了。第二多模态输入被统一成标准消息结构。不管是 OpenAI 还是其他家的模型你都可以用同一套 content part 来描述图片、音频、文本。不用再为不同模型适配不同的消息格式。第三步骤级别的观测更友好。Agent 跑的每一轮发生了什么、模型调了什么工具、返回了什么结果都有回调可以拿。这对排查问题太重要了后面我会专门讲。第四provider 生态更完善。图像、音频、文件输入都有对应的模型接口可以接。我用一张表格总结下前代和 6 代的核心差异能力AI SDK 5AI SDK 6工具调用需要手动维护循环Agent 内置循环与状态管理多模态消息各家模型格式不统一统一 content part 结构步骤观测基本无onStepFinish 等生命周期回调Agent 定义无标准写法agent({...}) 一等公民结构化输出靠解析内置 schema 校验与兜底这个变化意味着什么意味着如果你还在用手写while循环去调模型判断要不要调工具那你其实是在把 AI SDK 已经做完的事重新做一遍。2. 构建智能 Agent先想清楚循环再写工具2.1 Agent 不是魔法核心是“感知 - 决策 - 行动 - 观察”的循环很多人一上来就问 Agent 的 API 怎么调我反而建议先想清楚它背后的循环。想象一下你招了个新客服。你不会指望他第一次接电话就能回答所有问题。你会给他一本手册告诉他“遇到查订单的去系统里看遇到退款的先核对政策”。他接电话的完整过程是听用户描述感知→ 判断该查什么决策→ 敲键盘查系统行动→ 看到结果观察→ 再决定是直接回复还是继续查下一个东西。Agent 干的就是这件事。只不过“敲键盘查系统”被抽象成了工具函数“手册”被写进了 instruction。整个循环在 AI SDK 6 内部被封装成了可控的流程模型先接收用户输入然后决定是否需要调用工具如果需要SDK 执行对应函数把结果作为新消息喂回模型模型再决定继续调用还是生成最终答案。如此反复直到终止。我在实际项目里踩过的一个坑是把 Agent 当成一个“更聪明的 prompt 封装”结果工具调用一大堆上下文乱成一团。后来才意识到Agent 的设计重点是“循环里的每一步都要有明确目标”。你在写代码之前应该先在纸上回答这三个问题这个任务最多需要几步才能完成每一步需要哪些信息来自哪个工具什么情况下应该停止调用工具、直接回复用户想清楚这三件事再打开编辑器写代码会顺畅很多。2.2 工具定义质量直接决定 Agent 智商工具是 Agent 唯一的“手脚”它能不能在正确的时候调用正确的工具取决于你怎么描述这个工具。我有一次调试一个 Agent发现模型老是在查天气的时候去调一个“获取当前时间”的工具原因就是我 description 写得太宽泛了模型根本分不清边界。一个合格的工具定义要满足三个条件。第一description 得像是在给另一个工程师写交接文档。这里不要惜字如金要把“这个工具是干什么的”“什么时候用”“什么时候不能用”写清楚。比如// 差的描述 tool({ description: 查询订单, // ... }), // 好的描述 tool({ description: 根据订单号查询订单当前状态。订单号形如 SO-2025-00001。仅当用户能提供完整订单号时调用如果订单号缺失或格式不对不要调用直接向用户索要。, // ... }),第二parameters 用 zod 严格校验并给每个字段写 describe。模型在生成参数时会参考字段说明字段说明写得越具体生成出来的参数越准确。第三execute 要容错、幂等而且返回值要精简。这一点很多人会忽略。工具返回值会原封不动进入上下文如果你返回一个几百 KB 的完整数据表模型的注意力会被稀释成本也会爆炸。我习惯在 execute 里先取数然后只筛选当前决策真正需要的字段返回。比如查订单只返回“状态、预计送达时间、是否支持退款”而不是把整个订单对象抛回去。2.3 上下文管理别把工具返回值当垃圾场Agent 每调一轮工具就会多一轮消息往返。如果工具返回内容很大几轮下来你的上下文窗口就被占满了。我用过一个 Agent前两轮表现还行到第三轮开始“失忆”后来发现是某个工具把一坨 JSON 日志全塞回了上下文。处理办法有三个我实测都有效。工具返回前做字段白名单。只保留下一步决策需要的字段比如{ status: shipped, eta: 2025-06-20 }不要返回{ raw: { ... 大对象 } }。在 instruction 里显式约束模型的复述行为。告诉它“工具返回内容仅供你决策参考最终回复用户时不要原样复述只给结论和关键数据”。必要时允许工具返回“小抄”。如果工具结果本身很大但模型只需要其中一部分可以在 execute 里先把结论算好返回值就是一个精炼的判断结果。比如查库存不返回明细直接返回“库存不足建议补货”这比返回一堆明细列表管用得多。2.4 一个最小可运行的客服 Agent 模板这里给一个可以直接跑的模板。我用的是 AI SDK 6 的 agent 写法假设你已经安装好了ai、ai-sdk/openai、zod。// src/agent/supportAgent.ts import { agent, tool } from ai; import { openai } from ai-sdk/openai; import { z } from zod; export const supportAgent agent({ name: support-agent, model: openai(gpt-4o), instructions: 你是某电商平台的售后客服助理。 工作流程 1. 如果用户提供的凭证图片里有订单号用 getOrderStatus 查订单状态 2. 如果用户询问商品问题用 getOrderItem 查商品明细 3. 如果无法从图片或用户描述中确定订单号不要猜直接请用户提供。 回复用户时只给结论和关键数据不要复述工具返回的原始内容。 , tools: { getOrderStatus: tool({ description: 根据订单号查询订单当前状态。订单号形如 SO-2025-00001。仅当用户能提供完整订单号时调用如果订单号缺失或格式不对不要调用直接向用户索要。, parameters: z.object({ orderId: z.string().describe(订单号例如 SO-2025-00001) }), execute: async ({ orderId }) { const data await fetchOrderStatus(orderId); // 只返回决策需要的最小字段 return { status: data.status, eta: data.eta, canRefund: data.refundPolicy SUPPORTED }; } }), getOrderItem: tool({ description: 查询订单包含的商品清单用于核对售后商品。, parameters: z.object({ orderId: z.string().describe(订单号) }), execute: async ({ orderId }) { const items await fetchOrderItems(orderId); return items.map(item ({ sku: item.sku, name: item.name, price: item.price, qty: item.qty })); } }) } });调用它跑一个任务import { supportAgent } from /agent/supportAgent; const result await supportAgent.generate({ prompt: 用户说订单 SO-2025-00001 的鼠标坏了想申请退款。 }); console.log(result.text);如果你需要在每一步工具调用后记录日志方便排查和复盘可以加一个步骤回调const result await supportAgent.generate({ prompt: 用户说订单 SO-2025-00001 的鼠标坏了想申请退款。, onStepFinish: ({ step, response }) { console.log(本轮调用的工具, step.toolCalls); console.log(工具返回, response.toolResults); } });这套写法注意一点不同小版本的 agent 入参可能略有差异有的版本用messages有的版本用prompt加content以你当前安装的 AI SDK 6.x 文档为准。核心思路是通用的。3. 多模态应用从只能读文到能看图听音3.1 多模态消息结构content 数组背后的设计逻辑AI SDK 6 把多模态输入统一成 content part。一个用户消息不再只是一段字符串而是一个数组里面可以混合文本、图片、音频。顺序是有意义的模型会按顺序理解这些内容。这背后的设计逻辑很朴素真实场景里用户不会只发一张图也不会只发一段字往往是“你看这张截图为什么订单显示已签收但我没收到”。这段消息里图片是主体文字是意图说明。content 数组让这种天然表达方式能原样传给模型。一个典型的多模态消息长这样const content [ { type: text, text: 这张图里的问题怎么处理 }, { type: image, image: data:image/jpeg;base64,/9j/2... } ]; const result await generateText({ model: openai(gpt-4o-mini), messages: [ { role: user, content } ] });这里有个小坑有些模型 API 里 image 接受的是 data URL 字符串有些接受的是Buffer或Uint8Array。AI SDK 的统一层会帮你转换但如果你直接裸调底层 provider就要自己处理这个差异。这也是为什么我推荐尽量别绕过 SDK。3.2 图片输入实操从前端上传到模型识别全路径多模态应用最容易出问题的环节就是图片上传。我见过太多项目卡在“图片传到后端就挂了”问题基本都出在格式转换和体积控制。在前端用户选完图你拿到的是一个File对象。要传给模型最常见的方式是先转成 base64 的 data URL。这段代码可以直接用// 前端File 转 data URL function fileToDataUrl(file: File): Promisestring { return new Promise((resolve, reject) { const reader new FileReader(); reader.onload () resolve(reader.result as string); reader.onerror reject; reader.readAsDataURL(file); }); }然后在提交表单时把它塞进 FormDataconst formData new FormData(); formData.append(image, file); formData.append(message, 请分析这张售后退款凭证); await fetch(/api/agent-support, { method: POST, body: formData });后端收到之后重点来了不要直接把File对象转 base64 就传给模型。你要做三件事校验 mime type、限制体积、必要时压缩尺寸。我实测下来的经验是图片转成 base64 后体积大概膨胀 33%所以一张 3MB 的 jpg 会变成 4MB 左右的字符串。这不仅要命地耗 token还很容易把 Serverless 函数的请求体限制打爆。后面第 5 章我会详细讲这个坑。后端处理代码大致是这样// app/api/agent-support/route.ts import { supportAgent } from /agent/supportAgent; import { NextRequest } from next/server; export const runtime nodejs; export async function POST(req: NextRequest) { const formData await req.formData(); const imageFile formData.get(image) as File | null; const message (formData.get(message) as string) || 请分析这张图片; if (!imageFile) { return Response.json({ error: 缺少图片 }, { status: 400 }); } const buffer Buffer.from(await imageFile.arrayBuffer()); const base64 buffer.toString(base64); const mimeType imageFile.type || image/jpeg; const result await supportAgent.generate({ prompt: message, content: [ { type: text, text: 用户上传了一张图片请根据图片内容回答用户的问题。 }, { type: image, image: data:${mimeType};base64,${base64} } ] }); return Response.json({ text: result.text }); }这里有个非常实际的坑必须设置runtime nodejs。AI SDK 6 的默认运行时在 Edge 下对 Buffer 的支持很麻烦我一开始在 Edge Runtime 下跑直接报错改成 nodejs 才消停。3.3 流式多模态与前端配合多模态输入同样可以配流式输出。用户看图之后模型一个字一个字吐出来体验比干等几秒再一次性出结果好得多。写法不复杂还是老一套import { streamText } from ai; const result streamText({ model: openai(gpt-4o-mini), messages: [ { role: user, content: [ { type: text, text: 这张图上是什么问题 }, { type: image, image: dataUrl } ]} ] }); // 在 Next.js 里直接返回流 return result.toUIMessageStreamResponse();前端用useChat消费就行。如果你是手搓 fetch注意接口返回的 content-type 要设成text/plain; charsetutf-8这样流式解析才不容易出幺蛾子。多模态加流式还有一个额外好处用户看到模型在逐字读图、逐字分析心理上会更愿意等。这不算技术点但用户体验提升是实打实的。3.4 音频、PDF 和文件输入哪些场景真正值得做多模态不只图片。音频输入在会议纪要、客服录音质检场景非常值钱。PDF 文件在合同审核、简历筛选场景也有刚需。先说音频。用法的核心同样是 content partconst content [ { type: text, text: 把这段录音转成文字总结列出待办事项。 }, { type: audio, audio: data:audio/mpeg;base64,... } ];但注意音频模型的支持范围比图片窄。目前主流模型里音频直接理解能力并不是所有 provider 都默认开放。我在内部工具里做录音质检时其实是先转文字再走普通文本的 Agent 流程。如果只是想转写文字那没必要非走多模态模型用专门的语音识别接口更合适。再说 PDF。很多模型并不直接接受 PDF 文件。你传一个 PDF 进去模型要么报错要么只能当作一张图来理解第一页。我的经验是PDF 类需求先转成文本或图片再进多模态链路不要指望模型自己解析整个 PDF。如果 PDF 页数多老老实实拆页、转图、逐页喂或者走文档解析管道转成 Markdown 再进上下文。一句话总结多模态输入不是“模型什么都能吃”而是“模型能吃什么你喂什么”。动手前先确认你的模型支持哪些 content part能省很多排查时间。4. 把 Agent 和多模态装进同一个应用一个可复制的落地模板4.1 场景设计售后凭证图片快查 Agent理论讲完用一个贯穿场景把这些能力串起来。我选的是“售后凭证图片快查 Agent”背景是某电商售后团队每天要处理大量用户上传的凭证截图——物流异常、商品破损、退款申请。这个场景几乎覆盖了本系列的全部能力用户上传图片多模态识别提取订单号Agent 决定查哪张订单、判断是否支持退款最后给出处理建议。整个过程从用户角度看就是“发张图得到一个结论”。我设计的链路是前端用户上传图片加一句描述后端接口接收图片转成 data URLAgent识别图片里的订单号 → 调 getOrderStatus 查状态 → 调 getOrderItem 查商品 → 综合判断是否支持退款 → 输出处理建议返回给用户一段可读的结论4.2 服务端实现API 路由与 Agent 编排服务端我用 Next.js 的 Route Handler。核心代码在第 3.2 节已经给了这里补充 Agent 编排的细节。关键点是让 Agent 的 instruction 里显式说明“图片中可能包含订单号”这样模型才会在收到图片后先去识别再去调工具。否则模型可能忽略图片内容直接问用户要订单号那多模态就白做了。import { agent, tool } from ai; import { openai } from ai-sdk/openai; import { z } from zod; export const afterSalesAgent agent({ name: after-sales-agent, model: openai(gpt-4o), instructions: 你是电商售后审核助手。用户会发来售后凭证截图截图里通常包含订单号。 工作流程 1. 先用多模态能力识别图片中的订单号订单号形如 SO-2025-00001 2. 调用 getOrderStatus 查订单状态 3. 调用 getOrderItem 查订单包含的商品 4. 根据订单是否在售后期内、商品是否支持售后给出退款或换货建议。 注意如果图片里看不清订单号务必向用户索要不要编造订单号。 , tools: { getOrderStatus: tool({ /* ... */ }), getOrderItem: tool({ /* ... */ }) } });调用时把图片和用户描述一起传进去。这一步我已经在前文写过了真正落地时你只需要把supportAgent换成afterSalesAgent就行。4.3 前端实现上传、流程与状态展示前端我建议不要只做一个输入框加按钮。用户上传图片后应该能立即看到预览并且能感知到 Agent“正在处理”的过程而不是干等。我的做法是三步第一步上传前让用户看到预览图并允许重新上传。这一步能挡掉大量误传。第二步提交后展示状态文案比如“正在识别图片…”“正在查询订单…”这些状态其实对应 Agent 的不同工具调用阶段。你可以在后端用onStepFinish把步骤信息通过 SSE 推给前端前端根据步骤类型切换状态文案。第三步最终答案出来之后把 Agent 的结论和工具调用摘要展示出来。结论给用户看工具摘要给运营人员看两边都舒服。这里有个小技巧前端不要把原始File直接预览而是用URL.createObjectURL(file)生成本地预览链接速度更快也不占 base64 长度。const previewUrl URL.createObjectURL(file); // 显示预览 img src{previewUrl} / // 提交时再用 fileToDataUrl 转 base64 const dataUrl await fileToDataUrl(file);4.4 部署到生产环境前必须检查的四个点这个模板在本地跑通不算完上生产之前有四个点必须检查我每个都吃过亏。第一API 密钥必须放 Vercel 环境变量不能打包进前端。这个属于基础安全但真的有人犯错。检查方法很简单前端代码里搜一下有没有sk-开头的字符串。第二图片大小限制。Vercel 的 Serverless 函数默认请求体上限是 4.5MB你传一张 3MB 的原图转 base64 之后就 4MB 左右了再掺上别的表单字段非常容易直接 413。我建议前端在传到服务端之前先压缩一下这里有几个实测值你可以参考原图大小目标尺寸压缩后大小识别效果3MB1280px 宽约 300KB识别准确3MB800px 宽约 180KB大部分场景可接受3MB500px 宽约 100KB小字可能看不清我的经验是1280px 宽、80% 质量是识别效果和体积的平衡点。第三Agent 步数限制。Agent 循环如果没有上限遇到模型抽风会一直调工具消耗会失控。AI SDK 6 支持 maxSteps 之类的限制务必设置比如maxSteps: 5告诉它最多调 5 轮工具就必须给结论。第四函数超时。Vercel 函数默认执行时长只有 10 秒Agent 多轮工具调用很容易超。要么在vercel.json里配置maxDuration提高上限要么把耗时长的 Agent 任务拆成异步队列客户端轮询结果。我自己的做法是简单场景直接同步返回复杂场景走异步任务加轮询。5. 常见问题与排查技巧实录5.1 工具被模型乱调用怎么办症状用户问一句“你好”模型也去调了一遍查订单接口或者明明只需要查物流模型一口气把订单、商品、库存三个工具全调了个遍。原因基本出在工具定义和指令两个地方。指令里没有写清楚“什么时候不要调用工具”或者工具的 description 写得模棱两可。排查时先看步骤日志确认模型是在哪一步开始误调的。然后做两件事给工具 description 加上否定边界。比如“仅当用户能提供完整订单号时调用如果订单号缺失不要调用”这句话看着简单但非常有效。在 instruction 里加一句“除非用户明确要求否则一次只调用一个工具。如果信息不足先向用户提问而不是猜测参数。”工具量也值得检查。如果你一个 Agent 挂了十几个工具模型选择负担会很大。能合并的工具就合并工具数量控制在五个以内准确率会有明显提升。5.2 图片请求过大413 和 Token 超限图片相关报错里高频的是 HTTP 413。这个错误在本地通常很难复现因为本地开发没有 Serverless 的体限制一上生产就炸。413 的本质是请求体超过了平台限制。解决办法是前端压缩而不是后端把限制调大。前面表格里的压缩参数可以直接抄。另一种情况是模型直接报 token 超限。base64 图片会让 token 数量剧增尤其是高分辨率图片。处理办法有两个一是压缩图片尺寸二是如果模型支持图像继续token的近似算法直接传小图。注意不同的provider有差异有些模型只看 token 数不看分辨率有些模型反而对小图识别不友好。你最好用真实业务图做一轮测试不要想当然。5.3 Agent 循环停不下来症状模型反复调用工具永远不输出最终答案或者输出一句空话然后又去调工具。AI SDK 6 给了步数限制这是兜底方案。但根本解法是设计上的工具执行结果要让模型“有条件收手”。我的排查思路是三步看 onStepFinish 日志确认模型每一轮在调什么工具看工具返回结果是不是某轮返回的数据在诱导模型继续查。比如工具返回“订单异常”模型就想去查商品明细查完又想查物流越查越多。遇到这种情况在 instruction 里加明确终止条件“如果已经获取到可以回应用户的信息立即生成最终答案不要继续调用工具。”给 execute 加超时。单工具卡死也能拖垮整个 Agent设置合理的 Promise.race 超时兜底。5.4 结构化输出经常解析失败Agent 跑完结果往往还需要落到业务系统里比如生成一条售后处理记录。你要的是结构化的 JSON但模型偶尔就是会输出一段散文、一段 Markdown或者格式不对的 JSON。AI SDK 6 内置了结构化输出能力。你可以直接声明期望的 schema让模型按 schema 生成import { generateObject } from ai; import { z } from zod; const result await generateObject({ model: openai(gpt-4o-mini), schema: z.object({ orderId: z.string(), decision: z.enum([refund, exchange, reject]), reason: z.string(), refundAmount: z.number().optional() }), prompt: 根据以下对话内容输出处理决策... });用 generateObject 之后解析失败的问题基本绝迹因为校验发生在生成端而不是在客户端事后解析。如果个别 provider 的模型在复杂 schema 下仍出错可以拆小 schema分多次生成一次只输出一个字段组。5.5 换供应商模型后行为差异很大同一套 Agent 代码从 OpenAI 切换到 Google 或者其他家的模型工具调用表现可能完全不一样。这个不是 Bug是模型的“性格”差异。我遇到过的情况A 模型在逻辑判断时更喜欢调工具B 模型更喜欢根据上下文直接推理同一个工具描述A 模型理解得很好B 模型却频繁编造参数。应对策略是收窄描述语气尽量用中性、字面的描述不带太多感情色彩。同时建立一个小的模型测试矩阵把几只模型放进同一套测试集跑一遍看工具调用准确率和最终结论准确率。这套测试矩阵不需要自动化前期手动跑二十条用例就够了。跑完之后你会对每个模型在工具调用上的脾气有底后面选型就不拍脑袋了。我实际把 AI SDK 6 这套 Agent 加多模态组合跑通之后最大的感受是框架帮你省掉的最多的地方其实不是某个 API 好不好调而是让团队里每个人都按同一套消息结构、工具规范和步骤日志去做事排查问题的成本大幅降低。建议你在项目一开始就把 Agent 的工具调用日志记录下来后面比什么都值钱。最后再分享一个小技巧多模态图片识别场景一定不要只丢一张原图给模型。先在前端做一次小图预览并让用户确认既能减少误上传又能顺便把体积压缩了。实测下来这个交互能挡掉一半无效请求识别成功率也高了不少。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑