跳到内容
← 全部文章

vibe coding 一个多模型 Chatbot Agent:从 v0 原型到 Vercel 上线

用 v0 对话式拿到全栈原型,交给 Codex 补齐权限与邀请制,接入多模型,最后部署 Vercel——记录一次完整的 vibe coding 全流程:每一步的爽点、翻车点与沉淀出的方法论。

Neonity11 分钟读完
vibe-codingchatbotnextjsengineering

摘要:这次的目标不是玩具 demo,而是一个「能真给人用」的 Chatbot Agent:有注册登录、有管理员和普通用户两种角色、邀请码邀请制、可切换多种模型,最后部署到 Vercel 上线。全流程走 vibe coding:v0 拿原型,Codex 补工程深度,Vercel 上线。这篇文章记录每一步的爽点、翻车点,以及沉淀出的分工方法论。

先定技术栈:为什么是这个组合

开始之前先说清楚选型,因为后面的每一步都受它影响:

  • Next.js 16 + Vercel AI SDK:一个负责应用骨架,一个负责 LLM 接入。AI SDK 把各家模型的差异抹平成同一个 streamText,这是后面「多模型」能轻松实现的根本原因。
  • Shadcn/ui:UI 组件直接抄,v0 也原生生成它,风格统一省心。
  • Drizzle + Neon:TS 优先的 ORM + serverless Postgres,schema 即代码,适合 AI 生成。
  • Better-auth:比 NextAuth 更「schema 即代码」的 auth 库,角色扩展容易,v0 和 Codex 都很熟。

一句话总结这个组合:全部 TypeScript、全部为 serverless 而生、AI 工具对它们有足够的训练数据。选型本身就是 vibe coding 的一部分——选 AI 最熟的栈,等于给后面的 AI 写码减负。

第一站:v0,半小时拿到能点的原型

v0 的玩法是对话式生成。我的第一条消息大概是这样的:

做一个 Chatbot 应用:左侧会话列表(可新建/删除),中间聊天窗口(流式输出),右侧模型设置面板。技术栈 Next.js 16 + shadcn/ui,样式走简洁的毛玻璃风。数据用 Drizzle 存到 Neon,会话消息要持久化。

几分钟后 v0 给了一整个 Next.js 项目:布局、聊天 UI、useChat 流式接入、Drizzle schema(users/sessions/messages)、Neon 连接示例,一次到位。跑起来之后,聊天能流式、刷新不丢历史——原型验收通过。

v0 的甜区在这里体现得很清楚:它擅长所有「看得见」的部分。UI 长什么样、交互顺不顺、数据流通不通,肉眼一验就知道对不对。

边界也很清楚:一切「看不见的正确性」它都靠不住。v0 给的 Better-auth 配置常常是「样子对、缺细节」——比如注册接口没接数据库、role 字段没落进 user 表、middleware 只挡了未登录没挡未授权。这些不会在原型阶段暴露,但会在生产阶段爆炸。所以我给原型的定位是:可验证的 UI 基线,不是可交付的代码。

第二站:Codex,把原型补成产品

原型到手,接下来是重头戏:权限与邀请制。这两块都是「看不见的正确性」,我全程用 Codex 迭代,但每次改动都 review diff、手动跑两条路径。

多角色权限

Better-auth 的用户表加一个 role 字段,取值 admin / user:

// drizzle/schema.ts
export const users = pgTable('user', {
  id: text('id').primaryKey(),
  name: text('name'),
  email: text('email').notNull().unique(),
  role: text('role', { enum: ['admin', 'user'] }).default('user').notNull(),
  // ...better-auth 需要的其他字段
});

