
在網易 CodeWave 智慧開發平臺中,NASL(NetEase Application Specific Language)視覺化程式語言 作為產品核心,主要有以下三大作用:
-
承載完整應用不同粒度、多種領域的視覺化程式設計結構的精確描述。 大到一個頁面節點、一個流程節點,小到一個表示式、一個運算子。有通用的 基礎語言部分,也有 Web 應用常見的特定領域(如資料定義、資料查詢、頁面、流程、許可權等)子語言部分。
-
統一前後端各子領域的表達方式,降低學習門檻。NASL 統一了資料庫、服務端邏輯、介面定義、頁面邏輯、樣式等的各種型別和表示式的編寫形式。
-
對接各類語言設施,整合多種開發功能,** 增強綜合使用體驗。** 圍繞 NASL 的語言設施有很多,比如提供增刪改查、複製貼上等功能的各類視覺化設計器,提供多人協作、備份還原等功能的程式碼倉庫,提供型別檢查、查詢引用等功能的 Language Server,提供斷點除錯、變數監視等功能的 Debugger,能生成前後端原始碼的語言生成器,提供區域性模板、依賴庫匯入匯出等功能的資產中心,……
而在大模型時代下,NASL 又起到了一大新的作用:AI 友好和快速落地 AI 功能 的作用。AI 友好主要體現在不需要讓大模型生成傳統程式設計的各種語言框架,減少程式碼量即 token 數、以及各層次之間的轉換問題。快速落地 AI 功能主要體現在只要提供 AI 服務 + 互動輸入 代替或融入原來視覺化設計器的互動形式即可將 AI 能力快速引入到產品中。

CodeWave 引入 AI 功能的產品架構圖
然而讓大模型生成一門新的語言,也給 CodeWave 技術團隊帶來了 挑戰。由於當前各家大模型均不具備 NASL 相關知識,經過產品實踐,直接使用提示工程生成的內容在某些細節方面不太理想。下表是基於通用大模型 + 提示工程生成的 NASL 邏輯使用 HumanEval Benchmark 的評測,總體上只有 55.3% 的透過率。除了 25% 左右是工程側和語言方面可以最佳化的問題,仍有 18.3% 的分數如果不透過模型訓練就提升不了。

早期使用 HumanEval Benchmark 對生成 NASL 邏輯的評測
因此 CodeWave 技術團隊持續研究對已有模型怎麼進行微調訓練,增加其在 NASL 領域的相關能力,來提升 CodeWave 中自然語言生成邏輯、程式碼補全等 AI 產品功能的效果。
類似針對通用程式設計程式碼模型的訓練工作,NASL 大模型的訓練也大致包含以下環節:
模型訓練的首要環節是選擇一個合適的基座模型。
對於基座模型,一般會在六個關鍵方面進行評測,包括程式碼生成、程式碼補全、程式碼推理、長上下文生成、一般自然語言理解、數學推理。根據 NASL 語言的特點,我們主要測重對前四個方面進行評測,考察了目前最流行和最強大的各個開源語言模型基座,包括 DeepSeek Coder 系列、Qwen Coder 系列、StarCoder2 系列等,進行具體評測:

