低程式碼與大語言模型的探索實踐

發表於2024-02-24

低程式碼系列文章:

低程式碼相關倉庫:

前言

ChatGPT,作為一個先進的自然語言處理工具,可以理解和生成人類語言,提供智慧程式設計建議,自動化程式碼生成,以及提供互動式程式設計教學。這些功能為開發者提供了前所未有的便利,大大減少了程式設計的時間和複雜性。

低程式碼平臺則允許使用者透過圖形介面來構建應用,減少了對專業程式設計知識的依賴,在特定業務場景下能夠提升開發效率。使用者可以透過拖放元件和模型驅動的邏輯來快速地建立應用程式,而無需編寫大量程式碼。

如果可以把 ChatGPT 和低程式碼平臺結合起來使用,那麼使用者就可以透過對話來快速地建立應用程式,從而簡化了搭建應用程式的過程,並且提升了使用者體驗。

可行性

低程式碼平臺通常使用 JSON 格式的 DSL(領域特定語言 domain-specific language 指的是專注於某個應用程式領域的計算機語言) 來描述一個頁面。使用者拖拽元件、編輯頁面,實際上是在和頁面的 JSON 在進行互動。低程式碼平臺透過渲染引擎把 JSON 資料渲染成為頁面,至於頁面要渲染成為 Vue、React 或者是純粹的 HTML 頁面,就得看各個低程式碼平臺自己的實現了。一個應用程式一般會包含多個頁面,然後透過路由來決定各個頁面的跳轉邏輯。

因此,我們可以向 ChatGPT 提出生成頁面的需求,ChatGPT 再根據我們提供的 prompt 來生成一份符合低程式碼平臺資料格式的 JSON。理論上,這是可行的,現在來看一下怎麼實現這個功能。

使用 ChatGPT 進行頁面生成

首先,我們需要註冊一個 openai 或者 azure 賬號,在上面開通 api 服務(如何申請賬號請自行搜尋,網上有很多教程)。然後就可以使用這個 api 來和 ChatGPT 進行互動了。

另外,我們還需要一個低程式碼平臺,因為生成的 JSON 需要一個低程式碼平臺來驗證生成 JSON 是否真實可用。剛好前幾年我寫了一個低程式碼平臺教學專案,現在剛好可以用上。

低程式碼平臺元件 DSL

首先,我們要知道每一個元件的 DSL 描述,下面的程式碼是一個文字元件的 DSL 描述:

{
    "animations": [], // 動畫屬性
    "events": {}, // 事件
    "groupStyle": {}, // 組合元件樣式
    "isLock": false, // 是否鎖定
    "collapseName": "style",
    "linkage": { // 聯動元件
        "duration": 0,
        "data": [
            {
                "id": "",
                "label": "",
                "event": "",
                "style": [
                    {
                        "key": "",
                        "value": ""
                    }
                ]
            }
        ]
    },
    "component": "VText", // 元件型別
    "label": "文字", // 元件名稱
    "propValue": "雙擊編輯文字", // 元件值
    "icon": "wenben", // 元件圖示
    "request": { // 元件 API 請求
        "method": "GET",
        "data": [],
        "url": "",
        "series": false,
        "time": 1000,
        "paramType": "",
        "requestCount": 0
    },
    "style": { // 元件樣式
        "rotate": 0,
        "opacity": 1,
        "width": 200,
        "height": 28,
        "fontSize": "",
        "fontWeight": 400,
        "lineHeight": "",
        "letterSpacing": 0,
        "textAlign": "",
        "color": "",
        "top": 157,
        "left": 272
    },
    "id": "07l4byRWvsphAPo2uatxy" // 元件唯一 id
}

每一個元件就是一個 JSON 資料,那麼整個頁面就是一個 JSON 陣列,裡面包含了多個元件:

// 頁面 JSON
[
 { ... }, // 元件1
 { ... }, // 元件2
 { ... }, // 元件3
]

prompt 編寫

要讓 ChatGPT 來生成頁面,那麼我們要讓 ChatGPT 知道頁面、元件的資料結構,並且要給它提供示例。然後再向它提出頁面生成的需求,從而為我們生成符合要求的頁面。這個 prompt 的格式如下:

我有一個低程式碼平臺專案,它可以根據符合規範的 JSON 資料生成頁面,這個 JSON 資料是一個陣列,裡面的每一項都是一個 JSON 物件,每個 JSON 物件都對應著一個元件。
下面用 ### 包括起來的程式碼就是所有的元件列表。

###
---所有元件的 JSON 資料結構---
###

