unischool / knowledge-base-frontend

至青宇宙學校資料累積&查詢檢索生成系統
2 stars 0 forks source link

【模仿】https://github.com/3dw/start-learn/issues/10 ,【實驗】:使用CloudFlare Vectorize來製作[創源工具資料]問答機器人 #12

Open leechiuhui opened 2 days ago

leechiuhui commented 2 days ago

【模仿】https://github.com/3dw/start-learn/issues/10https://members-frontend.pages.dev/ 【實驗】使用CloudFlare Vectorize來製作[創源工具資料]問答機器人

leechiuhui commented 2 days ago

work on #12 加上AiView.vue和更改App.vue index.ts後,造成HomeView.vue的搜尋出不來關鍵字和相關文件了。

截圖 2024-11-11 晚上7 48 46 截圖 2024-11-11 晚上7 48 54

但是試了很多次,好像關掉 vursor ,重新從 github 開啟 desktop下載程式碼,重新 yarn dev,則首頁的搜尋產生關鍵字功能又可以出現了http://localhost:5174/ ⭕️

截圖 2024-11-11 晚上7 54 54

http://localhost:5175/ ⭕️ http://localhost:5176/

目前猜想是port的關係

bestian commented 2 days ago

猜想完全正確 因為在backend的部份的CORS頭只有寫

// CORS 頭
const allowedOrigins = [
    'https://knowledge-base-frontend.pages.dev',
    'https://knowledge-base-backend.leechiuhui.workers.dev',
    'https://chihching.org',
    'http://localhost:8787',
    'http://localhost:5173',
    'http://localhost:5174',
    'http://localhost:5175'
];

如果要讓更多port可以跑,只要新增例如'http://localhost:5176'到串列中即可~

leechiuhui commented 2 days ago

猜想完全正確 因為在backend的部份的CORS頭只有寫

// CORS 頭
const allowedOrigins = [
    'https://knowledge-base-frontend.pages.dev',
    'https://knowledge-base-backend.leechiuhui.workers.dev',
    'https://chihching.org',
    'http://localhost:8787',
    'http://localhost:5173',
    'http://localhost:5174',
    'http://localhost:5175'
];

如果要讓更多port可以跑,只要新增例如'http://localhost:5176'到串列中即可~

十分鐘前已經修改backend的部份的CORS頭,增加了

     'http://localhost:5176',
    'http://localhost:5177',
     'http://localhost:5178',
'http://localhost:5179',
'http://localhost:5180',
'http://localhost:5181',
'http://localhost:5182',
'http://localhost:5183',
'http://localhost:5184',
leechiuhui commented 2 days ago

https://knowledge-base-frontend.pages.dev/ai

截圖 2024-11-11 晚上8 22 41

@bestian Good Morning teacher,請問接下來我是不是要設定 worker的Vectorize索引?或是目前在我還沒有設定Vectorize索引前,上面截圖中的AI知識庫對話,請問它是在哪裡挖資料?

bestian commented 2 days ago

@leechiuhui 對,要先建立Vectorize的索引,

還要整批對R2的檔案建立對應的向量(可參考members-backend中的/vecortize/setup相關的邏輯)

再修改backend的279~289行的邏輯

目前的針對 /ai/{prompt} 的回應是直接把對話丟給 @cf/meta/llama-3.1-8b-instruct 去生成回答,這部份要改成去索引找最相關的資料來生成問答

// 如果路徑為 /ai/{prompt},則執行 AI 模型的邏輯
        if (url.pathname === "/ai" || url.pathname.startsWith("/ai/")) {
            const prompt = decodeURIComponent(url.pathname.slice(4)); // 提取並解碼 prompt
            const response = await env.MY_AI_MODEL.run("@cf/meta/llama-3.1-8b-instruct", {
                prompt: prompt || "What is the origin of the phrase Hello, World",
            });

            return new Response(JSON.stringify(response.response), {
                headers: { "Content-Type": "application/json", ...responseHeaders },
            });
        }

可以參考members-backend專案, public_api.js中的相關邏輯(註解掉的部份請忽略)