程式碼生成能力是程式碼模型處理更復雜任務的基礎,流行的程式碼生成基準測試是 HumanEval 和 MBPP。HumanEval 包含 164 個手動編寫的程式設計任務,每個任務都提供一個 Python 函式簽名和一個文件字串作為模型的輸入。而 MBPP 包含 974 個開源貢獻者建立的問題,包括一個問題描述(即文件字串)、一個函式簽名和三個測試用例。
為了確保更準確的評估,23 年 HumanEval+ 增加了 80 倍的測試用例,修正了 HumanEval 中不準確的真實答案。同樣,MBPP+ 也提供了是原始 MBPP 35 倍的測試用例。
Qwen2.5-Coder 在基礎程式碼生成任務上表現得最出色,在相同規模的開源模型中達到最先進的水平。
許多智慧程式設計輔助工具依賴於基於前後程式碼片段的程式碼補全能力,CodeWave 的 NASL 補全也不例外。流行的程式碼補全基準測試包括 HumanEval-FIM、CrossCodeEval、CrossCodeLongEval、RepoEval 和 SAFIM 等。但 NASL 的補全需要根據使用者資料自行構造。同時要考慮程式碼補全的體驗重在即時性,對模型的規模有較大限制。
經過測評,NASL 程式碼補全選擇 DeepSeek-Coder-1.3B-Base 的效果最好。
程式碼是一種高度抽象的邏輯語言,程式碼推理能力能判斷模型是否真正理解了程式碼背後的推理流程。一般選擇 CRUXEval 作為基準測試,包含 800 個 Python 函式及其對應的輸入輸出示例。它包含兩個不同的任務:CRUXEval-I,要求大型語言模型(LLM)根據給定的輸入預測輸出;CRUXEval-0,要求模型根據已知的輸出推斷輸入。對於 CRUXEval-I 和 CRUXEval-0,一般使用思維鏈(CoT)方法,要求 LLM 在模擬執行過程中按順序輸出步驟。
長上下文理解能力 對於程式碼大模型在處理複雜需求時很重要,它是理解倉庫級程式碼和成為程式碼智慧體的必要能力。然而,當前大多數程式碼模型對輸入長度的支援仍非常有限,這阻礙了它們的實際應用潛力。在 CodeWave 的智慧生成場景中,一般需要攜帶很多上下文,如實體、資料結構、邏輯定義等 應用上下文,擴充套件元件、依賴庫、介面定義、聯結器等 平臺資產上下文,以及使用者開啟的標籤頁、游標位置等 互動上下文。因此我們對支援 128k token 以上輸入長度的模型特別關注,並進行了調研評測。
資料準備是模型訓練中的關鍵環節。
雖然 CodeWave AI 工程化平臺收集到了一定量的使用者資料,但使用者提供的高質量程式碼數量有限,遠不及通用程式語言的數量。因此如何突破 “資料冷啟動” 實現 NASL 大模型訓練,是資料準備階段面臨的主要問題。經過對已採集的資料進行清洗和分析,我們決定主要採用資料合成的方式來構造資料。需要合成的資料主要是 “自然語言指令 -> NASL 程式碼答案”的問答對形式,下面是一個簡單示例:
// 使用者指令:接受一個字串列表,並返回最短的字串。如果有多個,返回第一個出現的字串。
// 合成的 NASL 程式碼:
logic findShortest(strings: List<String>) => result {
let lengths: List<Integer>;
let minLength: Integer;
let shortestString: String;
if (Length(strings) == 0) {
result = '';
end;
}
lengths = ListTransform(strings, { item => Length(item) });
minLength = ListMin(lengths);
shortestString = ListFind(strings, { item => Length(item) == minLength });
result = shortestString;
end;
}
和傳統程式設計大模型的資料準備工作類似,我們彙總了多個開原始碼資料集,提取了大量自然語言指令、程式碼答案和測試用例,結合 Codewave 使用場景合成了低程式碼指令作為補充。英文指令使用 LLM 翻譯為中文,去重後共計 10W+ 的指令資料。
為了全面評價問答對的質量,我們為每個樣本引入了多個維度的打分點:
-
問題和答案的一致性: 問答是否一致,是否正確,以便進行微調。
-
問答相關性: 問答是否與 Web 程式設計領域、CodeWave 產品功能相關。
-
問答難度: 問答是否具有足夠的挑戰性。
-
程式碼語法的正確性: 評價程式碼是否有語法錯誤,是否能解析回 NASL AST。
-
程式碼靜態語義的正確性: 評價程式碼是否能透過靜態檢查,如型別檢查、重名校驗、引數範圍等。
-
程式碼動態執行的正確性: 評價程式碼是否能透過測試用例,執行結果符合預期。
-
易理解性和最佳實踐: 評價程式碼是否容易理解,是否遵循 CodeWave 使用的最佳實踐。
在打完各項評分後,透過權重求和計算得出每個樣本的總分,便可評價問答對的質量。
上文中的打分點前 3 點側重於對問題質量的評價,以人工打分為主;後 4 點側重於對程式碼答案質量的評價,不僅可用於對樣本的評價,也可用於對大模型生成結果的評價。
為了自動化高效地驗證程式碼各方面的正確性,考慮到 CodeWave 真實環境編譯時間過長、需要與之隔離等問題,CodeWave 程式語言實驗室實現了一套輕量級 NASL 語言沙箱,在一臺訓練機器上就可以部署大量程序,主要用於檢查 NASL 程式碼語法和語義的正確性、在隔離環境安全地執行程式碼、將程式碼執行的輸出結果與測試用例的預期結果進行比較和彙總。
NASL 沙箱具體包含以下幾個部分:
-
語言編譯器:
-
解析器: 將 NASL 文字解析成 NASL AST,並同時判斷語法正確性
-
語言服務: 對語言進行靜態檢查,主要包括型別檢查、重名校驗、引數範圍等
-
最佳實踐檢查: 靜態分析程式碼是否符合最佳實踐,比如是否用了更合適的內建函式,迴圈中是否用了資料查詢等
-
程式碼執行引擎:
-
環境隔離: 為了安全執行程式碼,與部署機器隔離
-
並行執行: 能夠並行執行多個測試用例、多個程式碼測試請求、多套上下文環境
-
資源分配和超時機制: 針對超時的執行程序能夠及時釋放,比如死迴圈程式碼引發的超時等問題
-
系統和瀏覽器環境模擬: 支援對當前日期時間、域名、瀏覽器裝置資訊進行模擬
-
資料庫環境模擬:NASL 執行時的測試經常需要資料庫中有合適的資料,沙箱支援建立輕量級資料庫,並且快速建表和填充資料
-
資產的接入和模擬: 支援依賴庫、元件庫、介面、聯結器等資產接入,可以模擬介面、聯結器的實現等
-
結果分析器:
-
結果比較: 將程式碼執行的輸出結果與測試用例的預期結果進行比較,對巢狀結構會進行深度比較
-
報告彙總: 彙總生成測試用例成功和失敗的詳細報告
開原始碼資料集中包含大量自然語言指令,但程式碼答案是通用程式語言的,所以需要用通用大模型 + 自然語言指令提示詞的方式合成 NASL 程式碼。但由於各家大模型均不瞭解 NASL,即使輸入 NASL 語言的完整說明書,大模型也很難生成語法語義正確的 NASL 程式碼。
因此我們引入了多輪反饋機制,將沙箱編譯和執行時的報錯資訊即時反饋給大模型,讓大模型對生成的程式碼進行修改,反覆執行這個過程,直到生成的結果正確或者達到預定的最大修改輪數(比如 5 輪)。
剛開始,主要使用 gpt-4o/4o-mini 作為資料合成的核心 LLM。當指令比較簡單時,比如簡單演算法、簡單資料查詢,經過幾輪區域性的細節修改,基本可以生成完全正確的 NASL 程式碼,比如:
// 使用者指令:根據申請狀態查詢請假申請記錄,支援分頁,按申請時間升序排列
// 第 1 次生成
logic queryLeaveRequests(applyStatus: app.enums.ApplyStatus, page: Integer, size: Integer) => result {
result = for LeaveRequest in app.dataSources.defaultDS.entities.LeaveRequestEntity
where LeaveRequest.applyStatus == applyStatus
order by LeaveRequest.createdTime asc;
result = Paginate(result, page, size);
end;
}
// NASL 沙箱解析器報錯:缺少 select 語句
// 第 2 次生成
logic queryLeaveRequests(applyStatus: app.enums.ApplyStatus, page: Integer, size: Integer) => result {
result = for LeaveRequest in app.dataSources.defaultDS.entities.LeaveRequestEntity
where LeaveRequest.applyStatus == applyStatus
order by LeaveRequest.createdTime asc
select {
leaveRequest: LeaveRequest
};
result = Paginate(result, page, size);
end;
}
// NASL 沙箱最佳實踐報錯:語句順序不是最佳實踐,期望的呼叫順序為 for -> join -> where -> group by -> select/aggr select -> having -> order by -> paginate
// 第 3 次生成
logic queryLeaveRequests(applyStatus: app.enums.ApplyStatus, page: Integer, size: Integer) => result {
result = for LeaveRequest in app.dataSources.defaultDS.entities.LeaveRequestEntity
where LeaveRequest.applyStatus == applyStatus
select {
leaveRequest: LeaveRequest
}
order by LeaveRequest.createdTime asc;
result = Paginate(result, page, size);
end;
}
// NASL 沙箱型別檢查報錯:賦值:型別不一致!右邊型別:{ list: List<{ leaveRequest: LeaveRequest; }>; total: 整數 ; },左邊型別:List<{ leaveRequest: LeaveRequest; }>。result = paginate(result, page, size)
// 第 4 次生成
logic queryLeaveRequests(applyStatus: app.enums.ApplyStatus, page: Integer, size: Integer) => result: {
list: List<{ leaveRequest: app.dataSources.defaultDS.entities.LeaveRequest }>;
total: Integer;
} {
result = for LeaveRequest in app.dataSources.defaultDS.entities.LeaveRequestEntity
where LeaveRequest.applyStatus == applyStatus
select {
leaveRequest: LeaveRequest
}
order by LeaveRequest.createdTime asc
paginate page, size;
end;
}
// NASL 沙箱編譯成功,型別檢查成功,合成結束。
但當指令稍微複雜一些,比如既要做資料查詢又要基於查詢結果做邏輯處理的情況,採用非推理類模型,模型無法充分理解和遵循 NASL 語法語義的所有要求,基本都無法生成正確的結果。往往是在修復一個問題的同時又引入了其它問題,嚴重降低了資料合成的成功率和效率。
在這種情況下,我們引入了 DeepSeek-R1 模型,經過思考的過程,讓模型充分注意到語法中的各項要求。並且在收到錯誤反饋後,也更善於從整體實現的角度去進行修復,顯著提升程式碼生成的成功率。在合成數據的對比中,相比非深度思考模型有著顯著的收益:

