使用 Fresh、OpenAI 和 Supabase 构建你自己的 ChatGPT 风格文档搜索
继上个月我们的 SaasKit 获得积极反响之后,我们与 Supabase 合作,为你带来又一个 Deno Fresh starter。这一次,我们使用 OpenAI 文本补全 API 创建了一个自定义的 ChatGPT 风格文档搜索。
基于 Supabase 文档的 在 Deno Deploy 上托管的演示 截图。
Supabase 的免费托管 PostgresDB 非常适合与 OpenAI 的 GPT-3 一起使用,因为该数据库自带扩展 pgvector,允许你存储嵌入向量并执行向量相似度搜索。这两者都是构建 GPT-3 应用程序所必需的。
如果你想深入了解更多细节,请查看这篇博文。或者你可以直接开始构建你自己的自定义 ChatGPT 文档搜索。
让我们开始编码吧!
技术细节
构建你自己的自定义 ChatGPT 涉及四个步骤
- ⚡️ GitHub Action 预处理知识库(你的
docs
文件夹中的.mdx
文件)。 - ⚡️ GitHub Action 使用 pgvector 将嵌入向量存储在 Postgres 中。
- 🏃 运行时 执行向量相似度搜索,找到与问题相关的内容。
- 🏃 运行时 将内容注入 OpenAI GPT-3 文本补全提示,并将响应流式传输到客户端。
⚡️ GitHub Action
步骤 1 和 2 通过 GitHub Action 在每次我们对main
分支进行更改时发生。
当 main
分支合并时,将执行此 generate-embeddings 脚本,该脚本执行以下任务
- 使用
.mdx
文件预处理知识库 - 使用 OpenAI 生成嵌入向量
- 将嵌入向量存储在 Supabase 中
以下是发生的工作流程图
我们可以使用 setup-deno GitHub Action 在 GitHub Actions 中通过 Deno 执行 TypScript 脚本。此 action 还允许我们使用 npm specifiers。
以下是 GitHub Action yml
文件
name: Generate Embeddings
on:
push:
branches:
- main
workflow_dispatch:
jobs:
generate-embeddings:
runs-on: ubuntu-latest
env:
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
steps:
- uses: actions/checkout@v3
- uses: denoland/setup-deno@v1
with:
deno-version: v1.x
- run: deno task embeddings
除了存储嵌入向量外,此脚本还会为你的每个 .mdx
文件生成校验和,并将其存储在另一个数据库表中,以确保仅在文件发生更改时才重新生成嵌入向量。
🏃 运行时
步骤 3 和 4 在运行时发生,每当用户提交问题时都会发生。当这种情况发生时,将执行以下任务序列
- 边缘函数接收查询,并使用 OpenAI 为查询生成嵌入向量
- 嵌入向量用于在 Supabase 上使用
pgvector
执行向量相似度搜索,这将返回相关的文档 - 文档和查询被发送到 OpenAI,响应被流式传输到客户端
以下是描述更详细步骤的工作流程图
在代码中,用户提示以 SearchDialog
island 开始。
然后,vector-search
API 端点 生成嵌入向量,然后在 Supabase 上执行向量搜索。当它获得带有相关文档的响应时,它会组装 OpenAI 的提示
const prompt = codeBlock`
${oneLine`
You are a very enthusiastic Supabase representative who loves
to help people! Given the following sections from the Supabase
documentation, answer the question using only that information,
outputted in markdown format. If you are unsure and the answer
is not explicitly written in the documentation, say
"Sorry, I don't know how to help with that."
`}
Context sections:
${contextText}
Question: """
${sanitizedQuery}
"""
Answer as markdown (including related code snippets if available):
`;
const completionOptions: CreateCompletionRequest = {
model: "text-davinci-003",
prompt,
max_tokens: 512,
temperature: 0,
stream: true,
};
// The Fetch API allows for easier response streaming over the OpenAI client.
const response = await fetch("https://api.openai.com/v1/completions", {
headers: {
Authorization: `Bearer ${OPENAI_KEY}`,
"Content-Type": "application/json",
},
method: "POST",
body: JSON.stringify(completionOptions),
});
// Proxy the streamed SSE response from OpenAI
return new Response(response.body, {
headers: {
...corsHeaders,
"Content-Type": "text/event-stream",
},
});
最后,SearchDialog
island 使用 EventSource web API 处理从 OpenAI API 返回的 服务器发送事件。这使我们能够将响应从 OpenAI 生成时流式传输到客户端
const onSubmit = (e: Event) => {
e.preventDefault();
answer.value = "";
isLoading.value = true;
const query = new URLSearchParams({ query: inputRef.current!.value });
const eventSource = new EventSource(`api/vector-search?${query}`);
eventSource.addEventListener("error", (err) => {
isLoading.value = false;
console.error(err);
});
eventSource.addEventListener("message", (e: MessageEvent) => {
isLoading.value = false;
if (e.data === "[DONE]") {
eventSource.close();
return;
}
const completionResponse: CreateCompletionResponse = JSON.parse(e.data);
const text = completionResponse.choices[0].text;
answer.value += text;
});
isLoading.value = true;
};
下一步是什么?
这就是开源和现代 Web 技术的全部力量在你指尖汇聚。 立即试用,我们迫不及待地想看看你将构建什么!
以下是学习使用 OpenAI 和 ChatGPT 构建的更多资源。
- 阅读关于我们如何为 Supabase 文档构建ChatGPT 的博文。
- [文档] pgvector:嵌入向量和向量相似度
- 观看 Greg 在 Rabbit Hole Syndrome YouTube 频道上的“我是如何构建这个”视频。
最后,我们要感谢 Asher Gomez 对这个项目的贡献。
不要错过任何更新 - 在 Twitter 上关注我们!