如果一個頁面包含了一個文字和按鈕元件,那麼這個頁面的 JSON 程式碼如下:
###
---示例頁面 JSON 資料結構---
###

你作為一個技術專家,現在需要按照上面的規則來為我生成頁面,並且生成的頁面中每一個元件的屬性都不能忽略,也不需要解釋,只需要返回 JSON 資料即可。要注意的是,有些數值的單位是沒有 px 的。

現在我需要生成一個海報頁面,主要用於宣傳程式設計有什麼用。

由於篇幅有限,上面只展示了這個 prompt 的大綱,JSON 資料都省略了。完整的 prompt 請在 lowcode-llm-demo 上檢視。

prompt 準備好了,現在我們需要呼叫 ChatGPT 的 api 來生成頁面,示例程式碼如下:

import { AzureChatOpenAI } from '@langchain/azure-openai'
import 'dotenv/config'
import { readFileSync } from 'fs'
import { resolve, dirname } from 'path'
import { fileURLToPath } from 'url'

const model = new AzureChatOpenAI({
    modelName: process.env.AZURE_OPENAI_API_MODEL_NAME,
    azureOpenAIEndpoint: process.env.AZURE_OPENAI_API_ENDPOINT,
    azureOpenAIApiKey: process.env.AZURE_OPENAI_API_KEY,
    azureOpenAIEmbeddingsApiDeploymentName: process.env.AZURE_OPENAI_API_EMBEDDING_DEPLOYMENT_NAME,
    azureOpenAIApiVersion: process.env.AZURE_OPENAI_API_VERSION,
})

const dirName = dirname(fileURLToPath(import.meta.url))
const prompt = readFileSync(resolve(dirName, '../prompts/prompt-compress.md'), 'utf-8')
const response = await model.invoke(prompt)
console.log(JSON.stringify(response)) // 返回 ChatGPT 的結果

上面的程式碼執行後就能得到 ChatGPT 返回來的頁面 JSON 資料。不過這個資料不能直接使用,還需要額外寫點程式碼處理一下才能使用。下面的程式碼就是處理後的頁面 JSON 資料結構:

[
    {
        "animations": [],
        "events": {},
        "groupStyle": {},
        "isLock": false,
        "collapseName": "style",
        "linkage": {
            "duration": 0,
            "data": [
                {
                    "id": "",
                    "label": "",
                    "event": "",
                    "style": [
                        {
                            "key": "",
                            "value": ""
                        }
                    ]
                }
            ]
        },
        "component": "Picture",
        "label": "背景圖片",
        "propValue": {
            "url": "img/programming_poster_bg.jpg" // 替換為自己的圖片
        },
        "style": {
            "rotate": 0,
            "opacity": 1,
            "width": 500,
            "height": 700,
            "top": 0,
            "left": 0,
            "position": "absolute",
            "zIndex": 0
        },
        "id": "backgroundImage"
    },
    {
        "animations": [],
        "events": {},
        "groupStyle": {},
        "isLock": false,
        "collapseName": "style",
        "linkage": {
            "duration": 0,
            "data": [
                {
                    "id": "",
                    "label": "",
                    "event": "",
                    "style": [
                        {
                            "key": "",
                            "value": ""
                        }
                    ]
                }
            ]
        },
        "component": "VText",
        "label": "標題文字",
        "propValue": "程式設計改變世界",
        "icon": "wenben",
        "style": {
            "rotate": 0,
            "opacity": 1,
            "width": 450,
            "height": 100,
            "fontSize": "32px",
            "fontWeight": 700,
            "textAlign": "center",
            "color": "rgba(16, 15, 15, 1)",
            "top": 50,
            "left": 25,
            "position": "absolute",
            "zIndex": 10
        },
        "id": "titleText"
    },
    {
        "animations": [],
        "events": {},
        "groupStyle": {},
        "isLock": false,
        "collapseName": "style",
        "linkage": {
            "duration": 0,
            "data": [
                {
                    "id": "",
                    "label": "",
                    "event": "",
                    "style": [
                        {
                            "key": "",
                            "value": ""
                        }
                    ]
                }
            ]
        },
        "component": "VText",
        "label": "描述文字",
        "propValue": "透過程式設計,我們可以建立軟體來解決問題、分析資料,甚至改善人們的生活。",
        "icon": "wenben",
        "style": {
            "rotate": 0,
            "opacity": 1,
            "width": 400,
            "height": 200,
            "fontSize": "18px",
            "fontWeight": 400,
            "lineHeight": "1.5",
            "textAlign": "center",
            "color": "rgba(33, 31, 31, 1)",
            "top": 180,
            "left": 50,
            "position": "absolute",
            "zIndex": 10
        },
        "id": "descriptionText"
    },
    {
        "animations": [],
        "events": {},
        "groupStyle": {},
        "isLock": false,
        "collapseName": "style",
        "linkage": {
            "duration": 0,
            "data": [
                {
                    "id": "",
                    "label": "",
                    "event": "",
                    "style": [
                        {
                            "key": "",
                            "value": ""
                        }
                    ]
                }
            ]
        },
        "component": "VButton",
        "label": "行動按鈕",
        "propValue": "開始學習程式設計",
        "icon": "button",
        "style": {
            "rotate": 0,
            "opacity": 1,
            "width": 200,
            "height": 50,
            "borderRadius": "25px",
            "fontSize": "20px",
            "fontWeight": 500,
            "textAlign": "center",
            "color": "#FFF",
            "backgroundColor": "#f44336",
            "top": 420,
            "left": 150,
            "position": "absolute",
            "zIndex": 10
        },
        "id": "actionButton"
    }
]

