跳至主要内容

如何在聊天应用程序中使用 AI SDK、Prisma 和 Next.js

20 分钟

介绍

Prisma ORM 通过类型安全的查询简化了数据库访问,当与 Next.jsAI SDK 结合使用时,它为构建具有持久存储的 AI 驱动聊天应用程序奠定了强大的基础。

在本指南中,您将学习如何使用 AI SDK、Next.js 和 Prisma ORM 构建一个聊天应用程序,将聊天会话和消息存储在 Prisma Postgres 数据库中。您可以在 GitHub 上找到本指南的完整示例。

先决条件

  • Node.js 20+
  • OpenAI API 密钥或其他 AI 提供商 API 密钥

1. 设置项目

首先,您需要创建一个新的 Next.js 项目。

npx create-next-app@latest ai-sdk-prisma

它将提示你自定义设置。选择默认值

信息
  • 你想使用 TypeScript 吗?
  • 你想使用 ESLint 吗?
  • 你想使用 Tailwind CSS 吗?
  • 你想将代码放在 src/ 目录中吗?
  • 你想使用 App Router 吗? (推荐)
  • 你想为 next dev 使用 Turbopack 吗?
  • 你想自定义导入别名吗(默认为 @/*)?

导航到项目目录

cd ai-sdk-prisma

2. 安装和配置 Prisma

2.1. 安装依赖项

要开始使用 Prisma,您需要安装一些依赖项

npm install prisma tsx @types/pg --save-dev
npm install @prisma/client @prisma/adapter-pg dotenv pg
信息

如果你使用的是其他数据库提供程序(MySQL、SQL Server、SQLite),请安装相应的驱动程序适配器包,而不是 @prisma/adapter-pg。有关更多信息,请参阅 数据库驱动程序

安装后,在项目中初始化 Prisma

npx prisma init --db --output ../app/generated/prisma
信息

在设置 Prisma Postgres 数据库时,您需要回答几个问题。选择离您最近的区域,并为您的数据库选择一个易于记忆的名称,例如“My Next.js AI SDK Project”

这将创建

  • 一个包含 schema.prisma 文件的 prisma 目录。
  • 一个用于配置 Prisma 的 prisma.config.ts 文件
  • 一个 Prisma Postgres 数据库。
  • 项目根目录中包含 DATABASE_URL.env 文件。
  • output 字段指定生成的 Prisma Client 将存储的位置。

2.2. 定义 Prisma Schema

prisma/schema.prisma 文件中,添加以下模型

prisma/schema.prisma
generator client {
provider = "prisma-client"
output = "../app/generated/prisma"
}

datasource db {
provider = "postgresql"
}

model Session {
id String @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
messages Message[]
}

model Message {
id String @id @default(cuid())
role MessageRole
content String
createdAt DateTime @default(now())
sessionId String
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
}

enum MessageRole {
USER
ASSISTANT
}

这将创建三个模型:SessionMessageMessageRole

2.3 将 dotenv 添加到 prisma.config.ts

要访问 .env 文件中的变量,它们可以由您的运行时加载,或者通过使用 dotenv 加载。在 prisma.config.ts 顶部包含 dotenv 的导入

import 'dotenv/config'
import { defineConfig, env } from 'prisma/config';
export default defineConfig({
schema: 'prisma/schema.prisma',
migrations: {
path: 'prisma/migrations',
},
datasource: {
url: env('DATABASE_URL'),
},
});

2.4. 配置 Prisma Client 生成器

现在,运行以下命令创建数据库表并生成 Prisma Client

npx prisma migrate dev --name init
npx prisma generate

3. 将 Prisma 集成到 Next.js

创建一个 /lib 目录并在其中创建一个 prisma.ts 文件。该文件将用于创建和导出您的 Prisma Client 实例。

mkdir lib
touch lib/prisma.ts

像这样设置 Prisma 客户端

lib/prisma.ts
import { PrismaClient } from "../app/generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";

const adapter = new PrismaPg({
connectionString: process.env.DATABASE_URL!,
});

const globalForPrisma = global as unknown as {
prisma: PrismaClient;
};

const prisma = globalForPrisma.prisma || new PrismaClient({
adapter,
});

if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;

export default prisma;
警告

我们建议使用连接池(如 Prisma Accelerate)来有效地管理数据库连接。

如果您选择不使用一个,请**避免**在长生命周期环境中全局实例化 PrismaClient。相反,请为每个请求创建和处置客户端,以防止耗尽数据库连接。

4. 设置 AI SDK

4.1. 安装 AI SDK 并获取 API 密钥

安装 AI SDK 包

npm install ai @ai-sdk/react @ai-sdk/openai zod

要使用 AI SDK,您需要从 OpenAI 获取 API 密钥。

  1. 导航到 OpenAI API Keys
  2. 点击 Create new secret key
  3. 填写表单
    • 给您的密钥命名,例如 Next.js AI SDK Project
    • 选择 All 访问权限
  4. 点击 Create secret key
  5. 复制 API 密钥
  6. 将 API 密钥添加到 .env 文件中
.env
DATABASE_URL=<YOUR_DATABASE_URL_HERE>
OPENAI_API_KEY=<YOUR_OPENAI_API_KEY_HERE>

4.2. 创建路由处理程序

您需要创建一个路由处理程序来处理 AI SDK 请求。此处理程序将处理聊天消息并将 AI 响应流式传输回客户端。

mkdir -p app/api/chat
touch app/api/chat/route.ts

设置基本路由处理程序

app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText, UIMessage, convertToModelMessages } from 'ai';

export const maxDuration = 300;

export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();

const result = streamText({
model: openai('gpt-4o'),
messages: convertToModelMessages(messages),
});

return result.toUIMessageStreamResponse();
}

此路由处理程序

  1. 从请求正文提取对话历史记录
  2. 将 UI 消息转换为 AI 模型期望的格式
  3. 实时将 AI 响应流式传输回客户端

要将聊天会话和消息保存到数据库,我们需要

  1. 向请求添加会话 id 参数
  2. 在响应中包含 onFinish 回调
  3. idmessages 参数传递给 saveChat 函数(我们接下来会构建它)
app/api/chat/route.ts
import { openai } from "@ai-sdk/openai";
import { streamText, UIMessage, convertToModelMessages } from "ai";
import { saveChat } from "@/lib/save-chat";

export const maxDuration = 300;

export async function POST(req: Request) {
const { messages, id }: { messages: UIMessage[]; id: string } = await req.json();

const result = streamText({
model: openai("gpt-4o"),
messages: convertToModelMessages(messages),
});

return result.toUIMessageStreamResponse({
originalMessages: messages,
onFinish: async ({ messages }) => {
await saveChat(messages, id);
},
});
}

4.3. 创建 saveChat 函数

lib/save-chat.ts 创建一个新文件,用于将聊天会话和消息保存到数据库

touch lib/save-chat.ts

首先,创建一个名为 saveChat 的基本函数,它将用于将聊天会话和消息保存到数据库。

messagesid 参数传入,分别类型为 UIMessage[]string

lib/save-chat.ts
import { UIMessage } from "ai";

export async function saveChat(messages: UIMessage[], id: string) {

}

现在,添加逻辑以使用给定的 id 创建一个会话

lib/save-chat.ts
import prisma from "./prisma";
import { UIMessage } from "ai";

export async function saveChat(messages: UIMessage[], id: string) {
const session = await prisma.session.upsert({
where: { id },
update: {},
create: { id },
});

if (!session) throw new Error("Session not found");
}

添加逻辑以将消息保存到数据库。您将只保存最后两条消息(用户和助手的最后一条消息),以避免任何重叠的消息。

lib/save-chat.ts
import prisma from "./prisma";
import { UIMessage } from "ai";

export async function saveChat(messages: UIMessage[], id: string) {
const session = await prisma.session.upsert({
where: { id },
update: {},
create: { id },
});

if (!session) throw new Error("Session not found");

const lastTwoMessages = messages.slice(-2);

for (const msg of lastTwoMessages) {
let content = JSON.stringify(msg.parts);
if (msg.role === "assistant") {
const textParts = msg.parts.filter((part) => part.type === "text");
content = JSON.stringify(textParts);
}

await prisma.message.create({
data: {
role: msg.role === "user" ? "USER" : "ASSISTANT",
content: content,
sessionId: session.id,
},
});
}
}

此函数

  1. 使用给定的 id upsert(如果不存在则创建)一个会话
  2. 将消息保存到 sessionId 下的数据库中

5. 创建消息 API 路由

app/api/messages/route.ts 创建一个新文件,用于从数据库中获取消息

mkdir -p app/api/messages
touch app/api/messages/route.ts

创建一个基本的 API 路由,用于从数据库中获取消息。

app/api/messages/route.ts
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";

export async function GET() {
try {
const messages = await prisma.message.findMany({
orderBy: { createdAt: "asc" },
});

const uiMessages = messages.map((msg) => ({
id: msg.id,
role: msg.role.toLowerCase(),
parts: JSON.parse(msg.content),
}));

return NextResponse.json({ messages: uiMessages });
} catch (error) {
console.error("Error fetching messages:", error);
return NextResponse.json({ messages: [] });
}
}

6. 创建 UI

app/page.tsx 文件的内容替换为以下内容

app/page.tsx
'use client';

export default function Page() {

}

6.1. 设置基本导入和状态

首先导入所需的依赖项并设置将管理聊天界面的状态变量

app/page.tsx
'use client';

import { useChat } from '@ai-sdk/react';
import { useState, useEffect } from 'react';

export default function Chat() {
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(true);

const { messages, sendMessage, setMessages } = useChat();
}

6.2. 加载现有消息

创建一个 useEffect 钩子,当聊天组件加载时,它将自动获取并显示任何以前保存的消息

app/page.tsx
'use client';

import { useChat } from '@ai-sdk/react';
import { useState, useEffect } from 'react';

export default function Chat() {
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(true);

const { messages, sendMessage, setMessages } = useChat();

useEffect(() => {
fetch('/api/messages')
.then(res => res.json())
.then(data => {
if (data.messages && data.messages.length > 0) {
setMessages(data.messages);
}
setIsLoading(false);
})
.catch(() => setIsLoading(false));
}, [setMessages]);
}

这会在组件首次挂载时从数据库加载任何现有消息,以便用户可以看到他们以前的对话历史记录。

6.3. 添加消息显示

构建 UI 组件,这些组件将在获取数据时显示加载指示器,并以适当的样式渲染聊天消息

app/page.tsx
'use client';

import { useChat } from '@ai-sdk/react';
import { useState, useEffect } from 'react';

export default function Chat() {
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(true);

const { messages, sendMessage, setMessages } = useChat();

useEffect(() => {
fetch('/api/messages')
.then(res => res.json())
.then(data => {
if (data.messages && data.messages.length > 0) {
setMessages(data.messages);
}
setIsLoading(false);
})
.catch(() => setIsLoading(false));
}, [setMessages]);

if (isLoading) {
return <div className="flex justify-center items-center h-screen">Loading...</div>;
}

return (
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
{messages.map(message => (
<div key={message.id} className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'} mb-4`}>
<div className={`max-w-[80%] rounded-lg px-4 py-3 ${
message.role === 'user'
? 'bg-neutral-600 text-white'
: 'bg-neutral-200 dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100'
}`}>
<div className="whitespace-pre-wrap">
<p className="text-xs font-extralight mb-1 opacity-70">{message.role === 'user' ? 'YOU ' : 'AI '}</p>
{message.parts.map((part, i) => {
switch (part.type) {
case 'text':
return <div key={`${message.id}-${i}`}>{part.text}</div>;
}
})}
</div>
</div>
</div>
))}

消息渲染逻辑处理不同的消息类型并应用适当的样式 - 用户消息显示在右侧,带有深色背景,而 AI 响应显示在左侧,带有浅色背景。

6.4. 添加输入表单

现在我们需要创建输入界面,允许用户输入消息并将其发送给 AI

app/page.tsx
'use client';

import { useChat } from '@ai-sdk/react';
import { useState, useEffect } from 'react';

export default function Chat() {
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(true);

const { messages, sendMessage, setMessages } = useChat();

useEffect(() => {
fetch('/api/messages')
.then(res => res.json())
.then(data => {
if (data.messages && data.messages.length > 0) {
setMessages(data.messages);
}
setIsLoading(false);
})
.catch(() => setIsLoading(false));
}, [setMessages]);

if (isLoading) {
return <div className="flex justify-center items-center h-screen">Loading...</div>;
}

return (
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
{messages.map(message => (
<div key={message.id} className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'} mb-4`}>
<div className={`max-w-[80%] rounded-lg px-4 py-3 ${
message.role === 'user'
? 'bg-neutral-600 text-white'
: 'bg-neutral-200 dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100'
}`}>
<div className="whitespace-pre-wrap">
<p className="text-xs font-extralight mb-1 opacity-70">{message.role === 'user' ? 'YOU ' : 'AI '}</p>
{message.parts.map((part, i) => {
switch (part.type) {
case 'text':
return <div key={`${message.id}-${i}`}>{part.text}</div>;
}
})}
</div>
</div>
</div>
))}

<form
onSubmit={e => {
e.preventDefault();
sendMessage({ text: input });
setInput('');
}}
>
<input
className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
value={input}
placeholder="Say something..."
onChange={e => setInput(e.currentTarget.value)}
/>
</form>
</div>
);
}

7. 测试您的应用程序

要测试您的应用程序,请运行以下命令

npm run dev

打开浏览器并导航到 https://:3000 以查看您的应用程序的实际运行情况。

通过向 AI 发送消息并查看其是否已保存到数据库来测试它。检查 Prisma Studio 以查看数据库中的消息。

npx prisma studio

大功告成!您刚刚使用 Next.js 和 Prisma 创建了一个 AI SDK 聊天应用程序。以下是一些可供探索的后续步骤,以及一些有助于您开始扩展项目的更多资源。

下一步

现在您已经拥有一个连接到 Prisma Postgres 数据库的 AI SDK 聊天应用程序,您可以

  • 使用更多模型和关系扩展您的 Prisma schema
  • 添加创建/更新/删除路由和表单
  • 探索身份验证和验证
  • 使用 Prisma Postgres 启用查询缓存以获得更好的性能

更多信息


与 Prisma 保持联系

通过以下方式与我们保持联系,继续你的 Prisma 之旅: 我们的活跃社区。保持信息灵通,参与其中,并与其他开发人员协作。

我们真诚地感谢你的参与,并期待你成为我们社区的一部分!

© . This site is unofficial and not affiliated with Prisma Data, Inc.