下面透過一個例子來對比 DeepSeek-R1 模型和原模型的效果差異。原來使用 gpt-4o-mini 經過 5 輪修復後,編譯或執行仍然有問題:
// 使用者指令:根據是否顯示的標誌查詢咖啡資訊,並返回 CoffeeOrderItem 列表
// 已知 CoffeeOrderItem 如下:
structCoffeeOrderItem{
/* 咖啡 */
coffee: app.dataSources.defaultDS.entities.Coffee;
/* 下單數量 */
count: Integer;
}
// gpt-4o-mini 經過 5 輪修復後的結果,但編譯仍然有問題
logic loadCoffeeByIsDisplay() => coffeeList: List<app.structures.CoffeeOrderItem> {
coffeeList = forCoffeein app.dataSources.defaultDS.entities.CoffeeEntity
whereCoffee.isDisplay == true
select {
coffee: Coffee,
count: COUNT(Coffee.id) // 這裡假設數量是根據咖啡 ID 的數量來計算的
}
);
end;
}
錯誤原因是這裡 CoffeeOrderItem 中 count 屬性只是用於前端下單時的資料佔位。但 gpt-4o-mini 無法理解這點,習慣性當作聚合函式 COUNT 的語義來返回。即使 NASL 沙箱給出提示,gpt-4o-mini 依舊無法給出正確的程式碼。DeepSeek-R1 只要單輪就能生成正確結果。R1 的輸出結果如下,可以看出, R1 一開始也考慮使用 COUNT 聚合函式,但它經過自我反問,很快意識到這一點不符合要求,隨後推理出只能在資料查詢之後用邏輯來進行處理。
/**
r1 的思考過程:
好的,我需要根據使用者提供的 NASL 語言定義和專案上下文,編寫一個函式來查詢咖啡資訊並返回 CoffeeOrderItem 列表。首先,使用者的需求是根據是否顯示的標誌查詢咖啡資訊,所以需要用到資料庫查詢。
根據 NASL 的限制,必須使用 FROM 函式來構建查詢鏈。專案上下文中,Coffee 實體有一個 isDisplay 欄位,而 CoffeeOrderItem 需要包含 Coffee 實體和下單數量 count。但使用者指令中沒有提到數量的來源,可能需要進一步確認,但假設 count 可能是一個聚合值、固定值或需要其他處理,但根據結構體定義,可能需要從查詢結果中構造。
接下來,我需要使用 FROM 函式,傳入 CoffeeEntity,並在回撥中構建查詢鏈。WHERE 條件應該是 Coffee.isDisplay 等於傳入的引數,但使用者函式頭沒有引數,可能需要調整。不過根據指令,可能 isDisplay 是作為過濾條件,但函式沒有引數,可能預設查詢 isDisplay 為 true 的記錄?
然後,在 SELECT 部分,需要構造 CoffeeOrderItem 的結構,即 coffee 欄位和 count 欄位。但 count 的來源不明確,可能需要設定為 0 或其他值,或者可能有其他邏輯。但根據結構體定義,count 是 Integer 型別,可能需要從查詢結果中獲取,但當前上下文中沒有相關資料,可能需要預設值,比如每個咖啡記錄 count 設為 0。
此外,需要注意 SELECT 的命名規範,轉換表名和欄位名的首字母小寫。例如,Coffee 實體在 SELECT 中應轉換為 coffee,結構體欄位名保持不變。
最後,將查詢結果轉換為 CoffeeOrderItem 列表。可能需要使用 ListTransform 來處理查詢結果,新增 count 欄位。或者,在 SELECT 中直接構造結構體,但需要確保欄位匹配。
檢查 NASL 的限制,確保不使用禁止的語法,如迴圈,而是使用內建函式。同時,確保正確使用實體和結構體,避免型別錯誤。
*/
logic loadCoffeeByIsDisplay() {
// 查詢所有 isDisplay 為 true 的咖啡資訊
let coffeeList = for Coffee in app.dataSources.defaultDS.entities.CoffeeEntity
where Coffee.isDisplay == true
select {
coffee: Coffee,
}
);
// 轉換為 CoffeeOrderItem 列表並設定數量為 0
result = ListTransform(coffeeList, { item => new app.structures.CoffeeStructure({
coffee: item.coffee,
count: 0
}) });
end;
}
資料後處理是一個規則模組,根據模型評測中發現的程式碼和資料問題,設計進一步的過濾和組裝策略。
-
過濾模組: 總結分析模型評測過程中發現的問題,定位到訓練集中的錯誤資料樣本,設計使用規則對其快速過濾,提升訓練集質量。
-
組裝模組: 根據實際使用的場景,對已有資料進行重新組裝,保證輸入的資料與線上真實場景的上下文環境保持一致。
我們採用了從粗到細的多階段訓練方案,先使用初始構造的資料集訓練得到初始模型,再基於訓練後的模型做拒絕取樣,利用 NASL 沙箱對取樣程式碼進行驗證,將驗證透過的資料迴流到資料集中,以此豐富資料集的多樣性並提升資料質量。
另外在不同階段對模型效果進行分析評測,針對分析結果進一步調整資料配比和資料質量。重複以上過程,直至達到比較穩定的效果。在 NASL 模型微調中,所有引數最佳化均採用 LORA 方案。
本階段在 SFT 模型的基礎上,透過提供明確的負反饋資訊,來強化模型輸出與人類偏好的對齊程度,採用 DPO(Direct Preference Optimization)的演算法方案。
對微調後的模型進行取樣,根據 NASL 沙箱反饋作為正負樣本劃分依據,構造<問題, 正確程式碼, 錯誤程式碼>三元組資料,用於離線強化學習的訓練。
目前 NASL 大模型在多領域多場景的訓練過程中,基座模型的選擇與資料準備的合成階段根據評測效果按需採用 DeepSeek 模型。比如程式碼補全重在體驗的即時性,對模型的規模有限制要求,因此目前 NASL 程式碼補全的模型基座選擇的是 DeepSeek-Coder-1.3B-Base。而在資料準備的指令和答案合成階段深度使用了 DeepSeek R1 模型,特別是答案合成,在切換用深度思考的 DeepSeek-R1 模型之後,收益顯著。
最後,CodeWave 團隊針對產品中各項 AI 功能,正在對接 DeepSeek R1 的滿血版和殘血版進行全面評測。也在持續關注和跟蹤 DeepSeek 深度思考、Coder、多模態等各類模型的後續發展。
可參閱 CodeWave 前期相關文章:
-
CodeWave 智慧開發平臺為什麼要做程式語言?
-
在 AI 大模型重塑軟體開發的時代,我們如何把握變革?如何突破技術邊界?4 月 10-12 日,QCon 全球軟體開發大會· 北京站 邀你共赴 3 天沉浸式學習,跳出「技術繭房」,探索前沿科技的無限可能。
本次大會將匯聚頂尖技術專家、創新實踐者,共同探討多行業 AI 落地應用,分享一手實踐經驗,深度參與 DeepSeek 主題圓桌,洞見未來趨勢。

今日薦文