將 JSON 匯入到低程式碼平臺後,生成的頁面如下所示:

在這裡插入圖片描述
可以看到 ChatGPT 輸出了一個半成品頁面,背景圖片的地址需要我們手動修改。ChatGPT 每次輸出的頁面都是隨機的,下面是生成的另外一個頁面。

在這裡插入圖片描述

節省成本

ChatGPT 很好用,但是有一個缺點,太貴了。所以我們需要想一些辦法來降低成本,下面是幾個比較可行的辦法:

  1. 精簡 prompt
  2. 微調(Fine-tuning)
  3. 使用模板、分類標籤

    精簡 prompt

    ChatGPT 的 api 是透過 token 來收費的,所以最簡單直接的方法就是最佳化 prompt。下面是一個未最佳化的 prompt 示例:

我需要一個使用者管理系統的頁面。在頁面頂部,我需要一個新增使用者的按鈕。下面是一個表格,列出了所有使用者的姓名、郵箱和註冊日期。點選任何一個使用者,將會開啟一個包含完整使用者資訊的新頁面,其中包含姓名、郵箱、註冊日期、最後登入時間和使用者角色。還需要有一個搜尋欄,可以透過姓名或郵箱搜尋使用者。請基於這些要求為我的低程式碼平臺生成程式碼。

現在將這個 prompt 最佳化一下:

生成頁面:使用者管理
元件:新增按鈕,使用者表格(姓名,郵箱,註冊日期),使用者詳情(姓名,郵箱,註冊日期,最後登入,角色),搜尋欄(姓名,郵箱)

在這個例子中,我們簡化了描述頁面的需求,從而減少了 token 的數量。

微調(Fine-tuning)

在文章的開頭,我展示了一個生成頁面的 prompt 示例,並且可以看到這個 prompt 消耗的 token 數量非常大,因為它需要告訴 ChatGPT 每個元件的 JSON 資料結構是什麼,以及一個完整的示例頁面 JSON 資料結構是什麼樣的,這些示例都需要消耗大量的 token。

為了精簡 prompt,同時又能達到未精簡前的效果,這可以使用微調來實現。

微調是一種機器學習技術,它可以讓你自定義機器學習模型以適應特定的任務或資料集。透過微調,您可以在一個預訓練的模型(如ChatGPT)的基礎上進一步訓練它,使其更好地理解和執行特定的任務。這樣,您可以建立一個專門為您的低程式碼平臺生成頁面的模型。

我們可以給 ChatGPT 提供大量的資料集進行微調訓練,比如大量的低內碼表面示例。這樣你再讓它生成一個“用於宣傳程式設計的海報頁面”,就不需要提供每個元件的資料結構了,因為這些資料 ChatGPT 已經透過微調學會了。

現在來看一下用於微調訓練的資料集是什麼樣的:

{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}

上面是資料集的格式,現在我要提供大量的低程式碼平臺資料集:

