作者:京東零售 牛曉光
根據現有調研和實踐,由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
:基礎模型的名稱,可選的模型包括ada
、babbage
、curie
、davinci
等。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方式進行資料訪問時:
- 除非明確的授權,OpenAI不會使用使用者傳送的資料進行學習和改進模型。
- 使用者傳送的資料會被OpenAI保留30天,以用於監管和審查。(有限數量的授權OpenAI員工,以及負有保密和安全義務的專業第三方承包商,可以訪問這些資料)
- 使用者上傳的檔案(包括微調模型是提交的訓練資料),除非使用者刪除,否則會一直保留。
另外,OpenAI不提供模型的私有化部署(包括上述微調模型方式所生成的自定義模型),但可以透過聯絡銷售團隊購買私有容器。
文中所使用的訓練資料、私有框架知識以及低程式碼框架均源自本團隊開發並已開源的內容。使用者使用相關服務時也會進行資料安全提示。