路由保护分两层:middleware.ts 里做「登录 + 角色」判断(/admin/* 仅 admin),API 层再校验一次。第一层管体验,第二层管安全。

邀请制

产品不开放注册,新用户必须用邀请码。于是加一张表:

export const inviteCodes = pgTable('invite_codes', {
  id: text('id').primaryKey(),
  code: text('code').notNull().unique(),      // 8 位随机码
  createdBy: text('created_by').notNull(),    // 哪个管理员生成的
  expiresAt: timestamp('expires_at').notNull(),
  usedAt: timestamp('used_at'),               // 单次使用,用了就作废
});

注册流程变成:填邮箱密码 + 邀请码 → 校验码(未过期、未使用)→ 建用户 + 标记邀请码已用。管理员页可以生成、撤销、看使用情况。

Codex 干活的方式,和翻车点

我把需求写成 todo 丢给 Codex,让它自己读代码、改、跑构建、修 bug。大部分时候它干得漂亮,但有两处必须点名:

  • 角色判断放错了层。它一开始把 admin 判断写在页面组件里,middleware 只判断登录——API 直接裸奔。这类 bug 编译能过、UI 看不出来,只有「换普通用户账号打 API」才暴露。这正是权限代码必须人 review + 双角色手测的原因。
  • 邀请码并发使用。初始实现是「查到未用 → 标记已用」,两个请求同时进来会重复使用同一个码。改成 update ... where usedAt is null 的原子更新才堵住。这种竞态 AI 不会主动想,得靠 review 时提问逼出来。

第三站:接入多模型

单一模型有短板:贵的聪明但慢,便宜的快但笨。既然用户是来用 Agent 的,把选择权交给他们——这也是产品差异点。

抽象是核心

Vercel AI SDK 的 provider 抽象让这件事变得很便宜:

// lib/models.ts —— 模型注册表
import { createOpenAI } from '@ai-sdk/openai';
import { createAnthropic } from '@ai-sdk/anthropic';
import { createGoogleGenerativeAI } from '@ai-sdk/google';

export const providers = {
  openai: createOpenAI({ apiKey: process.env.OPENAI_API_KEY }),
  anthropic: createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY }),
  google: createGoogleGenerativeAI({ apiKey: process.env.GOOGLE_API_KEY }),
};

// modelId → { provider, model },列表存数据库,管理员可配置
export function streamWithModel(modelId: string, messages: UIMessage[]) {
  const { provider, model } = modelRegistry[modelId];
  return streamText({ model: providers[provider](model), messages });
}

接第 2 个模型只花半小时,第 3 个 10 分钟。模型列表存数据库,管理员能开关和排序,UI 上就是个下拉框。多模型的架构红利全在抽象层——没有这个统一入口,接第 3 个模型就是灾难。

翻车点:SDK 抹平了参数,抹不平行为

跨模型最大的坑不是代码,是同一个 system prompt 在不同模型上的表现差异:

  • Claude 对「输出格式要求」最听话,爱用 Markdown 结构。
  • Gemini 对 system prompt 敏感,一句话的表述差异影响很大。
  • OpenAI 的 tool call 最稳,但长上下文容易啰嗦。

结果就是:模型能切换了,但「切了之后回答风格大变」。我的解法是给每个模型配独立的 system prompt 模板,而不是全模型共用一份——配置跟着模型走,而不是跟着产品走。

另一个现实问题是成本:贵的模型用爽了,账单也爽了。所以聊天记录里加了模型维度的用量字段,至少能回答「钱花哪了」。

第四站:部署 Vercel,上线

原型 → 产品 → 多模型,最后一步是让它真的跑在公网上。Vercel 部署本身没难度:push 到 GitHub、import 项目、填环境变量(DATABASE_URL、BETTER_AUTH_SECRET、BETTER_AUTH_URL、各家 API key),自动构建上线。真正的坑全在 serverless 的「环境差异」上:

  1. Neon 连接池。开发时直连没问题,上线后并发一上来连接数就爆。解法是用 Neon 的 pooled 连接串 + drizzle-orm/neon-http 的 serverless 驱动——连接按请求建、用后即焚,冷启动也快。
  2. BETTER_AUTH_URL 忘设成生产域名。部署后登录接口 302 到 localhost,cookie 全废。auth 类库的「环境相关配置」是最容易翻车的点,检查清单必须包含。
  3. 预览部署的登录。每个 PR 一个临时域名,trustedOrigins 不放行的话,预览分支根本登不进去——邀请制产品别人没法体验,等于白部署。

上线前的检查清单我固定五条:邀请码注册全流程、管理员生成/撤销邀请码、普通用户看不到管理入口、模型切换 + 流式输出、Vercel Logs 里无异常。全部过一遍才算上线。

方法论沉淀

一次完整流程走下来,把 vibe coding 的分工想清楚了:

  1. v0 负责「看得见的」,Codex 负责「看不见的」,你负责判断「哪些是看不见的」。UI 肉眼可验,AI 随便写;权限、竞态、环境配置肉眼不可验,必须 review + 测试兜底。
  2. 权限与安全代码,永远不 YOLO。让 AI 写可以,但强制 diff review + 双角色手测。AI 会写出「编译能过、逻辑裸奔」的 auth 代码,这不是它的错,是验证方式的错。
  3. 多模型的架构红利在抽象层,不在代码量。接第 2 个模型才见真章;抽象没建好之前,模型越多越乱。
  4. 部署即验收。serverless 的环境差异(连接池、auth URL、preview 域名)才是上线前最该预演的坑——本地跑得越顺,越要警惕部署后的「环境性翻车」。

小结

这次 vibe coding 全流程给我的最大感受是:工具没有变笨,是分工变清晰了。v0 给视觉与交互的加速度,Codex 给工程深度的加速度,人负责的是「验证」——哪些可以肉眼验、哪些必须测试验、哪些上线前必须预演。把验证想清楚,vibe coding 就不是「让 AI 写代码」,而是「用 AI 的节奏把产品推上线」。

下一篇可以聊聊:多模型路由与成本控制——同样的对话,怎么让便宜的模型先答、贵的模型兜底,把账单打下来。