【OpenAI】私有框架程式碼生成實踐

京東雲開發者發表於2023-05-04

作者:京東零售 牛曉光

根據現有調研和實踐,由OpenAI提供的ChatGPT/GPT-4模型和CodeX模型能夠很好的理解和生成業界大多數程式語言的邏輯和程式碼,其中尤其擅長Python、JavaScript、TypeScript、Ruby、Go、C# 和 C++等語言。

然而在實際應用中,我們經常會在編碼時使用到一些私有框架、包、協議和DSL等。由於相關模型沒有學習最新網路資料,且這些私有資料通常也沒有釋出在公開網路上,OpenAI無法根據這些私有資訊生成對應程式碼。

一、OpenAI知識學習方式

OpenAI提供了幾種方式,讓OpenAI模型學習私有知識:

1. 微調模型

OpenAI支援基於現有的基礎模型,透過提供“prompt - completion”訓練資料生成私有的自定義模型。

使用方法

在執行微調工作時,需要執行下列步驟:

1. 準備訓練資料:資料需包含prompt/completion,格式支援CSV, TSV, XLSX, JSON等。

  • 格式化訓練集:openai tools fine_tunes.prepare_data -f <LOCAL_FILE>
  • LOCAL_FILE:上一步中準備好的訓練資料。

2. 訓練模型微調:openai api fine_tunes.create -t <LOCAL_FILE> -m <BASE_MODULE> --suffix "<MODEL_SUFFIX>"

  • LOCAL_FILE:上一步中準備好的訓練集。
  • BASE_MODULE:基礎模型的名稱,可選的模型包括adababbagecuriedavinci等。
  • MODEL_SUFFIX:模型名稱字尾。

3. 使用自定義模型

使用成本

在微調模型方式中,除了使用自定義模型進行推理時所需支付的費用外,訓練模型時所消耗的Tokens也會對應收取費用。根據不同的基礎模型,費用如下:

結論

使用微調模型進行私有知識學習,依賴於大量的訓練資料,訓練資料越多,微調效果越好。
此方法適用於擁有大量資料積累的場景。

2. 聊天補全

GPT模型接收對話形式的輸入,而對話按照角色進行整理。對話資料的開始包含系統角色,該訊息提供模型的初始說明。可以在系統角色中提供各種資訊,如:

  • 助手的簡要說明

  • 助手的個性特徵

  • 助手需要遵循的指令或規則

  • 模型所需的資料或資訊

我們可以在聊天中,透過自定義系統角色為模型提供執行使用者指令所必要的私有資訊。

使用方法

可以在使用者提交的資料前,追加對私有知識的說明內容。

openai.createChatCompletion({
  model: "gpt-3.5-turbo",
  messages: [
    { role: "system", content: "你是一款智慧聊天機器人,幫助使用者回答有關內容管理系統低程式碼引擎CCMS的技術問題。智慧根據下面的上下文回答問題,如果不確定答案,可以說“我不知道”。\n\n" +
      "上下文:\n" + 
      "- CCMS透過視覺化配置方式生成中後臺管理系統頁面,其透過JSON資料格式描述頁面資訊,並在執行時渲染頁面。\n" + 
      "- CCMS支援普通列表、篩選列表、新增表單、編輯表單、詳情展示等多種頁面型別。\n" + 
      "- CCMS可以配置頁面資訊、介面定義、邏輯判斷、資料繫結和頁面跳轉等互動邏輯。"
    },
    { role: "user", content: "CCMS是什麼?" }
  ]
}).then((response) => response.data.choices[0].message.content);


使用成本

除了使用者所提交的內容外,系統角色所提交的關於私有知識的說明內容,也會按照Tokens消耗量進行計費。

結論

使用聊天補全進行私有知識學習,依賴於系統角色的資訊輸入,且此部分資料的Tokens消耗會隨每次使用者請求而重複計算。

此方法適用於私有知識清晰準確,且內容量較少的場景。

二、私有知識學習實踐

對於私有框架、包、協議、DSL等,通常具備比較完善的使用文件,而較少擁有海量的使用者使用資料,所以在當前場景下,傾向於使用聊天補全的方式讓GPT學習私有知識。

