news 2026/9/13 1:31:34

如何用 AI SDK 在 SvelteKit 项目中完成第一次流式聊天 Agent 开发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何用 AI SDK 在 SvelteKit 项目中完成第一次流式聊天 Agent 开发

如何用 AI SDK 在 SvelteKit 项目中完成第一次流式聊天 Agent 开发

【免费下载链接】aiThe AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents项目地址: https://gitcode.com/GitHub_Trending/ai/ai

这篇文章带你完成一个具体任务:从零创建一个 SvelteKit 应用,用 AI SDK 的ai包和 Svelte 绑定@ai-sdk/svelte搭出一个带流式输出的聊天 Agent(agent)——后端是 SvelteKit 的 server endpoint,前端是Chat类驱动的聊天界面,并进一步给 Agent 加上工具调用(tool calling)。

开始前需要满足以下条件(均来自官方 Svelte quickstart):

  • 本地已安装 Node.js 22+ 和 pnpm;
  • 一个 Vercel AI Gateway API key(在 Vercel 网站注册后可获取),用于通过 AI Gateway 访问多家模型提供商的模型。

如果你还不熟悉 Prompt Engineering 或 HTTP Streaming 这两个概念,可以先读仓库里的 Streaming 文档 再动手。

创建 SvelteKit 项目并安装依赖

先创建一个新的 SvelteKit 应用,命令会生成名为my-ai-app的目录并初始化基础项目结构:

npx sv create my-ai-app cd my-ai-app

然后在项目目录中安装 AI SDK 的ai包(AI SDK 核心)、@ai-sdk/svelte(Svelte 绑定)和zod(用于定义工具输入 schema 的校验库):

pnpm add -D ai @ai-sdk/svelte zod

这里有一个前提要说明:AI SDK 的 Vercel AI Gateway provider 随ai包一起提供,因此默认方案只需安装上面三个包。这个 quickstart 选择 AI Gateway 是因为一个 API key 就能访问多个提供商的数百个模型;如果你想换成其他提供商,安装对应的 provider 包即可,例如@ai-sdk/openai

配置 AI Gateway API key

在项目根目录创建.env.local并填入你的 API key:

touch .env.local
AI_GATEWAY_API_KEY=xxxxxxxxx

xxxxxxxxx替换成你实际的 Vercel AI Gateway API key。

一个 SvelteKit/Vite 特有的细节:AI SDK 的 Gateway provider 默认读取AI_GATEWAY_API_KEY环境变量,但 Vite 不会自动把环境变量挂到process.env上,所以代码里需要从$env/static/private导入这个 key,而不是读process.env

创建流式聊天 API 路由

新建 SvelteKit Endpoint 文件src/routes/api/chat/+server.ts,写入以下内容:

