🤖 AI - 基于 OpenAI SDK、Vercel AI SDK 实现代码生成器
本篇讨论如何基于市面上主流的 AI 研发技术栈 - OpenAI SDK、Vercel AI SDK 实现代码生成器。
前置知识
OpenAI SDK 基本介绍
OpenAI 官方提供的 AI SDK:
- 提供了直接访问 OpenAI API 的能力
- 集成了 chat、embedding、Fine-tuning 等
- 支持所有 OpenAI 系列的 LLM
- 支持三方中转 api(302、openrouter 等)
OpenAI 更多资料详见官方文档:https://platform.openai.com/docs/overview
Vercel AI SDK
vercel AI SDK 是一个专注于前端 AI 应用开发的工具包,特别适合构建基于 React、Next.js、Vue 等的全栈 AI 应用。
- 提供了一系列的 Hooks 和组件,用于快速构建 AI 应用
- 支持多种 LLM 模型,包括 OpenAI、Anthropic、Google 等
Vercel AI SDK 更多资料详见官方文档:https://sdk.vercel.ai/docs/introduction#why-use-the-ai-sdk
对比
| AI 框架 | 类比 UI 框架 | 类比说明 |
|---|---|---|
| OpenAI SDK | Tailwind CSS | - 基础的工具集(原子化的样式) - 灵活性高,可控性高,但需要自己组装 |
| Vercel AI SDK | Shadcn UI | - 在基础工具集的基础上,拓展了一些使用场景,比如支持多模型、hooks 机制 - 相比较 OpenAI SDK,拓展了更多的使用场景,学习成本、灵活性、可控性更高 |
项目架构

项目技术栈
- Next.js
- Ant Design
- Tailwind CSS
- TypeScript
- Drizzle ORM
- PostgreSQL
- OpenAI SDK
- Vercel AI SDK
项目目录结构
├── app
│ ├── api // api 路由
│ │ ├── openai
│ │ ├── vercelai
│ ├── components // 业务组件
│ ├── openai-sdk // 对接 OpenAI SDK 的 page
│ ├── vercel-ai // 对接 Vercel AI 的 page
│ ├── page.tsx // 入口
├── lib
│ ├── db // 数据库
│ │ ├── openai
│ │ │ ├── schema.ts
│ │ │ ├── selectors.ts
│ │ │ ├── actions.ts
│ │ ├── vercelai
│ │ │ ├── schema.ts
│ │ │ ├── selectors.ts
│ │ │ ├── actions.ts快速开始
clone 项目
git clone https://github.com/OweQian/private-component-codegen.gitinit 分支包含最基础的模版:
- 整个项目的基础架构、依赖包、基础工具
- 私有组件知识文档
- 项目中用到的业务组件
不包含:
- OpenAI SDK、Vercel AI SDK 的 RAG 实现
- 对接不同 RAG 逻辑的页面层
配置环境变量
cp .env.template .env编辑 .env 文件,配置环境变量
# 数据库连接字符串:从supabase中获取(https://supabase.com/)
DATABASE_URL=postgresql://
# 嵌入模型
EMBEDDING=text-embedding-ada-002
# 大模型 API Key
AI_KEY=sk-xxx
# 大模型 API Base URL
AI_BASE_URL=https://api
# 大模型
MODEL=claude-3-5-sonnet-latest启动项目
# pnpm version >= 9
pnpm install
# 启动storybook业务组件文档
pnpm storybook
# 启动项目
pnpm dev演示效果


OpenAI SDK
实现 Embedding

数据库表初始化
新建 lib/db/openai/schema.ts 文件。
让 cursor agent composer 基于以下 prompt 生成代码:
使用 drizzle-orm/pg-core 创建一个 PostgreSQL 数据表 schema,用于存储 OpenAI embeddings。表名为 'open_ai_embeddings',包含以下字段:
- id: 使用 nanoid 生成的主键,varchar(191) 类型
- content: 文本内容,text 类型,不允许为空
- embedding: 向量类型字段,维度为 1536,不允许为空
同时需要创建一个使用 HNSW 算法的向量索引,用于余弦相似度搜索。
执行数据库同步命令 - 生成迁移文件:
pnpm db:generate
执行数据库同步命令 - 执行迁移
pnpm db:migrate注意:如果遇到以下错误:PostgresError:type “vector” does not exist
请在 supabase 的 sql 编辑器中执行以下命令:CREATE EXTENSION IF NOT EXISTS vector;