而在此基礎上,如何為系統角色提供少量而精確的知識資訊,則是在保障使用者使用情況下,節省使用成本的重要方式。

3. 檢索-提問解決方案

我們可以在呼叫OpenAI提供的Chat服務前,使用使用者所提交的資訊對私有知識進行檢索,篩選出最相關的資訊,再進行Chat請求,檢索Tokens消耗。

而OpenAI所提供的嵌入(Embedding)服務則可以解決檢索階段的工作。

使用方法

1. 準備搜尋資料(一次性)

  • 收集:準備完善的使用文件。如:https://jd-orion.github.io/docs

  • 分塊:將文件拆分為簡短的、大部分是獨立的部分,這通常是文件中的頁面或章節。

  • 嵌入:為每一個分塊分別呼叫OpenAI API生成Embedding。

await openai.createEmbedding({
  model: "text-embedding-ada-002",
  input: fs.readFileSync('./document.md', 'utf-8').toString(),
}).then((response) => response.data.data[0].embedding);


  • 儲存:儲存Embedding資料。(對於大型資料集,可以使用向量資料庫)

2. 檢索(每次查詢一次)

  • 為使用者的提問,呼叫OpenAI API生成Embedding。(同1.3步驟)

  • 使用提問Embedding,根據與提問的相關性對私有知識的分塊Embedding進行排名。

const fs = require('fs');
const { parse } = require('csv-parse/sync');
const distance = require( 'compute-cosine-distance' );

function (input: string, topN: number) {
  const knowledge: { text: string, embedding: string, d?: number }[] = parse(fs.readFileSync('./knowledge.csv').toString());

  for (const row of knowledge) {
    row.d = distance(JSON.parse(row.embedding), input)
  }

  knowledge.sort((a, b) => a.d - b.d);

  return knowledge.slice(0, topN).map((row) => row.text));
}


3. 提問(每次查詢一次)

  • 給請求的系統角色插入與問題最相關的資訊
async function (knowledge: string[], input: string) {
  const response = await openai.createChatCompletion({
    model: "gpt-3.5-turbo",
    messages: [
      {
        role: 'system',
        content: "你是一款智慧聊天機器人,幫助使用者回答有關內容管理系        統低程式碼引擎CCMS的技術問題。\n\n" + knowledge.join("\n")
      },
      {
        role: 'user',
        content: input
      }
    ]
  }).then((response) => response.data.choices[0].message.content);
  return response
}


  • 返回GPT的答案

使用成本

使用此方法,需要一次性的支付用於執行Embedding的費用。

三、低程式碼自然語言搭建案例

解決了讓GPT學習私有知識的問題後,就可以開始使用GPT進行私有框架、庫、協議和DSL相關程式碼的生成了。

本文以低程式碼自然語言搭建為例,幫助使用者使用自然語言對所需搭建或修改的頁面進行描述,進而使用GPT對描述頁面的配置檔案進行修改,並根據返回的內容為使用者提供實時預覽服務。

使用方法

OpenAI呼叫元件

const { Configuration, OpenAIApi } = require("openai");
const openai = new OpenAIApi(new Configuration({ /** OpenAI 配置 */ }));
const distance = require('compute-cosine-distance');
const knowledge: { text: string, embedding: string, d?: number }[] = require("./knowledge")

