[{"data":1,"prerenderedAt":969},["ShallowReactive",2],{"content:\u002F2026\u002Fvercel-ai-sdk-getting-started":3,"surround:\u002F2026\u002Fvercel-ai-sdk-getting-started":963},{"id":4,"title":5,"body":6,"categories":920,"date":922,"description":923,"draft":924,"extension":761,"image":925,"meta":926,"navigation":928,"path":929,"permalink":925,"published":925,"readingTime":930,"recommend":925,"references":935,"seo":950,"sitemap":951,"stem":952,"tags":953,"type":960,"updated":961,"__hash__":962},"content\u002Fposts\u002F2026\u002Fvercel-ai-sdk-getting-started.md","Vercel AI SDK 入门：从 0 搭一个会流式回复的聊天页",{"type":7,"value":8,"toc":905},"minimark",[9,13,16,33,36,84,88,91,94,97,109,180,205,209,212,222,225,244,247,255,258,266,269,326,329,333,339,347,350,359,362,368,377,381,386,395,398,444,447,453,465,472,476,482,491,494,521,530,533,541,545,548,556,559,567,570,606,610,619,660,663,671,677,680,690,697,702,711,714,717,752,755,765,768,875,878,881,884,893,896,902],[10,11,12],"p",{},"第一次做 AI 应用，最容易卡住的地方通常不是模型本身，而是一堆新名词同时扑过来：Provider、stream、tool calling、message parts、Route Handler、model messages、UI messages。像刚走进后厨，灶台、锅、出餐口全在响，你只是想先煮一碗能吃的面。",[10,14,15],{},"Vercel AI SDK 解决的正是这个问题。它把「怎么调模型」和「怎么把模型输出接到前端」这两件事拆开，又用一套比较统一的接口把它们接回去。你不用一开始就手写 SSE，也不用自己维护一堆模型供应商的适配逻辑。先让一个聊天页跑起来，再加工具调用、结构化输出、鉴权和监控，会稳很多。",[17,18,21],"alert",{"title":19,"type":20},"先记住一句话","info",[10,22,23,24,28,29,32],{},"Vercel AI SDK 是一套 TypeScript AI 应用工具箱。",[25,26,27],"code",{"code":27},"ai"," 包负责模型调用和流式输出，",[25,30,31],{"code":31},"@ai-sdk\u002Freact"," 负责前端聊天状态，Provider 包或 Vercel AI Gateway 负责连接具体模型服务。",[10,34,35],{},"可以先把它想成三层，后面的代码基本都在这三层之间传递消息：",[37,38,39],"card-list",{},[40,41,42,61,71],"ul",{},[43,44,45,49,50,53,54,53,57,60],"li",{},[46,47,48],"strong",{},"AI SDK Core","：模型层。",[25,51,52],{"code":52},"streamText","、",[25,55,56],{"code":56},"generateText",[25,58,59],{"code":59},"tool"," 这些 API 都在这里，负责把 prompt 或 messages 送进模型，再拿回文本、对象或流。",[43,62,63,66,67,70],{},[46,64,65],{},"AI SDK UI","：界面层。",[25,68,69],{"code":69},"useChat"," 负责输入框、消息列表、发送状态和流式更新，少写很多重复的 React 状态代码。",[43,72,73,76,77,53,80,83],{},[46,74,75],{},"Provider \u002F Gateway","：连接层。",[25,78,79],{"code":79},"@ai-sdk\u002Fopenai",[25,81,82],{"code":82},"@ai-sdk\u002Fanthropic"," 或 Vercel AI Gateway 把统一调用翻译成具体模型服务能听懂的请求。",[85,86,87],"h2",{"id":87},"你会做出什么",[10,89,90],{},"这篇不追求一口气讲完整个 AI SDK。我们只做一条最小但完整的链路：用户在页面输入一句话，后端调用模型，前端把回复一点点渲染出来。",[10,92,93],{},"能跑通这条链路，比先看十页概念更重要。因为一旦你知道消息怎么从浏览器走到模型、又怎么回来，后面加工具、加 RAG、加日志，都只是沿着这条路继续铺。",[10,95,96],{},"项目结构大概是这样：",[98,99,106],"pre",{"className":100,"code":102,"filename":103,"language":104,"meta":105},[101],"language-text","my-ai-app\u002F\n├─ app\u002F\n│  ├─ page.tsx\n│  └─ api\u002Fchat\u002Froute.ts\n├─ .env.local\n└─ package.json\n","最小项目结构","text","icon=tabler:folder-code",[25,107,102],{"__ignoreMap":108},"",[110,111,112,125],"table",{},[113,114,115],"thead",{},[116,117,118,122],"tr",{},[119,120,121],"th",{},"文件",[119,123,124],{},"作用",[126,127,128,142,155,165],"tbody",{},[116,129,130,136],{},[131,132,133],"td",{},[25,134,135],{"code":135},"app\u002Fpage.tsx",[131,137,138,139,141],{},"前端聊天界面，使用 ",[25,140,69],{"code":69}," 管输入、发送和消息渲染",[116,143,144,149],{},[131,145,146],{},[25,147,148],{"code":148},"app\u002Fapi\u002Fchat\u002Froute.ts",[131,150,151,152,154],{},"后端 Route Handler，使用 ",[25,153,52],{"code":52}," 调模型",[116,156,157,162],{},[131,158,159],{},[25,160,161],{"code":161},".env.local",[131,163,164],{},"放 API Key，不能提交到 Git",[116,166,167,172],{},[131,168,169],{},[25,170,171],{"code":171},"package.json",[131,173,174,175,53,177,179],{},"安装 ",[25,176,27],{"code":27},[25,178,31],{"code":31},"、Provider 包等依赖",[17,181,184],{"title":182,"type":183},"版本提醒","warning",[10,185,186,187,189,190,193,194,189,196,193,199,189,201,204],{},"我重新查了一下 npm：现在 ",[25,188,27],{"code":27}," 是 ",[25,191,192],{"code":192},"7.0.18","，",[25,195,31],{"code":31},[25,197,198],{"code":198},"4.0.19",[25,200,79],{"code":79},[25,202,203],{"code":203},"4.0.9","。AI SDK 更新很快，复制代码前最好顺手看一眼官方文档。本文按 AI SDK 7 的写法整理。",[85,206,208],{"id":207},"第一步创建-nextjs-项目","第一步：创建 Next.js 项目",[10,210,211],{},"先准备 Node.js 22+ 和 pnpm。官方 quickstart 也建议用 pnpm，这里就不折腾别的包管理器了。",[98,213,220],{"className":214,"code":216,"filename":217,"language":218,"meta":219},[215],"language-bash","pnpm create next-app@latest my-ai-app\ncd my-ai-app\n","创建项目","bash","icon=tabler:terminal wrap",[25,221,216],{"__ignoreMap":108},[10,223,224],{},"创建项目时，建议这样选：",[37,226,227],{},[40,228,229,235,238,241],{},[43,230,231,232,234],{},"App Router：选 yes。本文用的就是 ",[25,233,148],{"code":148},"。",[43,236,237],{},"TypeScript：选 yes。AI SDK 的类型提示能帮你少踩不少坑。",[43,239,240],{},"Tailwind CSS：选 yes。不是必须，但写一个舒服点的聊天页会快很多。",[43,242,243],{},"src 目录：看个人习惯。选不选都不影响 AI SDK。",[10,245,246],{},"然后安装 AI SDK 相关依赖：",[98,248,253],{"className":249,"code":250,"filename":251,"language":218,"meta":252},[215],"pnpm add ai @ai-sdk\u002Freact zod\n","安装依赖","icon=tabler:package wrap",[25,254,250],{"__ignoreMap":108},[10,256,257],{},"如果你要直接调用 OpenAI，再装 Provider 包：",[98,259,264],{"className":260,"code":261,"filename":262,"language":218,"meta":263},[215],"pnpm add @ai-sdk\u002Fopenai\n","使用 OpenAI Provider","icon=tabler:brand-openai wrap",[25,265,261],{"__ignoreMap":108},[10,267,268],{},"这里有两条常见路线：",[110,270,271,287],{},[113,272,273],{},[116,274,275,278,281,284],{},[119,276,277],{},"路线",[119,279,280],{},"安装",[119,282,283],{},"环境变量",[119,285,286],{},"适合谁",[126,288,289,307],{},[116,290,291,294,299,304],{},[131,292,293],{},"Vercel AI Gateway",[131,295,296],{},[25,297,298],{"code":298},"ai @ai-sdk\u002Freact zod",[131,300,301],{},[25,302,303],{"code":303},"AI_GATEWAY_API_KEY",[131,305,306],{},"想用一个入口管理多个模型",[116,308,309,312,318,323],{},[131,310,311],{},"直接 Provider",[131,313,314,315,317],{},"再加 ",[25,316,79],{"code":79}," 等",[131,319,320,317],{},[25,321,322],{"code":322},"OPENAI_API_KEY",[131,324,325],{},"已经确定只接某家模型服务",[10,327,328],{},"新手我建议先选一条路。先跑通，再抽象。很多奇怪 bug 都来自「我两个方案都配了一半」。",[85,330,332],{"id":331},"第二步写环境变量","第二步：写环境变量",[10,334,335,336,338],{},"在项目根目录创建 ",[25,337,161],{"code":161},"：",[98,340,345],{"className":341,"code":342,"filename":343,"language":218,"meta":344},[215],"cp \u002Fdev\u002Fnull .env.local\n","创建环境变量文件","icon=tabler:file-plus",[25,346,342],{"__ignoreMap":108},[10,348,349],{},"如果走 Vercel AI Gateway：",[98,351,357],{"className":352,"code":354,"filename":161,"language":355,"meta":356},[353],"language-dotenv","AI_GATEWAY_API_KEY=你的_key\n","dotenv","icon=tabler:key",[25,358,354],{"__ignoreMap":108},[10,360,361],{},"如果直接走 OpenAI Provider：",[98,363,366],{"className":364,"code":365,"filename":161,"language":355,"meta":356},[353],"OPENAI_API_KEY=你的_key\n",[25,367,365],{"__ignoreMap":108},[17,369,372],{"title":370,"type":371},"别把 Key 放到前端","error",[10,373,374,376],{},[25,375,161],{"code":161}," 不要提交到 Git。前端组件也不要直接读取模型 Key。浏览器里出现的东西，用户都能看到；把 Key 放进去，基本等于把门禁卡贴在楼下。",[85,378,380],{"id":379},"第三步写后端-route-handler","第三步：写后端 Route Handler",[10,382,383,384,338],{},"先看 Gateway 写法。新建 ",[25,385,148],{"code":148},[98,387,393],{"className":388,"code":390,"filename":148,"language":391,"meta":392},[389],"language-ts","import {\n  convertToModelMessages,\n  createUIMessageStreamResponse,\n  streamText,\n  toUIMessageStream,\n  type UIMessage,\n} from 'ai'\n\nexport async function POST(req: Request) {\n  const { messages }: { messages: UIMessage[] } = await req.json()\n\n  const result = streamText({\n    model: 'xai\u002Fgrok-build-0.1',\n    messages: await convertToModelMessages(messages),\n  })\n\n  return createUIMessageStreamResponse({\n    stream: toUIMessageStream({ stream: result.stream }),\n  })\n}\n","ts","icon=tabler:route wrap",[25,394,390],{"__ignoreMap":108},[10,396,397],{},"这段代码不长，但里面有几个动作很关键：",[399,400,401,404,414,417,424,427,432,435],"timeline",{},[10,402,403],{},"{1. 接收 UI messages}",[10,405,406,407,410,411,234],{},"前端发来的 ",[25,408,409],{"code":409},"messages"," 不只是纯文本。它们还带着角色、分段内容和 UI 侧需要的元数据，类型是 ",[25,412,413],{"code":413},"UIMessage[]",[10,415,416],{},"{2. 转成 model messages}",[10,418,419,420,423],{},"模型不需要前端元数据，所以用 ",[25,421,422],{"code":422},"convertToModelMessages"," 转成模型能处理的消息格式。",[10,425,426],{},"{3. 调用 streamText}",[10,428,429,431],{},[25,430,52],{"code":52}," 会发起模型调用，并返回一个可以继续往外传的文本流。聊天场景优先用它，因为用户能更早看到内容开始出现。",[10,433,434],{},"{4. 返回 UIMessage Stream}",[10,436,437,440,441,443],{},[25,438,439],{"code":439},"createUIMessageStreamResponse"," 把模型输出包装成 ",[25,442,69],{"code":69}," 能读取的响应格式。前端不用关心底层流协议，只管更新消息。",[10,445,446],{},"如果你走 OpenAI Provider，Route Handler 可以改成这样：",[98,448,451],{"className":449,"code":450,"filename":148,"language":391,"meta":263},[389],"import { openai } from '@ai-sdk\u002Fopenai'\nimport {\n  convertToModelMessages,\n  createUIMessageStreamResponse,\n  streamText,\n  toUIMessageStream,\n  type UIMessage,\n} from 'ai'\n\nexport async function POST(req: Request) {\n  const { messages }: { messages: UIMessage[] } = await req.json()\n\n  const result = streamText({\n    model: openai('gpt-5.1'),\n    messages: await convertToModelMessages(messages),\n  })\n\n  return createUIMessageStreamResponse({\n    stream: toUIMessageStream({ stream: result.stream }),\n  })\n}\n",[25,452,450],{"__ignoreMap":108},[17,454,457],{"title":455,"type":456},"为什么不直接 return result？","question",[10,458,459,461,462,464],{},[25,460,52],{"code":52}," 更偏模型层，",[25,463,69],{"code":69}," 需要的是 UI 消息流。中间这一层转换有点像后厨把菜做好，再按外卖盒的格式装好：菜是同一道菜，但前端需要按它认识的格式拆开。",[10,466,467,468,471],{},"实际项目里，模型名最好抽到环境变量里。教程为了少一点干扰，先写死在代码里；等跑通后再改成 ",[25,469,470],{"code":470},"MODEL_ID"," 会更稳。",[85,473,475],{"id":474},"第四步写前端聊天页","第四步：写前端聊天页",[10,477,478,479,481],{},"把 ",[25,480,135],{"code":135}," 改成下面这样：",[98,483,489],{"className":484,"code":486,"filename":135,"language":487,"meta":488},[485],"language-tsx","'use client'\n\nimport { useChat } from '@ai-sdk\u002Freact'\nimport { useState } from 'react'\n\nexport default function Chat() {\n  const [input, setInput] = useState('')\n  const { messages, sendMessage } = useChat()\n\n  return (\n    \u003Cmain className=\"mx-auto flex min-h-screen w-full max-w-2xl flex-col gap-4 px-4 py-10\">\n      \u003Ch1 className=\"text-3xl font-semibold tracking-tight\">\n        My AI Chat\n      \u003C\u002Fh1>\n\n      \u003Cdiv className=\"flex-1 space-y-4 rounded-lg border p-4\">\n        {messages.map(message => (\n          \u003Cdiv key={message.id} className=\"whitespace-pre-wrap\">\n            \u003Cdiv className=\"mb-1 text-sm text-zinc-500\">\n              {message.role === 'user' ? 'User' : 'AI'}\n            \u003C\u002Fdiv>\n\n            {message.parts.map((part, index) => {\n              if (part.type === 'text') {\n                return (\n                  \u003Cdiv key={`${message.id}-${index}`}>\n                    {part.text}\n                  \u003C\u002Fdiv>\n                )\n              }\n              return null\n            })}\n          \u003C\u002Fdiv>\n        ))}\n      \u003C\u002Fdiv>\n\n      \u003Cform\n        className=\"flex gap-2\"\n        onSubmit={(event) => {\n          event.preventDefault()\n          if (!input.trim())\n            return\n          sendMessage({ text: input })\n          setInput('')\n        }}\n      >\n        \u003Cinput\n          className=\"flex-1 rounded-md border px-3 py-2 outline-none focus:ring-2 focus:ring-black\u002F20\"\n          value={input}\n          placeholder=\"问点什么，比如：用一句话解释 AI SDK\"\n          onChange={event => setInput(event.currentTarget.value)}\n        \u002F>\n        \u003Cbutton className=\"rounded-md bg-black px-4 py-2 text-white\">\n          Send\n        \u003C\u002Fbutton>\n      \u003C\u002Fform>\n    \u003C\u002Fmain>\n  )\n}\n","tsx","icon=tabler:brand-react wrap",[25,490,486],{"__ignoreMap":108},[10,492,493],{},"这里最值得注意的是两处：",[37,495,496],{},[40,497,498,511],{},[43,499,500,502,503,506,507,510],{},[25,501,409],{"code":409},"：当前聊天记录。AI SDK 7 的消息内容通过 ",[25,504,505],{"code":505},"message.parts"," 渲染，不要再假设每条消息只有一个 ",[25,508,509],{"code":509},"content"," 字符串。",[43,512,513,516,517,520],{},[25,514,515],{"code":515},"sendMessage","：把用户输入发给 ",[25,518,519],{"code":519},"\u002Fapi\u002Fchat","。默认路径就是刚才写的 Route Handler。",[10,522,523,525,526,529],{},[25,524,505],{"code":505}," 乍看有点绕，但它是合理的。模型以后不一定只返回纯文本，还可能返回 reasoning、tool call、file、source 等片段。",[25,527,528],{"code":528},"parts"," 就像一排托盘格子，不同类型的输出按顺序放进去，前端再决定每一格怎么展示。",[10,531,532],{},"完整链路可以这样读：",[98,534,539],{"className":535,"code":536,"filename":537,"language":104,"meta":538},[101],"用户输入\n  ↓\nuseChat 发送 POST \u002Fapi\u002Fchat\n  ↓\nRoute Handler 读取 messages\n  ↓\nconvertToModelMessages 转成模型消息\n  ↓\nstreamText 调用模型\n  ↓\nUIMessage Stream 持续返回\n  ↓\nmessage.parts 逐段渲染到页面\n","流式聊天链路","icon=tabler:route",[25,540,536],{"__ignoreMap":108},[85,542,544],{"id":543},"第五步跑起来","第五步：跑起来",[10,546,547],{},"启动开发服务器：",[98,549,554],{"className":550,"code":551,"filename":552,"language":218,"meta":553},[215],"pnpm dev\n","启动开发环境","icon=tabler:player-play",[25,555,551],{"__ignoreMap":108},[10,557,558],{},"打开：",[98,560,565],{"className":561,"code":562,"filename":563,"language":104,"meta":564},[101],"http:\u002F\u002Flocalhost:3000\n","浏览器地址","icon=tabler:world",[25,566,562],{"__ignoreMap":108},[10,568,569],{},"如果配置没问题，你输入一句话后，回复会一点点冒出来。这个体验和一次性等完整结果不太一样：总耗时可能差不多，但页面开始动起来以后，人会明显更安心。",[571,572,574,603],"folding",{"title":573},"如果它没跑起来，先看这几个地方",[575,576,577,582,591,594,600],"ol",{},[43,578,579,581],{},[25,580,161],{"code":161}," 里有没有 Key，变量名是不是写对了。",[43,583,584,585,587,588,234],{},"改完 ",[25,586,161],{"code":161}," 后有没有重启 ",[25,589,590],{"code":590},"pnpm dev",[43,592,593],{},"Route Handler 里的模型名是否在当前 Provider 或 Gateway 里可用。",[43,595,596,597,599],{},"浏览器 Network 里 ",[25,598,519],{"code":519}," 是否返回 200。",[43,601,602],{},"终端有没有鉴权、配额、区域网络之类的错误。",[10,604,605],{},"第一轮调 AI 应用，很多问题都不神秘：Key 写错、模型名不可用、环境变量没重启。先查这些，别急着怀疑框架。",[85,607,609],{"id":608},"generatetext-和-streamtext-怎么选","generateText 和 streamText 怎么选",[10,611,612,613,615,616,618],{},"AI SDK Core 里最常见的是 ",[25,614,56],{"code":56}," 和 ",[25,617,52],{"code":52},"。名字很直白，但使用场景不一样。",[110,620,621,634],{},[113,622,623],{},[116,624,625,628,631],{},[119,626,627],{},"函数",[119,629,630],{},"适合场景",[119,632,633],{},"返回方式",[126,635,636,648],{},[116,637,638,642,645],{},[131,639,640],{},[25,641,56],{"code":56},[131,643,644],{},"标题、摘要、分类、短文本改写",[131,646,647],{},"等模型完整生成后一次性返回",[116,649,650,654,657],{},[131,651,652],{},[25,653,52],{"code":52},[131,655,656],{},"聊天、长文本、需要实时显示的输出",[131,658,659],{},"边生成边返回",[10,661,662],{},"比如你只是要生成一个标题：",[98,664,669],{"className":665,"code":666,"filename":667,"language":391,"meta":668},[389],"import { generateText } from 'ai'\n\nconst { text } = await generateText({\n  model: 'xai\u002Fgrok-build-0.1',\n  prompt: '给一篇 Vercel AI SDK 入门教程起一个中文标题',\n})\n\nconsole.log(text)\n","generate-title.ts","icon=tabler:sparkles wrap",[25,670,666],{"__ignoreMap":108},[10,672,673,674,676],{},"聊天页更适合 ",[25,675,52],{"code":52},"。用户发出消息后，系统马上开始吐字，比盯着空白区域等最终答案舒服很多。流式输出不一定让模型更快，但会让等待更可控。",[85,678,679],{"id":679},"加一点系统提示词",[10,681,682,683,685,686,689],{},"现在模型还在自由发挥。你可以在 ",[25,684,52],{"code":52}," 里加 ",[25,687,688],{"code":688},"system","，让它更像你的产品，而不是一个没有边界的万能聊天框：",[98,691,695],{"className":692,"code":693,"filename":148,"language":391,"meta":694},[389],"const result = streamText({\n  model: 'xai\u002Fgrok-build-0.1',\n  system: '你是一个简洁、耐心的中文编程助手。回答要短，但关键步骤不能省。',\n  messages: await convertToModelMessages(messages),\n})\n","icon=tabler:message-cog wrap",[25,696,693],{"__ignoreMap":108},[10,698,699,701],{},[25,700,688],{"code":688}," 不要写成一份公司制度。太长的提示词会互相打架，模型也未必抓得住重点。先写三五句，跑一段真实对话，再根据输出慢慢修。",[17,703,705],{"title":704,"type":183},"Prompt 不是权限系统",[10,706,707,708,710],{},"你可以在 system 里写「不要泄露密钥」，但真正的安全要靠代码：Key 不进前端，工具不随便读 ",[25,709,161],{"code":161},"，用户输入不直接拼进危险命令。Prompt 是方向盘，不是保险柜。",[85,712,713],{"id":713},"下一步可以加什么",[10,715,716],{},"跑通聊天只是第一关。AI SDK 后面能接的东西很多，但别一口气把工具、RAG、图片、语音、agent 全倒进去。先让主链路稳定，再一层一层加。",[399,718,719,722,734,737,740,743,746,749],{},[10,720,721],{},"{第一阶段：纯聊天}",[10,723,724,725,53,727,53,729,615,731,733],{},"先掌握 ",[25,726,69],{"code":69},[25,728,52],{"code":52},[25,730,409],{"code":409},[25,732,505],{"code":505},"。这一步看似基础，其实决定了后面所有功能怎么接。",[10,735,736],{},"{第二阶段：结构化输出}",[10,738,739],{},"用 schema 让模型返回固定结构，比如分类、表单字段、JSON 数据。适合做后台任务和半自动流程。",[10,741,742],{},"{第三阶段：工具调用}",[10,744,745],{},"让模型在需要时调用函数，比如查数据库、读文档、创建工单。这里开始要认真处理权限、日志和失败重试。",[10,747,748],{},"{第四阶段：产品化}",[10,750,751],{},"补上鉴权、限流、成本统计、错误提示、模型切换和评估。能 demo 不等于能上线，这一步最容易被低估。",[10,753,754],{},"新手可以先做几个小练习：",[98,756,763],{"className":757,"code":759,"filename":760,"language":761,"meta":762},[758],"language-md","1. 把 system prompt 改成「苏格拉底式老师」，让它少直接给答案，多追问。\n2. 给页面加一个「清空对话」按钮。\n3. 把模型名抽到环境变量里，比如 MODEL_ID=xai\u002Fgrok-build-0.1。\n4. 在 Route Handler 里捕获错误，给前端返回更友好的提示。\n","练习清单","md","icon=tabler:checklist",[25,764,759],{"__ignoreMap":108},[85,766,767],{"id":767},"常见坑",[110,769,770,783],{},[113,771,772],{},[116,773,774,777,780],{},[119,775,776],{},"坑",[119,778,779],{},"症状",[119,781,782],{},"处理",[126,784,785,804,820,834,847,864],{},[116,786,787,793,798],{},[131,788,789,790],{},"忘了 ",[25,791,792],{"code":792},"'use client'",[131,794,795,797],{},[25,796,69],{"code":69}," 报错或页面无法交互",[131,799,800,801,803],{},"在 ",[25,802,135],{"code":135}," 顶部加上",[116,805,806,809,814],{},[131,807,808],{},"Key 写错",[131,810,811,813],{},[25,812,519],{"code":519}," 返回 401\u002F403",[131,815,816,817,819],{},"检查 ",[25,818,161],{"code":161}," 和 Provider 文档",[116,821,822,825,828],{},[131,823,824],{},"改 env 不重启",[131,826,827],{},"明明改了 Key，还是报旧错误",[131,829,830,831,833],{},"停掉 ",[25,832,590],{"code":590}," 重新启动",[116,835,836,839,842],{},[131,837,838],{},"消息渲染空白",[131,840,841],{},"后端有返回，页面没显示",[131,843,844,845],{},"改成遍历 ",[25,846,505],{"code":505},[116,848,849,852,855],{},[131,850,851],{},"API 路径不一致",[131,853,854],{},"前端发不到后端",[131,856,857,858,860,861,863],{},"默认用 ",[25,859,519],{"code":519},"，自定义时配置 ",[25,862,69],{"code":69}," transport",[116,865,866,869,872],{},[131,867,868],{},"模型不支持",[131,870,871],{},"Provider 报 model not found",[131,873,874],{},"换可用模型，或确认 Gateway 权限",[10,876,877],{},"这张表可以先留着。第一次接 AI SDK，很多调试时间都花在这些小地方；它们不高级，但足够烦人。",[85,879,880],{"id":880},"总结",[10,882,883],{},"Vercel AI SDK 入门的重点不是记住所有 API，而是先把主链路跑通。",[10,885,886,887,889,890,892],{},"浏览器里，",[25,888,69],{"code":69}," 管输入、发送和消息渲染；Next.js Route Handler 里，",[25,891,52],{"code":52}," 调模型；Provider 或 Gateway 把请求送到真正的模型服务；UIMessage Stream 再把结果一点点送回页面。",[10,894,895],{},"这条路通了，你再加工具调用、结构化输出、RAG 或 agent 编排，心里会有一张地图。否则一开始就把所有能力堆上去，调试时很容易分不清到底是前端状态、后端格式、模型权限，还是流式协议出了问题。",[897,898],"link-card",{"description":899,"link":900,"title":901},"Core、UI、Providers、Streaming、Agents 都在这里，版本变化时优先看官方文档。","https:\u002F\u002Fai-sdk.dev\u002Fdocs\u002Fintroduction","Vercel AI SDK 官方文档",[10,903,904],{},"先做一条能稳定来回的消息链路。它看起来朴素，但这是所有 AI 应用的地基。",{"title":108,"searchDepth":906,"depth":906,"links":907},4,[908,910,911,912,913,914,915,916,917,918,919],{"id":87,"depth":909,"text":87},2,{"id":207,"depth":909,"text":208},{"id":331,"depth":909,"text":332},{"id":379,"depth":909,"text":380},{"id":474,"depth":909,"text":475},{"id":543,"depth":909,"text":544},{"id":608,"depth":909,"text":609},{"id":679,"depth":909,"text":679},{"id":713,"depth":909,"text":713},{"id":767,"depth":909,"text":767},{"id":880,"depth":909,"text":880},[921],"开发","2026-07-08 21:56:13","一篇面向新手的 Vercel AI SDK 7 入门教程：先理清 Core、UI 和 Provider，再用 Next.js App Router 跑通一个真正会流式回复的聊天页。",false,null,{"slots":927},{},true,"\u002F2026\u002Fvercel-ai-sdk-getting-started",{"text":931,"minutes":932,"time":933,"words":934},"15 min read",14.575,874500,2915,[936,938,941,944,947],{"title":937,"link":900},"AI SDK introduction",{"title":939,"link":940},"Getting Started: Next.js App Router","https:\u002F\u002Fai-sdk.dev\u002Fdocs\u002Fgetting-started\u002Fnextjs-app-router",{"title":942,"link":943},"AI SDK Core: streamText","https:\u002F\u002Fai-sdk.dev\u002Fdocs\u002Freference\u002Fai-sdk-core\u002Fstream-text",{"title":945,"link":946},"AI SDK UI: useChat","https:\u002F\u002Fai-sdk.dev\u002Fdocs\u002Freference\u002Fai-sdk-ui\u002Fuse-chat",{"title":948,"link":949},"ai package on npm","https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fai",{"title":5,"description":923},{"loc":929},"posts\u002F2026\u002Fvercel-ai-sdk-getting-started",[954,955,956,957,958,959],"AI","Vercel","AI SDK","Next.js","TypeScript","教程","tech","2026-07-09 09:19:46","uegr73rGxlhVZc6KHYcy8_BHgZmZBWrYFXo3ci1Kmko",[964,925],{"title":965,"path":966,"stem":967,"date":968,"type":960,"children":-1},"Deep Research Agent：让 AI 带着小本本去查资料","\u002F2026\u002Fdeep-research-agent","posts\u002F2026\u002Fdeep-research-agent","2026-07-08 21:14:04",1783560682660]