查看 private-component-codegen 数据库,存在一张新表 open_ai_embeddings。

数据库 action
新建 lib/db/openai/action.ts 文件。
让 cursor agent composer 基于以下 prompt 生成代码:
创建一个 server action function,能够接收外部的数据源,保存到 db 中,function 入参是:embeddings: Array<{ embedding: number[]; content: string }>,生成的代码写到 action.ts 中。
保存到数据库
新建 app/api/openai/embedding.ts 文件。
让 cursor agent composer 基于以下 prompt 生成代码:
使用 OpenAI SDK 创建一个函数,将输入的文本字符串转换为向量嵌入(embeddings)。支持将文本按特定分隔符分块处理,分隔符的默认值为 '-------split line-------',每个文本块都生成对应的 embedding 向量,并返回包含原文本和向量的结果数组。
新建 app/api/openai/embedDocs.ts 文件,将私有组件知识库文档嵌入到数据库中。
import { saveEmbeddings } from "@/lib/db/openai/actions";
import { generateEmbeddings } from "./embedding";
import fs from "fs";
import path from "path";
/**
* 将文档嵌入到数据库中
*/
export async function embedDocs() {
// 读取文档
const docs = fs.readFileSync(
path.join(process.cwd(), "ai-docs", "basic-components.txt"),
"utf-8"
);
// 生成 embeddings
const embeddings = await generateEmbeddings(docs);
// 保存 embeddings
await saveEmbeddings(
embeddings.map(({ content, embedding }) => ({
content,
embedding,
}))
);
console.log(`Embeddings saved: ${embeddings.length}`);
return embeddings;
}
embedDocs();添加 scripts 命令:
"openai:embedDocs": "tsx app/api/openai/embedDocs.ts"执行命令:
pnpm openai:embedDocs
查看 private-component-codegen 数据库,此时在 open_ai_embeddings 表中已经能看到我们插入的内容。

实现 RAG API 逻辑

