【免费下载链接】chatbot-template
A minimal chatbot template built with Next.js, AI SDK, shadcn/ui, shadcn/react, shadcn/typeset. It runs on the Vercel AI Gateway.
文章开始
chatbot-template是一个基于Next.js + AI SDK构建的最小化流式聊天机器人模板,开箱即用地支持流式输出(Streaming)、工具调用(Tool Calling)、网页搜索和"人在回路"问卷交互,模型接入由Vercel AI Gateway统一代理。这篇文章带你深入它的核心实现:一条消息从点击发送到逐字渲染,中间到底经历了什么。
如果你正在学习 AI SDK 的流式聊天架构,这个项目的代码量足够小、结构足够清晰,是理解"前后端流式数据流"的绝佳范例。
一、项目概览:一次聊天用到哪些技术?
chatbot-template 的技术栈可以概括为"四层":
| 层次 | 技术 | 作用 |
|---|---|---|
| 框架层 | Next.js(App Router) | 提供/api/chat流式接口路由与页面 |
| AI 层 | ai(AI SDK)+ Vercel AI Gateway | 统一接入多家模型,流式输出 |
| 交互层 | @ai-sdk/react的useChat | 前端聊天状态管理与流式订阅 |
| UI 层 | shadcn/ui + shadcn/typeset | 消息气泡、问卷、Markdown 排版 |
项目结构非常扁平,核心文件只有这几个:
- app/api/chat/route.ts —— 流式聊天接口,整个后端的"心脏"
- components/chat.tsx —— 前端聊天主组件,
useChat的调用处 - tools/index.ts —— 工具注册中心,定义聊天机器人"能做什么"
- components/chat-message.tsx —— 消息渲染分发器,把不同类型的"零件"交给对应组件
依赖清单见 package.json,AI 相关的关键依赖有:ai(核心 SDK)、@ai-sdk/react(React 集成)、@ai-sdk/gateway(AI Gateway 接入)。
二、核心数据流:一条消息的完整旅程
理解这个项目,最重要的是先建立一张"数据流地图"。一次聊天的完整链路如下:
前端发送 → Next.js 路由校验 →streamText调用模型 → 工具执行(多轮循环) → UI 消息流 → 前端逐段渲染
拆开来看:
- 前端:用户在输入框提交消息,components/prompt-form.tsx 调用
sendMessage,HTTP 请求发往/api/chat,同时把当前选中的模型 ID 放在请求体里。 - 路由层:app/api/chat/route.ts 校验模型白名单、校验消息结构,然后调用
streamText发起流式生成。 - 模型层:
streamText通过 AI Gateway 调用真实模型,返回的不是完整文本,而是一个流(Stream)——token 产生多少就推送多少。 - 工具循环:如果模型决定调用工具(查仓库、搜索网页、问用户),流中会插入工具调用片段,工具执行完结果再喂回模型继续生成,最多循环 5 步。
- 前端渲染:流被转换成 "UI Message Stream",
useChat按"零件(part)"粒度消费——文本块、工具调用块、引用来源块,各自渲染成不同的 UI 组件。
用户输入 │ ▼ prompt-form.tsx ──sendMessage──▶ /api/chat (route.ts) │ validateUIMessages ▼ streamText ──▶ AI Gateway ──▶ 模型 │ (tool call? 是 → 执行工具 → 回喂,≤5步) ▼ toUIMessageStream (UI 消息流) ▼ useChat 订阅 ──▶ chat-message.tsx 按 part 分发渲染三、后端核心:streamText流式输出是怎么写安全的
app/api/chat/route.ts 只有 70 行,却覆盖了生产级流式接口该做的所有防御,值得逐点看:
1. 请求体容错先try/catch解析 JSON,失败直接返回400,不会让整个服务崩掉。
2. 模型白名单模型列表集中在 lib/models.ts,路由里用isModelAllowed(modelId)拦截,用户传任意模型 ID 都没用。想换模型或加模型,改这一个文件即可。
3. 消息结构校验validateUIMessages在信任任何消息之前,逐条校验消息和工具片段的结构是否合法——这是防注入、防脏数据的关键一步。
4. 流式参数控制(见 app/api/chat/route.ts)
| 参数 | 作用 |
|---|---|
stopWhen: isStepCount(5) | 限制"模型↔工具"最多循环 5 步,防止无限递归烧钱 |
maxOutputTokens: 8192 | 限制单次输出长度,控制成本上限 |
abortSignal: req.signal | 用户关页面/停止生成时,立即中断上游请求 |
5. 流式响应封装最终通过createUIMessageStreamResponse+toUIMessageStream把原始 token 流封装为UI 消息流,并开启sendSources: true——这样网页搜索的引用来源也能随流推给前端。
💡 架构要点:
streamText的返回值是一个惰性流,不是"等模型说完再返回"。这正是聊天机器人"逐字蹦出来"体验的技术基础。
四、工具调用(Tool Calling):机器人的三种"超能力"
工具定义在 tools/ 目录,一个文件一个工具,文件名就是模型看到的工具名。tools/index.ts 负责组合注册:
const baseTools = { github_repo: githubRepo, ask_user: askUser } // 若当前模型支持,再追加 web_search1️⃣ github_repo —— 服务端执行的典型工具
tools/github_repo.ts 用zod定义了输入(owner/name格式的仓库名)和输出(stars、forks、语言等)的 Schema,execute里调用 GitHub API 查公开数据,并内置了5 秒超时+ 流中断信号合并,保证工具不会"卡死"整个聊天流。
2️⃣ web_search —— 各厂商原生搜索工具
tools/web_search.ts 展示了"工具随模型而变"的写法:OpenAI 模型用openai.tools.webSearch(),Anthropic 模型用其对应的搜索工具。这种按模型 ID 前缀分发的策略,让一套前端适配了不同厂商的原生能力。
3️⃣ ask_user —— 唯一"不在服务端执行"的工具
tools/ask_user.ts 定义了输入(若干问题 + 每题恰好 3 个选项)和输出(问答对),但没有execute函数——因为它的"执行者"是屏幕前的用户。这个设计是全文最巧妙的部分,下一节细说。
五、前端渲染:消息 parts 与"状态机"思维
AI SDK 里,一条助手消息不是纯文本,而是一组有序的类型化零件(parts)。components/chat-message.tsx 的核心就是一个switch (part.type)分发器:
| part 类型 | 渲染组件 | 界面效果 |
|---|---|---|
text | text-part.tsx | Markdown 富文本(react-markdown + typeset) |
tool-github_repo | github-repo-part.tsx | 先转圈 "Looking up…",再显示 stars/forks 统计条 |
tool-web_search | web-search-part.tsx | "Searching the web…" → "Searched the web" |
tool-ask_user | ask-user-part.tsx | 已回答的问答内联展示 |
source-url | sources-part.tsx | 流结束后聚合为 "Searched N websites" 抽屉 |
更有意思的是每个工具零件都实现了状态机:
input-streaming → input-available → output-available └→ output-error以 web-search-part.tsx 为例:流刚开始推送工具输入时显示"正在搜索…",执行完切换为"已搜索",出错则显示错误文案。一个组件,四种状态,随流自动演进——这就是流式 UI 与普通轮询 UI 的本质区别。
六、Human-in-the-loop:ask_user 问卷如何"打断"又"续传"对话
这是 chatbot-template 最有教学价值的功能:当用户的问题含糊不清时,模型会主动发起问卷,等用户答完再继续。
实现分三步:
第一步:模型发起提问模型调用ask_user工具,流中插入tool-ask_user零件。components/chat.tsx 检测到存在状态为input-streaming或input-available的待答问题,就在滚动区底部吸顶弹出问卷卡片 components/question-card.tsx——用户可点选选项,也可自由输入。
第二步:用户作答提交后调用addToolOutput(见 components/chat.tsx),把答案作为工具输出回传。因为ask_user没有服务端execute,用户的操作就是那次"执行"。
第三步:自动续传components/chat.tsx 中有一个关键配置:
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls它让前端在"最后一条助手消息已完成且带工具调用"时自动重发请求,把工具结果回喂给模型。用户答完问卷,对话无需任何手动操作就继续流式生成。
🎯 总结这个模式:省略 execute + 前端自动重发 = 人在回路(Human-in-the-loop)。这是 AI SDK 里把"用户"变成"工具执行器"的标准范式。
七、快速上手:本地运行 chatbot-template
三步跑起来:
# 1. 安装依赖 pnpm install # 2. 配置 AI Gateway 凭证(二选一) vercel link && vercel env pull # 从已链接的 Vercel 项目拉取 OIDC token # 或在 Vercel 控制台 AI Gateway → API Keys 创建密钥,写入 .env.local 的 AI_GATEWAY_API_KEY # 3. 启动 pnpm dev⚠️安全提醒(详见 README.md 的 Security 章节):/api/chat是公开无鉴权接口,每个请求都消耗你的 AI Gateway 额度。上真实流量前务必加限流、设置 Gateway 消费上限、或加登录鉴权。
八、如何扩展:给自己加一个新工具
想加工具(比如查天气),只需两步,类型系统全程护航:
- 新建 tools/ 下
weather.ts,用tool()导出描述、inputSchema和execute,再到 tools/index.ts 注册; - 在 components/parts/ 加一个零件组件,并在 chat-message.tsx 加一个
case "tool-weather"。
由于消息类型通过InferUITools从工具定义自动推导,你在零件组件里能拿到完整类型提示——改错字段名直接编译报错,而不是运行时悄悄变成undefined。
总结
chatbot-template 用极小的代码量,完整演示了一个现代流式聊天机器人的架构骨架:
- 后端:
streamText+ 流式响应封装 + 白名单/限步/限 token/可中断,构成安全的流式接口范式(app/api/chat/route.ts) - 工具层:一文件一工具、Schema 描述输入输出、
execute与"人在回路"分离(tools/) - 前端:
useChat消费 UI 消息流,parts 状态机驱动渐进式 UI(components/chat-message.tsx) - 范式:省略
execute+sendAutomaticallyWhen实现 Human-in-the-loop 问卷交互(tools/ask_user.ts)
如果你想给自己的产品加一个 AI 聊天能力,直接以它为起点改造,是最短的路径。🚀
【免费下载链接】chatbot-template
A minimal chatbot template built with Next.js, AI SDK, shadcn/ui, shadcn/react, shadcn/typeset. It runs on the Vercel AI Gateway.
相关推荐
揭秘Awesome ChatGPT:深入理解AI聊天机器人核心架构与实现原理
揭秘Awesome ChatGPT:深入理解AI聊天机器人核心架构与实现原理 在人工智能快速发展的今天,ChatGPT作为最受欢迎的AI聊天机器人之一,其背后的
SonataAdminBundle 与工作流集成:实现复杂业务状态管理
SonataAdminBundle 与工作流集成:实现复杂业务状态管理 SonataAdminBundle 作为 Symfony 的强大管理后台生成器,提供了丰
10分钟上手现代AI聊天机器人:GitHub_Trending/ai/ai-chatbot架构全解析
10分钟上手现代AI聊天机器人:GitHub_Trending/ai/ai chatbot架构全解析 你是否还在为构建AI聊天机器人时的技术选型而烦恼?是否想快
AI 应用前端后端交互助手
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考