// 如果路徑是/ai/{prompt}
    if (path.match(/^\/ai\/.+$/)) {
        // 從路徑中獲取 prompt
        const prompt = decodeURIComponent(path.split('/')[2]);
        console.log('收到的prompt:', prompt);

        // 為了確保程式確實執行到這裡
        console.log('=== 開始處理AI請求 ===');

        // 使用 Workers AI 生成回答
        //const response = await env.AI.run("@cf/meta/llama-3.1-8b-instruct", {
        //  prompt: prompt,
        //});

        const embeddings = await env.AI.run("@cf/baai/bge-base-en-v1.5", {
            text: '問題:' + prompt,
        });

        console.log(embeddings.data[0]);

        // 使用 Vectorize 索引進行回答
        const responses = await env.VECTORIZE.query(embeddings.data[0]);
        console.log(responses.matches.map(response => response.score));

        var goodResponses = responses.matches.filter(response => response.score > 0.85);
        console.log(goodResponses);

        if (goodResponses.length === 0) {
            console.log('使用字串比對');
            // 對D1使用字串比對
            const { results, success, error } = await env.DB.prepare("SELECT * FROM Faq").all();

            // 計算兩個字串的重疊字數
            function calculateOverlap(str1, str2) {
                let count = 0;
                for (let i = 0; i < str1.length; i++) {
                    if (str2.includes(str1[i])) {
                        count++;
                    }
                }
                return count;
            }

            // 根據重疊字數排序
            goodResponses = results
                .sort((a, b) => {
                    const overlapA = calculateOverlap(prompt, a.question);
                    const overlapB = calculateOverlap(prompt, b.question);
                    return overlapB - overlapA;  // 降序排列
                });
            console.log(goodResponses);

            return new Response(JSON.stringify(goodResponses[0].answer), {
                headers: {
                    'Content-Type': 'application/json; charset=utf-8',
                    ...corsHeaders,
                },
            });
        }

        var prompt_arr = [];
        for (const response of goodResponses) {

            // 從 DB 中獲取 response.id 的資料
            const { results, success, error } = await env.DB.prepare("SELECT * FROM Faq WHERE id = ?").bind(response.id).all();
            prompt_arr.push(results[0]);
        }

        console.log(prompt_arr);

        // const category_order = ['起步', '計畫', '支持', '資源', '其他'];
        // prompt_arr.sort((a, b) => category_order.indexOf(a.category) - category_order.indexOf(b.category));

        if (prompt_arr.length === 1) {
            let answer = prompt_arr[0].answer;
            return new Response(answer, {
                headers: {
                    'Content-Type': 'application/json; charset=utf-8',
                    ...corsHeaders,
                },
            });
        } else {
            const prompt_new = prompt_arr.map(item => item.answer).join('\n');
            console.log(prompt_new);

            const response = await env.AI.run("@hf/thebloke/neural-chat-7b-v3-1-awq", {
                messages: [
                    {
                        role: "system",
                        content: "請用正體中文把以下內容整理出來,重點整理,不要有廢話,如果有內容是關於時程、數字或流程的請一定要保留。"
                    },
                    {
                        role: "user",
                        content: prompt_new
                    }
                ]
            });
            // const response = responses.matches[0];

            // 確保response.response的結尾是「。」,並移除最後一個句點後的所有內容
            var arr = response.response.split('。');
            if (arr.length > 2) {
                arr.pop();
            }
            const formattedResponse = arr.join('。\n') + '。';
            console.log(formattedResponse);

            //console.log(response.id);
            //console.log(response.score);

            // 從 DB 中獲取 response.id 的資料
            //const { results, success, error } = await env.DB.prepare("SELECT * FROM Faq WHERE id = ?").bind(response.id).all();

            //if (!success) {
            //  throw new Error(error);
            //}
            //console.log(results);

            //console.log(results[0].answer);

            return new Response(formattedResponse, {
                headers: {
                    'Content-Type': 'application/json; charset=utf-8',
                    ...corsHeaders,
                },
            });
        }
    }
leechiuhui commented 2 days ago

@bestian 感謝老師的指導!目前倫敦這邊已經是晚上了,先行告退,明天會繼續努力建立Vectorize的索引💪,並研讀 unischool/knowledge-base-backend#19 中各個任務的不同功能。

瞭解Vecortize向量索引資料庫的運作邏輯 https://developers.cloudflare.com/vectorize/ https://developers.cloudflare.com/vectorize/get-started/intro/ https://developers.cloudflare.com/vectorize/tutorials/

建立Vecortize向量索引資料庫 https://dash.cloudflare.com/54a327f11ec193522723b4cb437303e2/ai/vectorize

將R2上的檔案以AI解析,生成關鍵字並存入Vecortize向量索引資料庫 將所有關鍵字存入D1一般資料庫 將所有R2檔案和關鍵字的配對存入D1一般資料庫 建立/api/keywordsWithFiles 建立/vector_search/{keyword}的API 用於建立/vector_search/{keyword} 的 API