{"prompt":"生成一個帶有矩形元件的頁面","completion": "[{\"animations\":[],\"events\":{},\"groupStyle\":{},\"isLock\":false,\"collapseName\":\"style\",\"linkage\":{\"duration\":0,\"data\":[{\"id\":\"\",\"label\":\"\",\"event\":\"\",\"style\":[{\"key\":\"\",\"value\":\"\"}]}]},\"component\":\"RectShape\",\"label\":\"矩形\",\"propValue\":\"矩形\",\"icon\":\"juxing\",\"style\":{\"rotate\":0,\"opacity\":1,\"width\":200,\"height\":200,\"fontSize\":\"\",\"fontWeight\":400,\"lineHeight\":\"\",\"letterSpacing\":0,\"textAlign\":\"center\",\"color\":\"\",\"borderColor\":\"#000\",\"borderWidth\":1,\"backgroundColor\":\"\",\"borderStyle\":\"solid\",\"borderRadius\":\"\",\"verticalAlign\":\"middle\",\"top\":143,\"left\":379},\"id\":\"j3i41NnZw8Zcxu3BfbXwv\"}]"}
{"prompt":"生成一個帶有文字元件的頁面","completion": "[{\"animations\":[],\"events\":{},\"groupStyle\":{},\"isLock\":false,\"collapseName\":\"style\",\"linkage\":{\"duration\":0,\"data\":[{\"id\":\"\",\"label\":\"\",\"event\":\"\",\"style\":[{\"key\":\"\",\"value\":\"\"}]}]},\"component\":\"VText\",\"label\":\"文字\",\"propValue\":\"雙擊編輯文字\",\"icon\":\"wenben\",\"request\":{\"method\":\"GET\",\"data\":[],\"url\":\"\",\"series\":false,\"time\":1000,\"paramType\":\"\",\"requestCount\":0},\"style\":{\"rotate\":0,\"opacity\":1,\"width\":200,\"height\":28,\"fontSize\":\"\",\"fontWeight\":400,\"lineHeight\":\"\",\"letterSpacing\":0,\"textAlign\":\"\",\"color\":\"\",\"top\":145.3333282470703,\"left\":195},\"id\":\"WKqULBX4bKcmREgPJef3D\"}]"}
{"prompt":"生成一個帶有按鈕元件的頁面","completion": "[{\"animations\":[],\"events\":{},\"groupStyle\":{},\"isLock\":false,\"collapseName\":\"style\",\"linkage\":{\"duration\":0,\"data\":[{\"id\":\"\",\"label\":\"\",\"event\":\"\",\"style\":[{\"key\":\"\",\"value\":\"\"}]}]},\"component\":\"VButton\",\"label\":\"按鈕\",\"propValue\":\"按鈕\",\"icon\":\"button\",\"style\":{\"rotate\":0,\"opacity\":1,\"width\":100,\"height\":34,\"borderWidth\":1,\"borderColor\":\"\",\"borderRadius\":\"\",\"fontSize\":\"\",\"fontWeight\":400,\"lineHeight\":\"\",\"letterSpacing\":0,\"textAlign\":\"\",\"color\":\"\",\"backgroundColor\":\"\",\"top\":126.33332824707031,\"left\":224},\"id\":\"6wgvR1wyRyNqIl37qs1iS\"}]"}
...

透過微調訓練後,ChatGPT 就變成了一個專門的低程式碼模型,它會更好地理解低程式碼的需求。後面我們再讓 ChatGPT 生成頁面就不需要大量的 prompt 了,可以直接讓它生成一個“用於宣傳程式設計的海報頁面”。記住,微調是一個需要精心設計和執行的過程,需要我們不停的校正,才能達到最好的效果

使用模板、分類標籤

一個成功的低程式碼平臺,一定會內建大量的模板,包括但不限於頁面模板、應用模板等等。其實在大多數時候,使用者提出生成頁面的需求時,我們可以提取關鍵詞,根據關鍵詞找到符合使用者需求的模板,再展示給使用者選擇。如果沒有找到符合要求的模板,才使用 ChatGPT 來生成頁面,這樣不僅能節省成本,還避免了 ChatGPT 隨機生成頁面並且有可能生成錯誤頁面的弊端。

現在我們來看看怎麼做。首先,除了給模板命名,還需要給模板歸類,比如打上幾個類似於“醫療”、“後臺管理系統”之類的標籤。

當使用者提出一個”生成用於宣傳程式設計的頁面“需求時,我們可以使用自然語言處理(NLP)庫,如 natural 或者 compromise 進行關鍵詞提取,然後再透過 Elasticsearch 來進行搜尋,最後把搜尋到的模板返回給使用者。下面是程式碼示例:

const { Client } = require('@elastic/elasticsearch');
const { NlpManager } = require('node-nlp');

// 初始化Elasticsearch客戶端
const client = new Client({ node: 'http://localhost:9200' });

// 初始化NLP管理器
const nlpManager = new NlpManager({ languages: ['en'], nlu: { useNoneFeature: false } });

// 假設我們有一些模板資料
const templates = [
    { name: 'Medical Service Promotion Page', tags: ['medical', 'promotion'] },
    { name: 'Programming Education Poster', tags: ['education', 'programming', 'poster'] },
    { name: 'Backend Management System Dashboard', tags: ['backend', 'management', 'system'] }
];