export default function OpenAI (input, schema) {
  return new Promise((resolve, reject) => {
    // 將使用者提問資訊轉換為Embedding
    const embedding = await openai.createEmbedding({
      model: "text-embedding-ada-002",
      input,
    }).then((response) => response.data.data[0].embedding);
    
    // 獲取使用者提問與知識的相關性並排序
    for (const row of knowledge) {
      row.d = distance(JSON.parse(row.embedding), input)
    }
    knowledge.sort((a, b) => a.d - b.d);
    
    // 將相關性知識、原始程式碼和使用者提問傳送給GPT-3.5模型
    const message = await openai.createChatCompletion({
      model: "gpt-3.5-turbo",
      messages: [
        {
          role: 'system',
          content: "你是程式設計助手,需要閱讀協議知識,並按照使用者的要求修改程式碼。\n\n" + 
                  "協議知識:\n\n" +
                  knowledge.slice(0, 10).map((row) => row.text).join("\n\n") + "\n\n" + 
                  "原始程式碼:\n\n" +
                  "```\n" + schema + "\n```"
        },
        {
          role: 'user',
          content: input
        }
      ]
    }).then((response) => response.data.choices[0].message.content);

      // 檢查返回訊息中是否包含Markdown語法的程式碼塊標識
    let startIndex = message.indexOf('```');
    if (message.substring(startIndex, startIndex + 4) === 'json') {
      startIndex += 4;
    }

    if (startIndex > -1) {
      // 返回訊息為Markdown語法
      let endIndex = message.indexOf('```', startIndex + 3);
      let messageConfig;

      // 需要遍歷所有程式碼塊
      while (endIndex > -1) {
        try {
          messageConfig = message.substring(startIndex + 3, endIndex);

          if (
            /** messageConfig正確性校驗 */
          ) {
            resolve(messageConfig);
            break;
          }
        } catch (e) {
          /* 本次失敗 */
        }

        startIndex = message.indexOf('```', endIndex + 3);

        if (message.substring(startIndex, startIndex + 4) === 'json') {
          startIndex += 4;
        }

        if (startIndex === -1) {
          reject(['OpenAI返回的資訊不可識別:', message]);
          break;
        }

        endIndex = message.indexOf('```', startIndex + 3);
      }
    } else {
      // 返回訊息可能為程式碼本身
      try {
        const messageConfig = message;

        if (
          /** messageConfig正確性校驗 */
        ) {
          resolve(messageConfig);
        } else {
          reject(['OpenAI返回的資訊不可識別:', message]);
        }
      } catch (e) {
        reject(['OpenAI返回的資訊不可識別:', message]);
      }
    }
  })
}


低程式碼渲染

import React, { useState, useEffect } from 'react'
import { CCMS } from 'ccms-antd'
import OpenAI from './OpenAI'

export default function App () {
  const [ ready, setReady ] = useState(true)
  const [ schema, setSchema ] = useState({})

  const handleOpenAI = (input) => {
    OpenAI(input, schema).then((nextSchema) => {
      setReady(false)
      setSchema(nextSchema)
    })
  }

  useEffect(() => {
    setReady(true)
  }, [schema])

  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      {ready && (
        <CCMS
          config={pageSchema}
          /** ... */
        />
      )}
      <div style={{ position: 'fixed', right: 385, bottom: 20, zIndex: 9999 }}>
        <Popover
          placement="topRight"
          trigger="click"
          content={
            <Form.Item label="使用OpenAI助力搭建頁面:" labelCol={{ span: 24 }}>
              <Input.TextArea
                placeholder="請在這裡輸入內容,按下Shift+回車確認。"
                defaultValue={defaultPrompt}
                onPressEnter={(e) => {
                  if (e.shiftKey) {
                    handleOpenAI(e.currentTarget.value)
                  }
                }}
              />
            </Form.Item>
          }
        >
          <Button shape="circle" type="primary" icon={ /** OpenAI icon */ } />
        </Popover>
      </div>
    </div>
  )
}


四、資訊保安

根據OpenAI隱私政策說明,使用API方式進行資料訪問時:

  1. 除非明確的授權,OpenAI不會使用使用者傳送的資料進行學習和改進模型。
  2. 使用者傳送的資料會被OpenAI保留30天,以用於監管和審查。(有限數量的授權OpenAI員工,以及負有保密和安全義務的專業第三方承包商,可以訪問這些資料)
  3. 使用者上傳的檔案(包括微調模型是提交的訓練資料),除非使用者刪除,否則會一直保留。

另外,OpenAI不提供模型的私有化部署(包括上述微調模型方式所生成的自定義模型),但可以透過聯絡銷售團隊購買私有容器。

文中所使用的訓練資料、私有框架知識以及低程式碼框架均源自本團隊開發並已開源的內容。使用者使用相關服務時也會進行資料安全提示。

相關文章