数据库向量相似度查询
新建 lib/db/openai/selectors.ts 文件。
让 cursor agent composer 基于以下 prompt 生成代码
创建一个基于向量嵌入的语义相似度搜索函数。该函数需要:
- 接收一个查询向量(embedding)作为输入
- 计算输入向量与数据库中存储的向量之间的余弦相似度
- 筛选出相似度高于指定阈值的结果
- 返回相似度最高的 N 个结果,包含原始内容和相似度分数
- 使用 SQL ORM 实现数据库查询
针对单条 message 的 Embedding 函数
在 app/api/openai/embedding.ts 中添加函数:
// 生成单个 embedding
export async function generateSingleEmbedding(text: string): Promise<number[]> {
const openai = new OpenAI({
apiKey: env.AI_KEY,
baseURL: env.AI_BASE_URL,
});
const embedding = await openai.embeddings.create({
model: env.EMBEDDING,
input: text,
});
return embedding.data[0].embedding;
}检索向量数据库并召回函数
在 app/api/openai/embedding.ts 中添加函数:
// 检索召回
export async function retrieveRecall(
text: string,
threshold: number = 0.7,
limit: number = 5
): Promise<SimilaritySearchResult[]> {
// 生成单个 embedding
const embedding = await generateSingleEmbedding(text);
// 相似度搜索
const results = await similaritySearch(embedding, threshold, limit);
return results;
}新建 RAG API 路由
新建 app/api/openai/types.ts 文件,定义 RAG API 的请求体。
import { ChatCompletionMessageParam } from "openai/resources/chat/completions.mjs";
export type OpenAIRequest = {
message: ChatCompletionMessageParam[];
};新建 app/api/openai/route.ts 文件。
让 cursor agent composer 基于以下 prompt 生成代码:
创建一个基于 Next.js 的流式 AI 对话 API 路由处理器,使用 OpenAI API 实现。该接口需要实现以下功能:
1. 通过 POST 请求接收对话消息
2. 基于最后一条消息使用向量嵌入(embeddings)查找相关内容
3. 创建 OpenAI 的流式对话补全,要求:
- 将相关内容整合到系统提示词中
- 使用服务器发送事件(SSE)进行流式响应
- 在流中同时返回 AI 响应片段和相关内容
生成的代码:
import { NextRequest } from "next/server";
import OpenAI from "openai";
import { env } from "@/lib/env.mjs";
import { retrieveRecall } from "./embedding";
import { getSystemPrompt } from "@/lib/prompt";
import { OpenAIRequest } from "./types";
// 初始化 OpenAI 客户端
const openai = new OpenAI({
apiKey: env.AI_KEY,
baseURL: env.AI_BASE_URL,
});
/**
* POST 处理函数:处理流式 AI 对话请求
*/
export async function POST(request: NextRequest) {
try {
// 解析请求体
const body: OpenAIRequest = await request.json();
const { message } = body;
if (!message || !Array.isArray(message) || message.length === 0) {
return new Response(JSON.stringify({ error: "消息数组不能为空" }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
}
// 获取最后一条用户消息用于向量检索
const lastMessage = message[message.length - 1];
let lastUserMessageText: string | null = null;
// 提取最后一条用户消息的文本内容
if (lastMessage.role === "user" && lastMessage.content) {
if (typeof lastMessage.content === "string") {
lastUserMessageText = lastMessage.content;
} else if (Array.isArray(lastMessage.content)) {
// 如果是数组类型(多模态),提取所有文本部分
const textParts = lastMessage.content
.filter((part) => part.type === "text")
.map((part) => (part as { text: string }).text)
.join(" ");
if (textParts) {
lastUserMessageText = textParts;
}
}
}
// 如果最后一条消息是用户消息,进行向量检索
let referenceContent = "";
if (lastUserMessageText) {
try {
const searchResults = await retrieveRecall(lastUserMessageText, 0.7, 5);
if (searchResults && searchResults.length > 0) {
// 将检索到的相关内容合并
referenceContent = searchResults
.map((result) => result.content)
.join("\n\n");
}
} catch (error) {
console.error("向量检索失败:", error);
// 检索失败不影响主流程,继续执行
}
}
// 构建系统提示词,整合相关内容
const systemPrompt = getSystemPrompt(referenceContent || undefined);
// 构建完整的消息列表,包含系统提示词
const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [
{
role: "system",
content: systemPrompt,
},
...message,
];
// 创建流式对话补全
const stream = await openai.chat.completions.create({
model: env.MODEL,
messages,
stream: true,
temperature: 0.7,
});
// 创建 SSE 流式响应
const encoder = new TextEncoder();
const readableStream = new ReadableStream({
async start(controller) {
// 首先发送相关内容(如果存在)
if (referenceContent) {
const referenceData = {
type: "reference",
content: referenceContent,
};
const referenceChunk = `data: ${JSON.stringify(referenceData)}\n\n`;
controller.enqueue(encoder.encode(referenceChunk));
}
// 然后发送 AI 响应流
try {
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta;
if (delta?.content) {
const data = {
type: "content",
content: delta.content,
};
const chunkData = `data: ${JSON.stringify(data)}\n\n`;
controller.enqueue(encoder.encode(chunkData));
}
// 检查是否完成
if (chunk.choices[0]?.finish_reason) {
const doneData = {
type: "done",
finish_reason: chunk.choices[0].finish_reason,
};
const doneChunk = `data: ${JSON.stringify(doneData)}\n\n`;
controller.enqueue(encoder.encode(doneChunk));
break;
}
}
} catch (error) {
console.error("流式响应错误:", error);
const errorData = {
type: "error",
error: "流式响应过程中发生错误",
};
const errorChunk = `data: ${JSON.stringify(errorData)}\n\n`;
controller.enqueue(encoder.encode(errorChunk));
} finally {
controller.close();
}
},
});
// 返回 SSE 流式响应
return new Response(readableStream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
"X-Accel-Buffering": "no", // 禁用 Nginx 缓冲
},
});
} catch (error) {
console.error("API 路由错误:", error);
return new Response(
JSON.stringify({
error: "处理请求时发生错误",
message: error instanceof Error ? error.message : String(error),
}),
{
status: 500,
headers: { "Content-Type": "application/json" },
}
);
}
}对接 RAG API