// 建立Elasticsearch索引
async function createIndex(indexName) {
    // 省略建立索引程式碼
}

// 索引模板資料到Elasticsearch
async function indexTemplates(indexName, templates) {
    // 省略索引資料程式碼
}

// 提取關鍵詞
async function extractKeywords(text) {
    const result = await nlpManager.extractEntities(text);
    const keywords = result.entities.map(entity => entity.option || entity.utteranceText);
    return keywords;
}

// 使用Elasticsearch進行搜尋
async function searchTemplates(indexName, keywords) {
    const { body } = await client.search({
        index: indexName,
        body: {
            query: {
                bool: {
                    should: [
                        { match: { name: { query: keywords.join(' '), boost: 2 } } },
                        { terms: { tags: keywords } }
                    ]
                }
            }
        }
    });

    return body.hits.hits.map(hit => hit._source);
}

// 主程式
async function main() {
    const indexName = 'templates';

    // 建立索引並索引資料
    await createIndex(indexName);
    await indexTemplates(indexName, templates);

    // 使用者輸入
    const userInput = "I want to create a poster page for programming promotion";

    // 提取關鍵詞
    const keywords = await extractKeywords(userInput);

    // 執行搜尋
    const results = await searchTemplates(indexName, keywords);

    // 輸出結果
    console.log(results);
}

main().catch(console.error);

使用開源大語言模型 ChatGLM-6B 進行頁面生成

對於無法使用外網或預算有限的專案,我們可以考慮使用開源的大語言模型。本文選了 ChatGLM-6B 開源模型來做演示。

安裝

ChatGLM-6B 本地部署的教程網上有很多,由於我的電腦是 Windows,並且沒有 N 卡,所以我參考了這篇文章手把手教你本地部署清華大學KEG的ChatGLM-6B模型來部署 ChatGLM-6B。

這篇文章講得很細緻,按照流程走下來只出現了兩個小問題,解決後就跑通了。這裡也記錄一下這兩個問題及相應的解決辦法。

TDM-GCC 編譯錯誤

按照文章中的要求安裝了 TDM-GCC 後發現編譯 quantization_kernels_parallel.c 檔案錯誤,解除安裝 TDM-GCC 後換了 MinGW-w64 就好了,編譯順利透過。

執行 python 指令碼偶爾沒反應

這個問題排查了很久,經過不斷的除錯和重啟,最後發現是系統資源不足(我的電腦 CPU 是 6800H,32G 記憶體,沒有顯示卡)。在關掉無關程式後,只保留一個終端用於啟動指令碼後,就沒有問題了。

使用

ChatGLM-6B 的相關程式碼已經上傳到了 Github ,大家可以把專案下載下來,然後按照倉庫文件中的說明修改目錄位置後,就可以執行 cli-demo.py 或者 web-demo.py 指令碼和 ChatGLM-6B 進行互動了。下面的幾張圖片就是部署在我電腦上的 ChatGLM-6B 使用示例:

在這裡插入圖片描述

在這裡插入圖片描述

由於電腦配置不是特別好,ChatGLM-6B 在我電腦上執行起來比較慢,一個問題回答起來要花幾分鐘。像生成低內碼表面的這個需求,就跑了十幾分鍾,最後程式直接崩了,只給我輸出了一半的 JSON,不過資料結構是對的,所以換個好點的顯示卡後應該不是問題。

總結

其實,大語言模型不僅能和低程式碼領域配合使用,經過訓練後的模型可以和任何領域結合,從而生成該領域的專門模型。例如 Figma、即時設計、MasterGo 這種設計工具,它們儲存的也是一份 JSON 資料。所以理論上也可以透過對話來生成設計頁面。

還有其他的類似於 AI 客服、催收機器人都可以透過這種方法訓練出來。

參考資料

求職

我是一名有 7 年工作經驗的前端,學歷是非全大專,目前正在看機會。我的期望工作地點是北京/天津/遠端,期望的崗位是前端/全棧

以下是我掌握的一些技能:

  1. 前端有做過低程式碼、監控、腳手架、前端工程化、效能最佳化,業餘時間有研究過微前端、瀏覽器渲染原理、編譯原理等
  2. 後端能用 nodejs 寫業務,懂點 docker k8s
  3. 有帶團隊經驗(團隊規模 15 +)
  4. 瞭解 Rust、 LLM 應用相關知識,做過一些練手專案

我的 Github: https://github.com/woai3c 和微訊號:qq411020382,有興趣的大佬請加我微信聯絡。

相關文章