Deno Fresh OpenAI 實現智能搜索
大家好,我是Echa。
在上個月Deno 官方的SaasKit受到如此積極的歡迎之后,官方與Suabase合作,為大家帶來了另一款Deno Fresh首發產品。這次,Deno 官方使用OpenAI Text Completion API創建了一個自定義ChatGPT風格的文檔搜索。
在線體驗:https://supabase-openai-doc-search.deno.dev/
Suabase的免費托管PostgresDB非常適合與OpenAI的GPT-3一起使用,因為該數據庫帶有擴展pgvector,允許您存儲嵌入并執行向量相似性搜索。這兩者都是構建GPT-3應用程序所必需的。接下來咱們看看怎么實現。
技術細節
構建您自己的自定義ChatGPT需要四個步驟:
- ?? GitHub操作預處理知識庫(docs文件夾中的.mdx文件)。
- ?? 使用pgvector在Postgres中嵌入GitHub Action Store。
- 運行時執行向量相似性搜索,以查找與問題相關的內容。
- 運行時將內容注入OpenAI GPT-3文本完成提示并流式響應到客戶端。
GitHub操作
步驟1。和2。無論何時我們對主分支進行更改,都可以通過GitHub Action進行。
合并main時,將執行此生成嵌入腳本,該腳本將執行以下任務:
- 使用.mdx文件預處理知識庫
- 使用OpenAI生成嵌入
- 將嵌入內容存儲在Suabase中
以下是所發生情況的工作流程圖:
我們可以在GitHub Actions中使用setup deno GitHub Action通過deno執行TypScript腳本。此操作還允許我們使用npm說明符。
Github Action:https://github.com/marketplace/actions/setup-deno
這是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。無論何時用戶提交問題,都會在運行時發生。發生這種情況時,將執行以下任務序列:
- Edge函數接收查詢并使用OpenAI為查詢生成嵌入
- 嵌入向量用于使用pgvector對Supadase進行向量相似性搜索,返回相關文檔
- 文檔和查詢被發送到OpenAI,響應被流式傳輸到客戶端
下面是一個工作流程圖,詳細描述了這些步驟:
在代碼中,用戶提示以SearchDialog 開始。
然后,向量搜索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使用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;
};
最后
不知道小伙們看明白了,希望大家動手也嘗試一下看看能否實現。遇到問題歡迎在評論區留言。