import { streamText, type UIMessage, convertToModelMessages, createGateway, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { AI_GATEWAY_API_KEY } from '$env/static/private'; const gateway = createGateway({ apiKey: AI_GATEWAY_API_KEY, }); export async function POST({ request }) { const { messages }: { messages: UIMessage[] } = await request.json(); const result = streamText({ model: gateway('anthropic/claude-sonnet-4.5'), messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); }

这段代码做了四件事:

  1. createGateway创建 gateway provider 实例;
  2. POST处理函数中从请求体取出messagesUIMessage[],即包含时间戳等元数据的完整对话历史);
  3. 调用streamText,传入modelmessages。注意streamTextmessages参数期望的是ModelMessage[],它不含 UI 元数据,所以要先用convertToModelMessagesUIMessage[]转换过去;
  4. streamText返回的StreamTextResult.stream交给toUIMessageStream,再用createUIMessageStreamResponse包成流式响应返回给客户端。

如果编辑器里对AI_GATEWAY_API_KEYPOST函数报类型错误,直接运行一次 dev server 即可消除。

模型与 provider 的写法

quickstart 用的是gateway('anthropic/claude-sonnet-4.5')这种字符串模型引用(AI Gateway 是默认的 global provider)。文档还给出两种等价的显式写法:

// Option 1: 从 'ai' 包导入(默认包含) import { gateway } from 'ai'; model: gateway('anthropic/claude-sonnet-4.5'); // Option 2: 安装并从 '@ai-sdk/gateway' 包导入 import { gateway } from '@ai-sdk/gateway'; model: gateway('anthropic/claude-sonnet-4.5');

要直接接入其他提供商,以 OpenAI 为例:

pnpm add @ai-sdk/openai
import { openai } from '@ai-sdk/openai'; model: openai('gpt-5.1');

以上只是文档给出的 provider 替换示例,换成哪个提供商取决于你自己安装的 provider 包和支持的模型,本文主路径继续使用 AI Gateway。

接入前端聊天界面

更新根页面src/routes/+page.svelte

<script lang="ts"> import { Chat } from '@ai-sdk/svelte'; let input = ''; const chat = new Chat({}); function handleSubmit(event: SubmitEvent) { event.preventDefault(); chat.sendMessage({ text: input }); input = ''; } </script> <main> <ul> {#each chat.messages as message, messageIndex (messageIndex)} <li> <div>{message.role}</div> <div> {#each message.parts as part, partIndex (partIndex)} {#if part.type === 'text'} <div>{part.text}</div> {/if} {/each} </div> </li> {/each} </ul> <form onsubmit={handleSubmit}> <input bind:value={input} /> <button type="submit">Send</button> </form> </main>

这里用到的关键 API:

  • Chat类(来自@ai-sdk/svelte)把聊天接口的复杂度收敛到一个类里,其属性与 API 大体和 React 的useChat相同。new Chat({})默认使用你前面创建的POST路由;
  • chat.messages是当前消息数组,每个消息对象带idroleparts属性;
  • chat.sendMessage负责把用户消息发送到聊天 API;
  • 模型输出通过message.parts数组访问:每条消息包含一个有序的parts数组,按模型生成的顺序排列文本、推理 token 等内容,前端按顺序渲染即可实现逐段流式显示。

启动并验证

启动应用:

pnpm run dev

在浏览器打开 http://localhost:5173 。你应该能看到一个输入框;输入一条消息发送后,聊天界面会以实时流式方式显示模型的回复——这是本次任务的基础成功条件。

给 Agent 添加工具调用(可选扩展)

基础聊天跑通后,文档建议的下一步是让 Agent 具备工具调用能力:工具(tools)是 LLM 可以调用的动作,动作结果会回传给模型,供下一轮生成参考。例如用户问天气时,模型调用weather工具拿到结果,再据此回答。

更新src/routes/api/chat/+server.ts,加入一个返回随机温度(文档中用于模拟,非真实天气数据)的weather工具,并开启stopWhen

import { createGateway, streamText, type UIMessage, convertToModelMessages, tool, isStepCount, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { z } from 'zod'; import { AI_GATEWAY_API_KEY } from '$env/static/private'; const gateway = createGateway({ apiKey: AI_GATEWAY_API_KEY, }); export async function POST({ request }) { const { messages }: { messages: UIMessage[] } = await request.json(); const result = streamText({ model: gateway('anthropic/claude-sonnet-4.5'), messages: await convertToModelMessages(messages), stopWhen: isStepCount(5), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); }

几个要点:

  • inputSchema用 Zod 定义工具入参(此处要求一个location字符串),模型会从对话上下文中提取该输入;提取不到时会向用户询问缺失信息;
  • execute是运行在服务器端的异步函数,实际项目中可以在这里请求真实的外部 API;
  • stopWhen默认为isStepCount(1),即有工具结果时第一轮结束后就停止生成,模型不会拿工具结果继续作答。改成stopWhen: isStepCount(5)后,模型最多可用 5 个 step,能把工具结果回送给自己继续生成,直到满足停止条件。

同步更新src/routes/+page.svelte,让界面渲染工具调用部分:

<script lang="ts"> import { Chat } from '@ai-sdk/svelte'; let input = ''; const chat = new Chat({}); function handleSubmit(event: SubmitEvent) { event.preventDefault(); chat.sendMessage({ text: input }); input = ''; } </script> <main> <ul> {#each chat.messages as message, messageIndex (messageIndex)} <li> <div>{message.role}</div> <div> {#each message.parts as part, partIndex (partIndex)} {#if part.type === 'text'} <div>{part.text}</div> {:else if part.type === 'tool-weather'} <pre>{JSON.stringify(part, null, 2)}</pre> {/if} {/each} </div> </li> {/each} </ul> <form onsubmit={handleSubmit}> <input bind:value={input} /> <button type="submit">Send</button> </form> </main>

验证方式:刷新页面后输入 "What's the weather in New York?"。你会看到模型生成的是工具调用而非文本(此时界面上对应消息位置没有文本是正常的,因为模型输出的是 tool call),工具调用与结果以tool-weatherpart 显示在message.parts中——工具 part 的命名规则固定为tool-{toolName},即定义工具时用的 key。由于已设置stopWhen: isStepCount(5),模型接下来会用天气工具的结果回答你的原始问题。

文档还演示了一个可选的多步验证:再加一个convertFahrenheitToCelsius工具(入参为华氏温度数字,返回摄氏温度),并让 UI 同时处理tool-convertFahrenheitToCelsiuspart。此时问 "What's the weather in New York in celsius?",预期观察到四步交互:模型调用 weather 工具 → 显示工具输出 → 调用温度转换工具 → 用自然语言综合回答。这一步只是对多步工具调用的演示,完成基础聊天不需要。

Svelte 绑定与 React 版的行为差异

@ai-sdk/svelte@ai-sdk/react的表层差异是 Svelte 用类管理状态(Chat)而 React 用 hook(useChat)。有两个 Svelte 特有的坑会直接影响你第一次跑的聊天界面,来自 Svelte quickstart 的说明:

  • 类构造参数默认不是响应式的。Svelte 组件的script块只在组件创建时执行一次,所以给Chat传动态值时要传引用而不是值,例如用 getter:

    <script> import { Chat } from '@ai-sdk/svelte'; let { id } = $props(); // 不会更新:id 按值拷贝,类实例只创建一次 let chat = new Chat({ id }); // 正确:通过 getter 传引用,Chat 始终拿到最新值 let chat = new Chat({ get id() { return id; }, }); </script>
  • 不能对类的属性做解构let { messages } = chat;会按值拷贝一次并和实例断开,后续sendMessage后这个messages不再变化;必须始终通过chat.messages读取。

另外,如果需要多个Chat实例之间像 React 的useChat一样按id同步状态,可以在根 layout 中调用createAIContext();多数初次开发用不到这个能力。

限制与下一步

  • 本文的 provider 路径以 Vercel AI Gateway 为例,需要对应的 API key;更换 provider 时需安装对应 provider 包并改用其 provider 实例(如openai('gpt-5.1'))。
  • weather工具的execute返回的是文档中用于演示的随机温度,不是真实天气;接入真实数据时由你在execute中自行请求外部 API。

完整代码与更多细节可以对照仓库中的 Svelte quickstart 原文;工具机制的完整说明见 Tools 文档,模型与 provider 的选择背景见 Providers and Models。

【免费下载链接】aiThe AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents项目地址: https://gitcode.com/GitHub_Trending/ai/ai

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 1:30:51

Claude与OpenAI大模型API核心技术对比与工程实践

1. 核心能力对比&#xff1a;Claude与OpenAI的基因差异Claude和OpenAI虽然都是当前领先的大模型API服务&#xff0c;但两者的技术路线和擅长领域存在显著差异。经过半年多的生产环境实测&#xff0c;我发现这种差异会直接影响开发效率和应用效果。1.1 文本处理能力的实测对比在…

作者头像 李华
网站建设 2026/9/13 1:26:27

Python+Django构建高并发校园食堂点餐系统

1. 项目背景与核心价值校园食堂点餐系统是每个高校信息化建设中不可或缺的一环。传统的人工排队点餐方式存在诸多痛点&#xff1a;高峰时段排队时间长、人工结算效率低、菜品信息不透明、订单管理混乱等。这套基于PythonDjango的解决方案&#xff0c;正是为了解决这些实际问题而…

作者头像 李华
网站建设 2026/9/13 1:26:17

有机婴幼儿食品品牌Once Upon a Farm的成功案例分析

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

作者头像 李华
网站建设 2026/9/13 1:24:04

告别逆向破解:合规路径下的点赞数据分析实战指南

做内容运营这几年&#xff0c;我有个特别深的体会&#xff1a;点赞数据是判断流量质量最直接的指标之一&#xff0c;但真想把它分析透的时候&#xff0c;第一步就容易卡住——打开抓包工具一看&#xff0c;抖音这类App的每个请求后面都挂着一串加密参数&#xff0c;abogus、as、…

作者头像 李华