Vercel AI SDK 实战:用 React + Next.js 搭建前端驱动的 Agent Harness 1. 为什么前端驱动的 Agent Harness 值得折腾Vercel AI SDK 是一个前端原生的 LLM 应用开发工具包它把流式响应、工具调用、多轮调度、状态同步这些原本要写在后端的活封装成了 React Hooks 和 Route Handler。Agent Harness 说白了就是 Agent 的“运行托管框架”——大模型通信、工具调度、记忆管理、错误重试这些通用能力它全包了你只写业务逻辑。适合谁有 React/Next.js 基础、想快速做出能跑能演示的 LLM Agent、又不想先啃一遍 Python 后端框架的前端开发者。我见过太多人卡在同一个地方想做个能查文档、能跑代码、能记住上下文的智能助手结果教程一打开全是 FastAPI LangChain 向量库光环境就劝退。其实用 Next.js App Router 加 Vercel AI SDK一个route.ts加一个page.tsx就能把流式聊天、工具调用、多轮状态跑通。这篇就按“能复制、能启动、能验证”的路线走重点放在可复制的配置和真实会遇到的报错上而不是概念堆砌。核心检索词先明确Vercel AI SDK 在 React/Next.js 里构建前端驱动 Agent Harness覆盖流式响应、工具调用与状态管理。下面所有代码都围绕这条链路模型接入统一走 OpenAI 兼容协议Base URL 指向https://taotoken.net/api这样你换模型只改一个 Model ID。2. TaoToken 前置把模型接入这层先铺好在写 Agent 之前得先解决“模型从哪来”的问题。Vercel AI SDK 本身不绑定某一家模型它通过 Provider 适配不同服务。最省事的做法是用 OpenAI 兼容协议因为ai-sdk/openai这个包支持自定义baseURL只要你的服务兼容/v1/chat/completions和流式返回就能直接接。TaoToken 在这里扮演的是模型接入层你拿到一个 API Key 和一个 Base URL就能在 Next.js 的 Route Handler 里调用多种模型不用为每个模型写一套适配代码。对前端驱动 Agent Harness 来说这层越薄越好因为你的精力应该花在工具定义和状态管理上。先做三件事。第一注册并登录控制台创建 API Key。第二确认你要用的 Model ID比如gpt-4o、claude-3-5-sonnet这类具体以控制台模型列表为准。第三把 Key 写进.env.local绝对不要写进前端组件。# .env.local OPENAI_API_KEY你的_TaoToken_Key OPENAI_BASE_URLhttps://taotoken.net/api这里有个坑要提前说很多人把OPENAI_BASE_URL写成带/v1的地址结果 SDK 又拼了一次/v1变成/v1/v1/chat/completions直接 404。TaoToken 的 API 地址是https://taotoken.net/apiSDK 内部会补/v1所以环境变量里不要自己加。如果你还没建 Key可以直接去 API Keys 页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi_keys 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc 里面有各语言的调用示例遇到协议细节可以对照。这一步做完你手里应该有三样东西Base URL、API Key、Model ID。这三件套后面在route.ts里会反复出现缺一个都跑不起来。3. 可复制配置Next.js 路由与组件一次配齐这一节是全文最该抄的部分。目标是把 Agent Harness 的骨架搭起来一个 Edge Route Handler 负责模型调用和工具调度一个客户端组件负责流式渲染和状态管理。先初始化项目并装依赖npx create-next-applatest ai-agent-harness --typescript --tailwind --app --eslint cd ai-agent-harness npm install ai ai-sdk/react ai-sdk/openai zod版本上ai和ai-sdk/react建议用 3.x 以上ai-sdk/openai同版本线。装完检查package.json避免ai和ai-sdk/react大版本不一致导致 Hook 找不到。3.1 模型 Provider 配置在app/api/chat/route.ts里用createOpenAI自定义 baseURL// app/api/chat/route.ts import { createOpenAI } from ai-sdk/openai; import { streamText, tool } from ai; import { z } from zod; export const runtime edge; const provider createOpenAI({ apiKey: process.env.OPENAI_API_KEY, baseURL: process.env.OPENAI_BASE_URL, // https://taotoken.net/api }); const MODEL_ID gpt-4o; // 换成控制台里可用的 Model ID这段就是三件套的落点Base URL 走环境变量Key 走环境变量Model ID 单独抽成常量方便切换。runtime edge让这个 Handler 跑在边缘环境冷启动快适合流式请求。3.2 工具定义与 Agent 调度Agent Harness 的核心是工具。用tool()加 Zod schema 定义参数大模型按 schema 生成参数SDK 负责校验和执行const tools { getWeather: tool({ description: 查询指定城市的天气用户问天气时调用, parameters: z.object({ city: z.string().describe(城市名例如 上海), }), execute: async ({ city }) { // 这里替换成真实天气 API return { city, temp: 26, condition: 多云 }; }, }), runJs: tool({ description: 运行一段 JavaScript 代码并返回结果, parameters: z.object({ code: z.string().describe(合法的 JS 代码片段), }), execute: async ({ code }) { try { const result eval(code); // 演示用生产环境务必用沙箱 return { ok: true, result: String(result) }; } catch (e: any) { return { ok: false, error: e.message }; } }, }), };description写得越具体模型判断“什么时候该调这个工具”就越准。我试过把 description 写成“查询天气”模型经常在闲聊时也去调改成“用户问天气时调用”之后误触发明显下降。3.3 流式响应与多轮工具调用export async function POST(req: Request) { const { messages } await req.json(); const result streamText({ model: provider(MODEL_ID), system: 你是一个前端技术助手。需要实时信息或计算时调用工具拿到结果后再回答不要编造。, messages, tools, maxSteps: 5, // 允许多轮工具调用 temperature: 0.7, }); return result.toDataStreamResponse(); }maxSteps是 Agent Harness 的关键参数。它控制“模型调用工具 → 拿到结果 → 再决定是否继续调用”这个循环最多跑几轮。设成 1 就只能调一次工具设成 5 能处理“先查天气再查机票”这类多步任务。别设太大否则模型可能陷入循环白白烧 token。3.4 客户端组件与状态管理// app/page.tsx use client; import { useChat } from ai-sdk/react; export default function Page() { const { messages, input, handleInputChange, handleSubmit, isLoading, stop } useChat({ api: /api/chat }); return ( div classNamemax-w-3xl mx-auto p-4 div classNamespace-y-4 mb-6 {messages.map((m) ( div key{m.id} className{m.role user ? text-right : } div classNameinline-block p-3 rounded-lg bg-gray-100 whitespace-pre-wrap {m.content} /div /div ))} /div form onSubmit{handleSubmit} classNameflex gap-2 input value{input} onChange{handleInputChange} classNameflex-1 border p-2 rounded placeholder问点什么比如上海天气怎么样 / button typesubmit disabled{isLoading} {isLoading ? 生成中 : 发送} /button {isLoading button typebutton onClick{stop}停止/button} /form /div ); }useChat把消息列表、输入值、加载状态、提交、停止全管了。你不用自己写useState管 messages也不用处理 SSE 解析。这就是前端驱动 Agent Harness 的爽点状态在组件里交互逻辑在前端模型调用被封装成一个 Hook。4. 验证请求本地启动后确认多轮工具调用真的发生配置写完跑起来验证。执行npm run dev打开http://localhost:3000。第一轮验证流式响应输入“你好介绍一下你自己”你应该看到文字逐字出现而不是等几秒后整段蹦出来。如果整段蹦出说明流式没生效检查toDataStreamResponse()有没有返回以及有没有中间层缓冲。第二轮验证工具调用输入“上海现在天气怎么样”。预期行为是模型先决定调用getWeatherSDK 执行工具拿到{temp: 26, condition: 多云}模型再基于结果组织回答。你可以在execute里加一行console.log(tool called, city)在终端看到打印就证明工具真的被调用了而不是模型自己编的。第三轮验证多轮调度输入“帮我算一下 (12 8) * 3然后告诉我上海天气”。这个请求需要先调runJs再调getWeather。如果maxSteps设得够你会看到两次工具执行最后模型把两个结果合并成一段回答。这一步是 Agent Harness 和普通聊天机器人的分水岭。第四轮验证状态管理发几条消息后刷新页面消息会清空——因为useChat默认不持久化。这正常。要持久化得把 messages 存到 localStorage 或后端 KV这是下一步的事不影响当前链路验证。验证模型本身是否通可以单独去模型对话页面发一条消息https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodels 。如果那边能通、这边不通问题多半在代码配置而不是 Key。5. 本篇常见错排查401、local proxy failed、reading choices这一节按真实报错来。下面这几个是我和身边人踩过的对照着查能省不少时间。401 Unauthorized / invalid api key。最常见的原因是 Key 没读到。检查.env.local里变量名和代码里process.env.OPENAI_API_KEY是否一致改完.env.local必须重启npm run devNext.js 不会热更新环境变量。还有一种情况是 Key 复制时带了空格或换行用console.log(process.env.OPENAI_API_KEY?.length)确认长度合理。local proxy failed / fetch failed。这个报错通常出现在 Edge Runtime 里发起外部请求失败。先确认OPENAI_BASE_URL是https://taotoken.net/api没有多余斜杠。再确认你的网络环境能正常访问该地址。如果本地开发环境有额外的网络层可能导致 Edge Runtime 的 fetch 走不通可以临时把runtime edge去掉用 Node.js runtime 跑一遍对比定位是环境问题还是代码问题。reading choices / Cannot read properties of undefined。这个报错说明 SDK 拿到的响应结构不是预期的 OpenAI 格式。常见原因是 Base URL 指错了请求打到了某个返回 HTML 的地址SDK 解析 JSON 失败。检查baseURL是否精确到/api以及 Model ID 是否是控制台里真实存在的。Model ID 写错有时不会立刻 401而是返回一个错误结构SDK 解析时就报choices读不到。工具调用不触发 / 模型一直闲聊。先看工具的description是否足够明确再看maxSteps是否大于 1。如果maxSteps是默认值且你没显式设置某些版本下多轮调度不会自动开启。另外system prompt 里明确写“需要实时信息时调用工具”比不写要有效得多。流式变成一次性返回。检查有没有在 Route Handler 外面包了缓冲逻辑或者用了某些会聚合响应的中间件。toDataStreamResponse()必须直接 return不要先await result.text()再自己拼 Response。OAuth / 认证相关报错。如果你用的是需要 OAuth 的模型服务注意 Vercel AI SDK 的 Provider 配置和普通 API Key 不同。本篇走的是 API Key 模式遇到 OAuth 报错说明你接错了 Provider回到createOpenAI这条线即可。排障时如果怀疑是 Key 或接入层的问题直接去 API Keys 页面重新生成一个对比测试https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi_keys 。接入细节对照文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc 。6. 语义一致 CTA把这条链路继续往下走到这里一个前端驱动的 Agent Harness 已经能跑流式响应有了工具调用有了多轮调度有了状态管理交给useChat。接下来往哪走取决于你的目标。如果你只是想验证模型通不通、换个 Model ID 看看效果直接去模型对话页面发消息最快https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodels 。如果你要把这套东西长期用于编码或 Agent 类任务比如接 Claude Code、做持续的工具调用工作流那 Coding Plan 更合适额度和稳定性按长期使用设计https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding_plan 。如果你要管理多个 Key、看用量、给不同项目分配不同额度控制台在这里https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentconsole 。最后留一个实用技巧把MODEL_ID抽成环境变量NEXT_PUBLIC_MODEL_ID之外的服务端变量这样你在本地可以快速切换模型对比工具调用的准确率而不用改代码重新部署。Agent Harness 的调优很多时候就是换模型加改 description 这两件事的反复。