天天看點

Deno Fresh OpenAI 實作智能搜尋

作者:Echa攻城獅
Deno Fresh OpenAI 實作智能搜尋

大家好,我是Echa。

在上個月Deno 官方的SaasKit受到如此積極的歡迎之後,官方與Suabase合作,為大家帶來了另一款Deno Fresh首發産品。這次,Deno 官方使用OpenAI Text Completion API建立了一個自定義ChatGPT風格的文檔搜尋。

Deno Fresh OpenAI 實作智能搜尋

線上體驗: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中

以下是所發生情況的工作流程圖:

Deno Fresh OpenAI 實作智能搜尋

我們可以在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,響應被流式傳輸到用戶端

下面是一個工作流程圖,較長的描述了這些步驟:

Deno Fresh 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;
};           

最後

不知道小夥們看明白了,希望大家動手也嘗試一下看看能否實作。遇到問題歡迎在評論區留言。

創作不易,喜歡的友友們加個關注,點個贊,打個賞,後面會不定期更新幹貨和技術相關的資訊,速速收藏,謝謝!你們的一個小小舉動就是對小編的認可,更是創作的動力。

繼續閱讀