补全 OpenAI SDK 的业务组件
打开 app/openai-sdk/index.tsx 文件。
"use client";
import { ChatMessages } from "../components/ChatMessages";
const Home = () => {
return (
<ChatMessages
messages={[]}
input={""}
handleInputChange={() => {}}
onSubmit={() => {}}
isLoading={false}
messageImgUrl={""}
setMessagesImgUrl={() => {}}
onRetry={() => {}}
/>
);
};
export default Home;让 AI 基于业务组件和 API 进行数据对接和联调
打开 app/openai-sdk/index.tsx 文件。
让 cursor agent composer 基于以下 prompt 生成代码:
对接 OpenAI API 数据
演示效果

查看 RAG Docs:

Vercel AI SDK
实现 Embedding
复制一份 lib/db/openai 文件夹,重命名为 lib/db/vercelai。
数据库表初始化
修改 lib/db/vercelai/schema.ts,将 openai 替换为 vercelai。
import { index, pgTable, text, varchar, vector } from "drizzle-orm/pg-core";
import { nanoid } from "nanoid";
// Define the vercelAI embeddings table
export const vercelAiEmbeddings = pgTable(
"vercel_ai_embeddings",
{
id: varchar("id", { length: 191 })
.primaryKey()
.$defaultFn(() => nanoid()),
content: text("content").notNull(),
embedding: vector("embedding", { dimensions: 1536 }).notNull(),
},
(t) => ({
vercelaiEmbeddingIndex: index("vercelai_embedding_index").using(
"hnsw",
t.embedding.op("vector_cosine_ops")
),
})
);数据库 action
修改 lib/db/vercelai/actions.ts:
"use server";
import { db } from "@/lib/db";
import { vercelAiEmbeddings } from "./schema";
export async function saveEmbeddings(
embeddings: Array<{ embedding: number[]; content: string }>
) {
try {
// 批量插入数据
const result = await db.insert(vercelAiEmbeddings).values(
embeddings.map(({ embedding, content }) => ({
content,
embedding,
}))
);
return {
success: true,
data: result,
};
} catch (error) {
console.error("保存 embeddings 时出错:", error);
return {
success: false,
error: error instanceof Error ? error.message : "未知错误",
};
}
}执行数据库同步命令:
pnpm db:generate
pnpm db:migrate查看 private-component-codegen 数据库,存在一张新表 vercel_ai_embeddings。

保存到数据库
新建 app/api/vercel/embedding.ts 文件。
让 cursor agent composer 基于以下 prompt 生成代码:
请使用 vercel ai sdk 重构 @app/api/openai/embedding.ts 中的代码,保存到@app/api/vercelai/embedding.ts 下
复制 app/api/openai/embedDocs.ts 文件到 app/api/vercelai/embedDocs.ts。
import { saveEmbeddings } from "@/lib/db/vercelai/actions";
import { generateEmbeddings } from "./embedding";
import fs from "fs";
import path from "path";
/**
* 将文档嵌入到数据库中
*/
export async function embedDocs() {
// 读取文档
const docs = fs.readFileSync(
path.join(process.cwd(), "ai-docs", "basic-components.txt"),
"utf-8"
);
// 生成 embeddings
const embeddings = await generateEmbeddings(docs);
// 保存 embeddings
await saveEmbeddings(
embeddings.map(({ content, embedding }) => ({
content,
embedding,
}))
);
console.log(`Embeddings saved: ${embeddings.length}`);
return embeddings;
}
embedDocs();添加 scripts 命令:
"vercelai:embedDocs": "tsx app/api/vercelai/embedDocs.ts"执行命令:
pnpm vercelai:embedDocs
查看 private-component-codegen 数据库,此时在 vercel_ai_embeddings 表中已经能看到我们插入的内容。
