百度關(guān)于大模型在研發(fā)領(lǐng)域落地的深度思考
一、智能研發(fā)工具的發(fā)展
首先來看一下智能研發(fā)工具的發(fā)展歷程和方向。
1. 智能化的發(fā)展背景與落地訴求
早期的智能化工具,如 GitHub 的 Copilot 工具,大約在兩年半前推出。最初,Copilot 的主要功能是在開發(fā)者編寫代碼時提供自動補全建議。隨著時間的發(fā)展,其功能逐漸豐富,現(xiàn)在的 Copilot chat 插件支持語言轉(zhuǎn)換、單元測試等多種功能。
隨著大模型的發(fā)展,所有相關(guān)產(chǎn)品都在迅速升級進化。從最初的輔助編碼開始,到現(xiàn)在能夠與開發(fā)者協(xié)同工作,我們甚至可以將部分任務(wù)完全交給 AI 完成。由代碼補全轉(zhuǎn)變?yōu)榇a生成,不僅是在代碼基礎(chǔ)上生成代碼,更多是在需求的基礎(chǔ)上去生成代碼,從單純的寫代碼轉(zhuǎn)變?yōu)榉?wù)于整個工程。例如,最近 Copilot 推出的幫用戶搭建單元測試環(huán)境的功能,已經(jīng)超越了單純的代碼編寫層面。
接下來,將從代碼生成的體量、工具職責(zé)的變化、效果的優(yōu)化、能力的提升以及用戶體驗交互方式上的創(chuàng)新等幾個方面來介紹這一工具近期發(fā)生的變化。
2. Go Fat:更大規(guī)模的代碼補全
首先是“Go Fat”,也就是代碼補全的規(guī)模在不斷變大。代碼補全是最經(jīng)典的智能開發(fā)形式。在編寫代碼過程中,自動出現(xiàn)一行灰色的提示,這并非通過函數(shù)定義等方式自動生成,而是基于大模型推測出一段話。這種形式對于開發(fā)者來說最容易理解和使用,因為它將敲幾個字符的時間簡化為按下 Tab 鍵的時間。在此基礎(chǔ)上,所有工具都希望在符合用戶習(xí)慣的前提下進一步升級。
從單行代碼的補全,逐步擴展到代碼塊,甚至基于注釋生成整個方法或函數(shù),從而擴大補全的范圍。
然而,隨著模型生成的代碼量的增加,錯誤率也會相應(yīng)上升。尤其是當(dāng)一個工具或模型在不了解具體業(yè)務(wù)需求的情況下自動生成代碼時,錯誤率往往更高。一旦錯誤率達到一定比例,比如超過一半,那么人工修正這些錯誤所花費的時間將抵消甚至超過工具帶來的效率提升。因此,對于工具而言,必須通過結(jié)合模型的編程現(xiàn)場、項目需求以及用戶的操作歷史等多維度信息來提升準(zhǔn)確率,使其達到一個既準(zhǔn)確又高效的水平。
所有工具都需要解決四個核心問題:觸發(fā)時機、推理邏輯、結(jié)束位置和檢查質(zhì)量。
觸發(fā)時機的核心在于判斷何時能夠補全一塊代碼,何時應(yīng)僅補全一行代碼。顯然,這取決于代碼中重復(fù)性和規(guī)律性的特點。例如,在存在 if-else,for 或 switch 結(jié)構(gòu)時,代碼往往具有明顯的規(guī)律性,很多 switch 的 case 中的代碼都非常相似,因此許多工具會在這些場景下觸發(fā)多行代碼的補全。在其他場景下,工具可能還是會退回到單行補全。另一種特征是注釋,當(dāng)你已經(jīng)寫了一行注釋時,工具可以根據(jù)該注釋進一步推理代碼,從而獲得更完整的上下文信息,提高準(zhǔn)確性。觸發(fā)時機是通過產(chǎn)品層面的語法樹解析來解決的,以達到高質(zhì)量、低錯誤率的目標(biāo)。
第二是推理邏輯,將什么東西輸入給模型,模型才能更好的推理。最簡單的情況是將光標(biāo)位置上下的兩段代碼作為輸入,模型在中間插入新代碼。除此之外,模型還可以利用文件依賴關(guān)系和 import 的內(nèi)容來更好地進行推斷。例如,當(dāng)你編寫代碼時,通常會打開其他文件以查看可用的功能或變量,然后返回繼續(xù)編寫。因此,你最近打開的文件在一定程度上可能會被用到。對此,在后文中還將進行更詳細的說明。
第三是結(jié)束位置。由于模型本質(zhì)上是一個續(xù)寫工具,它根據(jù)前文生成后文,但若不斷續(xù)寫最后一定是錯誤的。并且代碼過長可能會無法使用,少量代碼行則可能更為實用。因此,如何適時地結(jié)束代碼生成是關(guān)鍵問題。然而,正因為代碼是多行的,沒法僅憑換行符作為 stop sequence 的結(jié)束標(biāo)志,我們需要通過程序方式檢測模型輸出的內(nèi)容是否應(yīng)結(jié)束。這涉及多種策略,例如:當(dāng)觸發(fā)時機為一個代碼塊時,我們可以在遇到匹配的右括號時結(jié)束;如果是 if 里面還有個 if,那等外層的 if 結(jié)束時結(jié)束;如果觸發(fā)時機是一個注釋,那么連續(xù)的換行或下一個注釋的出現(xiàn)可能是合適的結(jié)束信號;如果代碼塊之間存在空行時,這通常意味著不同的邏輯塊的開始,因此可以在此結(jié)束。這些策略與觸發(fā)時機密切相關(guān),旨在確保代碼塊的適時結(jié)束。
接下來再看檢查質(zhì)量。在使用模型時,我們偶爾會遇到一些問題,例如模型生成完全重復(fù)的代碼片段,這是一解決個顯而易見且需要的問題,但對于代碼質(zhì)量而言,還存在其他明顯的不符合標(biāo)準(zhǔn)的情況。例如,在訓(xùn)練過程中可能會遇到數(shù)據(jù)污染,導(dǎo)致模型生成包含韓文字符的注釋。顯然,無論是面向客戶還是我們自己使用,這種情況都是不可接受的。因此,我們的首要任務(wù)是通過策略來屏蔽這些錯誤內(nèi)容的輸出,第二就是追加模型訓(xùn)練。除此之外,還有一類模型很容易出現(xiàn)的問題就是代碼過度擬合,生成的代碼與現(xiàn)有代碼過度相似,甚至完全相同,導(dǎo)致無法直接使用,這類問題也要解決掉。最終,通過質(zhì)量檢查和過濾措施,將高質(zhì)量的代碼呈現(xiàn)到用戶面前。
通過上述工作,多行代碼補全的正確率或者用戶采納率,都可以達到平均值以上。
3. Be Rich:豐富的生成能力
除了協(xié)助補全代碼,我們還需探索模型在更多場景中的應(yīng)用潛力。一個顯著的例子便是代碼優(yōu)化。在日常業(yè)務(wù)開發(fā)中,寫面條代碼是再平常不過的事。然而,當(dāng)業(yè)務(wù)已經(jīng)十分繁忙時,再要求他們回頭審視代碼,進行重構(gòu)與優(yōu)化,無疑是一種難以接受的工作模式。如果將這類任務(wù)交給模型,能夠得到怎樣的幫助呢?
首先,模型會指出代碼中存在重復(fù)部分,并建議將這些重復(fù)的內(nèi)容抽象化,通過創(chuàng)建一個函數(shù)來封裝它們。其次,模型會指出代碼中的硬編碼問題,如果未來需要修改這些編碼,而其他地方未同步更新,可能會導(dǎo)致問題。因此,它建議提取一些常量,以確保所有相關(guān)部分都能通過修改常量來同步更新。第三,它會指出代碼中存在的類型安全問題,比如某個類型應(yīng)該是從其他部分獲取的,但代碼中沒有進行相應(yīng)的判斷。因此,模型會建議修改語法,采用問號點等方式來增強類型安全性。第四,模型會指出代碼中的命名不規(guī)范問題,并提供一段經(jīng)過優(yōu)化的代碼作為示例,經(jīng)過評測,這段代碼是可靠的。當(dāng)然,在將這段代碼應(yīng)用到原始代碼之前,我們進行了一些工程化處理,以確保它們能夠?qū)?。這樣,你可以直接采納這段代碼,從而自然地實現(xiàn)一些常見的優(yōu)化,而這些優(yōu)化既不影響業(yè)務(wù)邏輯,又是相對可靠和正確的。因此,在編碼過程中,我們可以將一部分工作,特別是事后的優(yōu)化和檢查,交給模型來完成,讓它與我們協(xié)同開發(fā)。這與以前模型中只能單純編寫一兩行代碼的情況相比,已經(jīng)有了截然不同的效果。
再來看另一個場景,即函數(shù)代碼量過大的情況。當(dāng)我們不小心編寫了一個 500 行的函數(shù)時,作為工程師的第一反應(yīng)通常是認為這個函數(shù)需要拆分。然而,對于忙碌的工程師來說,可能會因為時間緊張而無法立即進行拆分。那么,在這個場景下,模型能做些什么呢?
以前端場景為例,假設(shè)我們有一個展示在界面上的組件或者元素。模型首先會在這個組件中抽取一些公共的功能,并將它們封裝成一個函數(shù),使得這個函數(shù)可以在多個地方復(fù)用。其次,模型會在函數(shù)的基礎(chǔ)上再做一層封裝,這在 React 框架中被稱為 hook,這種封裝依然保持了代碼的可復(fù)用性。接下來,模型會進行第三層的拆分,將一個大組件拆分成多個小組件。比如,一個包含框、標(biāo)題和內(nèi)容的組件,模型會將其拆分成獨立的標(biāo)題、內(nèi)容和布局(框)組件。這樣的拆分有助于從代碼實踐的角度將組件分離成不同的層次,然后再通過組合的方式將它們重新整合在一起。當(dāng)然,這并不是通過簡單的 prompt 就能實現(xiàn)的。這背后其實是我們平時大量實踐經(jīng)驗的積累,需要將這些經(jīng)驗轉(zhuǎn)化為復(fù)雜的 prompt,并轉(zhuǎn)化為一系列類似于 One-shot 或 Few-shots 的例子,放入模型的上下文中。
這是我非常期望的開發(fā)類工具所能達到的效果,將我們具有豐富經(jīng)驗的工程師的實踐融入到產(chǎn)品中,讓所有人都能享受到這些經(jīng)驗所帶來的好處。
4. Seek Deep:更深度地了解全庫
接下來,來看看產(chǎn)品的第三個進度,這主要體現(xiàn)在它對整個代碼庫的了解程度上。最初的工具對代碼庫沒有任何感知,只是根據(jù)光標(biāo)前后的內(nèi)容憑感覺生成代碼,這在實際業(yè)務(wù)中很容易出錯。因此,后來的工具都發(fā)展出了全庫代碼索引的概念。簡單來說,全庫代碼索引是通過向量化 embedding 技術(shù),將代碼庫中的所有代碼進行索引,然后根據(jù)用戶的需求將其轉(zhuǎn)化為向量,并進行向量的相似度計算,從而找到相關(guān)的代碼。這個過程雖然復(fù)雜,但效果卻比較明顯。
例如,當(dāng)你詢問一個方法的作用時,如果工具沒有了解整個代碼庫,它可能只會告訴你這個方法看起來像是一個用于對話的方法,并逐行解釋代碼。然而,這對于大多數(shù)工程師來說并沒有太大的價值。但如果工具了解整個代碼庫,它就能為你梳理出整條業(yè)務(wù)流程,并生成一個流程圖。這個流程圖并不是簡單的函數(shù)調(diào)用關(guān)系,而是真正業(yè)務(wù)層面上的流程。
當(dāng)你需要啟動一個會話時,工具會分情況根據(jù)你調(diào)用的方法,分析出下游做了哪些復(fù)雜的流程,并分析出整個業(yè)務(wù)流程。特別是當(dāng)你剛接觸一個代碼庫,還不熟悉其業(yè)務(wù),但又急需完成一個需求或修復(fù)一個bug時,這種效果會非常明顯。
5. Create By Trust:值得信賴的工作
再審視我們當(dāng)前所使用的這些工具,還存在一個問題,即其生成的結(jié)果常常會產(chǎn)生幻覺。一旦這種現(xiàn)象發(fā)生,用戶的信任度便會大幅下降。那么,我們應(yīng)如何解決這一問題呢?在特定情境下,例如執(zhí)行某個命令時,該命令可能會在運行過程中突然報錯。面對此類錯誤,我們可以將其提交給模型,并結(jié)合一系列工程調(diào)優(yōu)手段進行修復(fù)。模型能夠精確地指出錯誤所在,具體到文件的某一行或某幾行代碼,這是因為我們擁有調(diào)用棧(step trace)信息。在修復(fù)問題后,我們可以清晰地觀察到代碼的變化。
這種做法的優(yōu)勢在于,當(dāng)你再次執(zhí)行該命令時,若看到命令成功運行,顯示為綠色,你便能自然而然地確認問題已被解決。在這種可驗證的場合下使用模型,我們可以以極低的成本迅速判斷模型輸出的正確性,若發(fā)現(xiàn)錯誤,則將其撤回即可。在這種情境下,我們與模型之間的協(xié)同配合,其效率遠高于人工逐行審查模型輸出的代碼。
6. AT Your Hand:更貼合現(xiàn)場的交互
接下來,我們探討一下交互方面的問題。當(dāng)前的眾多工具,包括我們自主開發(fā)的以及其他大模型應(yīng)用,一個最顯著的特征就是在界面邊緣添加一個側(cè)邊欄,內(nèi)置對話框以供聊天,這已成為大模型的主要使用方式。然而,對話框在編寫代碼時卻帶來了一個顯著問題:它會打斷編寫者的思路。當(dāng)我們?nèi)褙炞⒂诰帉懘a時,視線始終聚焦于代碼區(qū)域。如果此時需要查看對話框,就必須將視線從代碼上移開,轉(zhuǎn)至側(cè)邊欄,這無疑會打斷我們的編程節(jié)奏。對于編程人員而言,專注力至關(guān)重要。因此,現(xiàn)有的許多工具已開始嘗試將對話框融入代碼區(qū)域中,我們稱之為“行間對話(inline chat)”。在行間對話模式下,你可以直接在代碼光標(biāo)處與工具進行交互。例如,當(dāng)你需要編寫某段代碼時,只需簡單說明需求,大模型便會根據(jù)需求調(diào)用并返回相應(yīng)的代碼。
此外,還可以選擇一段代碼,請求工具根據(jù)需求進行修改,如添加邊界條件檢測、判斷返回值是否正確,甚至進行代碼優(yōu)化等,工具會在你的代碼基礎(chǔ)上進行編輯,并以紅綠塊的形式展示修改內(nèi)容。若你滿意修改結(jié)果,點擊確認即可保留;若不滿意,則點擊取消,修改內(nèi)容將被撤銷。這種方案能夠讓你在專注編寫代碼的同時,通過自然語言與大模型進行交互,從而更加高效地完成編程任務(wù)。
以上就是我們產(chǎn)品在交互方面的發(fā)展。
二、企業(yè)落地智能研發(fā)經(jīng)驗
1. 在企業(yè)中踏實落地開發(fā)智能化
從企業(yè)和團隊的角度來看,大模型無疑是一個極具潛力的工具,這一點我們都普遍認同。然而,當(dāng)我們將工具下放至每個個體時,卻會發(fā)現(xiàn)大家的接受程度參差不齊。有些人認為大模型對他們的幫助極大,工作效率有了百分之五六十甚至翻倍的提升,他們非常樂意接受并使用這一工具。而另一些人則可能覺得不習(xí)慣,偶爾的錯誤會讓他們感到困擾,覺得需要花費額外的時間去校對和修正,因此對大模型持保留態(tài)度。還有一些人可能習(xí)慣于原有的工作模式,不愿意因為引入大模型而做出改變,他們更傾向于等待,直到大模型成為主流后再考慮使用。
面對這種差異,如果團隊希望大模型能夠盡快被大家接受并發(fā)揮作用,那么我們需要做兩件事情:
第一,建立大家對大模型的心智認知。讓大家在日常工作的方方面面都能感受到大模型的存在,使他們在遇到問題時能夠自然而然地想到“或許我可以試試用大模型來解決”,這種心智的建立需要時間和持續(xù)的引導(dǎo)。
第二,增強大家對大模型效果的信心。雖然大模型在使用過程中可能會偶爾出現(xiàn)錯誤,但是不是兩次三次,實際上可能是一百次才可能出錯兩次,這種錯誤是相對較少的。只有當(dāng)大家真正相信大模型即高效又準(zhǔn)確時,大家才能愿意去接受它。
針對這兩件事情,我們的處理方式如下:首先,我們思考的是,在何種情況下能讓大家真正感受到大模型始終存在。若僅將大模型的應(yīng)用局限于編寫代碼的過程中,這顯然是不充分的。因為在編寫代碼時,大模型無非充當(dāng)了一個 IDE 插件的角色,一旦關(guān)閉,其存在感便蕩然無存。此后,我們也可能忘記啟動大模型以輔助其他工作。
2. 感知存在:融合 Devops 必經(jīng)鏈路
對于工程師而言,有一個不可或缺的環(huán)節(jié),即整個 Devops 鏈路。從需求提出,通過需求平臺,到編寫代碼,再到代碼評審、文檔查閱、測試、構(gòu)建、編譯驗證,直至最后的部署,這些步驟都是不可或缺的。代碼提交后,必須查看流水線狀態(tài);測試結(jié)果出來后,必須進行分析;部署上線后,還需確認線上運行情況及上線進度。我們希望在這些環(huán)節(jié)中融入大模型的應(yīng)用,即便不期望大模型能帶來顯著的幫助,也希望能通過頻繁的接觸,使工程師們在面對問題時,能夠自然而然地想到大模型。
因此,我們在產(chǎn)品中嘗試引入了以下幾個功能。
首要的是智能化的基于大模型的代碼評審。代碼評審對我們來說是一個必不可少的環(huán)節(jié),所有代碼必須經(jīng)過評審才能入庫。在這個環(huán)節(jié),我們引入了大模型來分析提交的代碼差異,并為大家提供一些推薦。具體推薦的內(nèi)容其實并不是最重要的,關(guān)鍵在于大模型能夠發(fā)現(xiàn)一些典型的問題。將這一功能應(yīng)用到我們公司內(nèi)部后,目前的結(jié)果是,在所有的代碼評審環(huán)節(jié)的評論中,有 20% 是由大模型生成的。其中,有 15% 被大家認為確實是有用的,是一個很好的功能,而非廢話。這個數(shù)字對我們來說其實并不高,因為 15% 的正確率,如果放在正常產(chǎn)品中,通常會引起質(zhì)疑,擔(dān)心會有錯誤。但我們的主要目標(biāo)并不是追求極高的正確率,而是讓大家感受到大模型在協(xié)助他們工作。
因此,我們并未將該功能作為產(chǎn)品下線,而是持續(xù)保留在第二個環(huán)節(jié),即流水線編譯失敗的階段。當(dāng)編譯失敗時,大模型會自動運行,分析失敗的原因,查看日志,提出可能的問題,并建議開發(fā)者通過哪些關(guān)鍵詞搜索網(wǎng)絡(luò),或者檢查代碼中可能存在的問題。這個功能并不直接產(chǎn)生任何代碼。
如果從大模型編寫代碼的角度來看,這個功能產(chǎn)生的代碼量為零,但它仍然能夠讓開發(fā)者隨時隨地感受到大模型的存在。因此,我們一直將該功能保留在線上,幫助大家進行最基礎(chǔ)、最簡單的分析。通過這樣不斷地向開發(fā)者推送大模型的能力,我們公司內(nèi)部目前已有超過 80% 的工程師在開發(fā)階段持續(xù)使用大模型相關(guān)的工具。
3. 效果信心:利用 CICD 確保效果
接下來,要探討的是如何讓工程師對大模型生成的結(jié)果有信心。在這方面,我們有一個非常有效的做法。我們所有的正規(guī)項目都配備了 CICD 流水線,它本質(zhì)上就是檢查代碼的正確性,確保代碼能夠成功編譯和運行。這個流水線為我們提供了一個最基礎(chǔ)、最可靠的信心保障,即我們提供的代碼至少是編譯通過的,能夠運行的。
在這個場景中,我們最深入的一個應(yīng)用就是使用大模型來編寫單元測試(UT)。這并不是簡單地讓大模型根據(jù)重要代碼生成單元測試用例,而是有一系列的處理流程。首先,大模型會刪除不需要測試的代碼,因為保留這些代碼會干擾大模型。同時受限于大模型的上下文長度和成本,它會通過代碼依賴關(guān)系添加更多的相關(guān)代碼,以確保大模型能夠生成有效的單元測試。在生成單元測試后,大模型并不會直接將其交給用戶。因為此時并不知道這些代碼是否正確,所以它會運行這些單元測試,并生成一個測試報告,根據(jù)這個報告大模型會進行翻譯,從最基礎(chǔ)的 Json 中獲取關(guān)鍵信息,例如如果我們用的是國內(nèi)的模型,它就會把英文改成中文。
這個報告通常會反映兩類問題。第一類問題是單元測試是否通過。如果單元測試失敗,可能是單元測試本身寫錯了,也可能是業(yè)務(wù)邏輯有問題。我們會通過另一個模型來判斷是哪種情況,并相應(yīng)地修正單元測試或業(yè)務(wù)邏輯。修正單元測試可能包括修改斷言、刪除錯誤的單元測試或讓大模型修復(fù)錯誤的單元測試。第二類問題是覆蓋率不夠。如果大模型生成的單元測試覆蓋率太低,我們會讓模型有針對性地去提升覆蓋率。我們會從覆蓋率報告中找出哪些行沒有被覆蓋,然后分析這些行是在哪些分支條件下,讓大模型解釋這個未覆蓋的分支是什么判斷條件,再將這個解釋吐回給大模型,讓大模型注意到這個情況,再讓大模型根據(jù)情況補充更多的單元測試。
通過不斷提升單元測試的正確性和覆蓋率,我們最終會得到一組既正確又全面的單元測試。然后,我們會通過增刪用例,將這些單元測試組合起來,生成最終的單元測試代碼交給用戶。
通過使用文心的小模型來進行不同復(fù)雜度的單元測試,我們能夠在模型規(guī)模幾乎小了 10 倍的情況下,達到與 GPT-4 基本相同的覆蓋率效果。當(dāng)然,這是在實驗室環(huán)境下可以多輪運行的結(jié)果。然而在實際用戶使用時,我們不能接受長時間的運行,因為那會影響用戶體驗。所以我們對時間上會有要求。
我們最終產(chǎn)品化的效果:首先,生成正確率要達到 100%,因為只要代碼能運行,那它一定是正確的;其次,行覆蓋率要達到 30% 以上;最后,整個過程的耗時要小于 30 秒。對于大模型一個比較大的生成任務(wù)來說,30 秒已經(jīng)是一個相對較短的時間了,而且在這個時間內(nèi),我們能夠提供 30% 的覆蓋率,這是我們產(chǎn)品化的最終結(jié)果。
對于用戶來說,這個能力幾乎是一個純收益的事情。他們不再需要管理代碼,只需要試錯、生成并保存即可。這在我們內(nèi)部提供了大量的大模型代碼生成量,也是用戶非常喜歡的一個能力。
由于該過程本就正確無誤,無需用戶手動保存文件,因此我們將其融入到代碼管理之中。當(dāng)提交代碼后,大模型會自動基于你的代碼生成單元測試,并再次提交一份包含單元測試的代碼補丁。對于這份代碼,你幾乎無需進行額外的審查,直接合并即可,因為其正確性已經(jīng)得到了保證。基于這樣的設(shè)計,我們可以讓用戶在全新的開發(fā)階段也無需關(guān)注單元測試的編寫,同時仍然能夠獲得具有高覆蓋率和正確性的單元測試代碼。
三、開發(fā)者智能工具使用實踐
1. 形式變革:智能時代理念
作為個體,在使用這些工具時應(yīng)該采取哪些實踐方法以更有效地發(fā)揮工具的作用呢?
未來,AI 與我們的關(guān)系絕不僅僅是工具與人之間的簡單關(guān)聯(lián),因此我們不會僅僅停留于使用 AI 的階段,而是致力于與 AI 實現(xiàn)真正的協(xié)同工作。所謂協(xié)同,意指在某些情況下,我們會將任務(wù)分配給 AI 完成,而在其他情況下,則親自處理。并非指在一項任務(wù)中,我僅負責(zé)前半部分而 AI 負責(zé)后半部分?;诖?,我在實踐中形成了一些個人的準(zhǔn)則。
2. 作為開發(fā)者擁抱智能化實踐
首先,關(guān)于代碼檢索這一任務(wù),我認為讓 AI 來完成是一個極為出色的選擇。其次,當(dāng)我利用 AI 來處理事務(wù)時,如何加強上下文信息,從而提升 AI 的準(zhǔn)確率。第三,我會分享如何管理自己的知識,使其能夠與 AI 有效關(guān)聯(lián)。最后,我們將討論如何與 AI 進行協(xié)同分工。
3. 一種全新的找代碼方法
我們先來探討代碼檢索這一任務(wù),這是我認為在代碼編寫領(lǐng)域中,AI 有可能真正超越人類的一項能力。人通常是如何尋找代碼的呢?我們往往會根據(jù)所需代碼的大致要求,先在腦海中構(gòu)想在代碼里面的方法名或者變量名,然后利用全局搜索功能進行查找。如果搜不到就換個名字繼續(xù)搜,如果找到了,就進一步檢查該函數(shù)被哪些其他函數(shù)調(diào)用,以及函數(shù)內(nèi)部調(diào)用了哪些其他函數(shù),即我們常說的“Go to Definition,Go to Reference”。通過這一系列的步驟,我們最終在腦海中匯總這些信息,得出一個結(jié)果。
然而如果整個代碼庫都具備 embedding 的向量能力,那么我們的模型就有辦法準(zhǔn)確地定位到代碼庫中的任意代碼片段。其次,模型天生就具備總結(jié)和解釋的能力,這使得它能夠在代碼檢索方面發(fā)揮出色。因此,我們設(shè)想了一個未來的場景:在尋找代碼時,我們可以直接使用自然語言進行搜索,而無需再糾結(jié)于具體的變量名或方法名。
在此,我有兩個實踐建議:第一,我們應(yīng)該描述要找的功能或需求,而不是局限于描述自己的代碼。第二,如果我們尋找的目標(biāo)不僅僅是代碼本身,而是對代碼進行整理和總結(jié)后的結(jié)論,那么我們可以直接讓模型為我們生成這個結(jié)論。
這里有兩個典型的例子來說明這一點。左邊第一個例子是要找代碼,關(guān)于在應(yīng)用程序中查找創(chuàng)建應(yīng)用表單的實現(xiàn)位置。如果我自己去找,可能會嘗試使用與“創(chuàng)建應(yīng)用”相關(guān)的詞匯,如“create”等,進行搜索,但很可能無法找到。因為這個代碼庫的命名方式較為特殊,它將創(chuàng)建應(yīng)用的功能命名為“fork”。我自己很難想到這個詞,但模型卻能夠幫我找到正確的位置。它指出在“fork”目錄下有“Info”和“UI”兩個文件,這兩個文件共同實現(xiàn)了創(chuàng)建應(yīng)用的功能。整個過程模型只用了大約二十秒,而我自己可能需要花費五分鐘甚至更長時間來嘗試不同的關(guān)鍵詞進行搜索。
第二個例子是關(guān)于總結(jié)系統(tǒng)暴露的路由數(shù)量。我并不是在尋找具體的代碼,而是希望模型能夠幫我總結(jié)在某個目錄下系統(tǒng)暴露了多少個路由,并給出它們的詳細信息。模型同樣出色地完成了這項任務(wù)。它列出了所有的路由,包括它們的請求方法(如 POST、GET 等)、目標(biāo)地址以及所在的文件位置。而我如果要自己去做這件事情,可能需要逐個打開 Controller 文件,查找其中的注解來識別路由,然后再將這些信息記錄下來。這同樣是一個非常耗時的過程,但模型只用了十幾秒就完成了。
因此,在代碼檢索這件事情上,模型的表現(xiàn)確實非常出色和可靠。
4. 讓相關(guān)代碼先走一步
接下來關(guān)注的是,我們在實際應(yīng)用中如何提升模型的表現(xiàn),以優(yōu)化輸出結(jié)果。這主要依賴于對大模型 prompt 的編寫,因為提示詞的質(zhì)量直接影響模型的表現(xiàn)。然而在代碼檢索或相關(guān)應(yīng)用中,用戶往往無法直接輸入提示詞,而是依賴于產(chǎn)品內(nèi)置的邏輯。這些內(nèi)置邏輯主要基于以下幾個核心原則。
第一,關(guān)于編寫 import 語句,建議盡早進行。我了解到許多開發(fā)者并不習(xí)慣首先編寫 import 語句,因為 IDE(集成開發(fā)環(huán)境)通常會在使用時自動補全。然而,如果你能夠預(yù)見到將會使用到某些模塊或庫,并提前將它們寫入 import 語句中,大模型就能提前知道。因為你的開發(fā)工具會根據(jù) import 語句自動查找并補全所需的依賴,從而增強模型的準(zhǔn)確性。
第二,如果你正在處理與當(dāng)前任務(wù)相關(guān)的文件,建議將它們打開并放置在旁邊。例如,當(dāng)你在編寫一個 Controller 時,它可能會依賴于 Service 層,而 Service 層又可能依賴于 DAO(數(shù)據(jù)訪問對象)。將這些相關(guān)的文件都打開并放置在視野內(nèi),開發(fā)工具都會讀取這些當(dāng)前打開的文件,并從中提取有用的信息提供給模型。
比如需要補充一個名為“APPID”的信息,如果僅依賴模型進行單獨補充,它可能會生成一個毫無意義的字符串,如“134567”,這樣的結(jié)果顯然不符合你的期望。然而,如果你已經(jīng)打開了一個包含環(huán)境變量的文件,并且該文件中有關(guān)于“APPID”的環(huán)境變量設(shè)置,那么模型或開發(fā)工具就能自動讀取這個環(huán)境變量,并將其準(zhǔn)確地補充到所需位置。這種能夠智能識別并利用當(dāng)前環(huán)境中已有信息的細節(jié)處理非常出色。
關(guān)于 import 語句,在導(dǎo)入了某個模塊或庫后,模型或開發(fā)工具就能自動在這個文件或庫中尋找你所需的方法。例如,如果你要調(diào)用一個名為 getPrice 的方法,并且該方法接受一個參數(shù),那么如果你已經(jīng) import 了這個方法,模型就能準(zhǔn)確地為你補全這段代碼,包括方法名和參數(shù)傳遞。然而,如果你沒有導(dǎo)入相應(yīng)的模塊,模型在嘗試補全代碼時可能會遇到困難,因為它無法確定你應(yīng)該調(diào)用的方法的確切名稱。在這種情況下,模型可能會給出一些與你的意圖不符的建議,比如 getTotal、getAmount 或其他任何可能的方法名。因此,import 語句的使用,對于確保模型能夠準(zhǔn)確補全代碼,效果非常好。
再來看之前提到的代碼補全問題,如果將其置于對話模式下進行考慮。這里舉一個最簡單的對話例子來說明,有一個普通的需求,即編寫一個組件,該組件的功能是向后端接口發(fā)送請求,并接收返回列表數(shù)據(jù),同時在表格中展示這些數(shù)據(jù)。這是我們在日常工作中經(jīng)常會遇到的任務(wù)。
如果我直接對模型表述這個需求,模型可能會根據(jù)當(dāng)前社區(qū)中最流行的技術(shù)方案來為我生成代碼。例如,如果模型認為 React 框架很流行,且通常使用 Axios 來發(fā)送請求,那么它可能會按照這種方式來編寫代碼。然而,這樣的代碼在我們的具體業(yè)務(wù)中是否真正有價值呢?這并不一定。因為我們的業(yè)務(wù)可能會采用不同的社區(qū)框架,如果生成的代碼沒有基于我們實際使用的框架,那么這些代碼對我們來說就可能是無用的。
那我應(yīng)該怎么做呢?在這個對話之前,我引用了一個在前端的項目文件 package.json,這個文件記錄了項目中安裝的所有第三方依賴的聲明。我僅僅做了這一件事情,其他的內(nèi)容都沒有改變。模型似乎注意項目中使用了 antd 和 react-query 這兩個庫,因此它決定使用這兩個庫來為我編寫代碼。在發(fā)送請求時,它選擇了使用 react-query 的 useQuery,接下來是 antd 的表格,并聲明了表格的列(columns)屬性。
這樣生成的代碼與我的實際需求匹配度高,而且在我的實際業(yè)務(wù)場景中可以直接使用,幾乎不需要進行任何修改。因此我們在編寫對話時,通過引用幾個非常核心的文件作為參考,可以立即將代碼的質(zhì)量從 0 分提升到七八十分的效果。這是我們在對話實踐中得出的一個經(jīng)驗。
而且,這個方法不僅適用于前端 JavaScript 項目中的 package.json,還適用于其他編程語言和框架。比如 Java 項目中的 pom.xml 和 gradle 文件,PHP 項目中的 composer.json 文件,以及 Python 項目中的 requirements.txt 文件。我認為在任何需要生成有用代碼的對話中,都可以無腦地將這些文件提供給模型作為參考,這樣生成的代碼就是有用的。
5. 你就慣著她吧
最后,我們來探討一些更理論層面的內(nèi)容。模型就擺在那里,它的能力如何,是否優(yōu)秀,這都是既定的。那么作為使用者,應(yīng)該如何去適應(yīng)模型呢?適應(yīng)的程度應(yīng)該達到這樣的程度,即了解模型執(zhí)行的時間。比如,當(dāng)我輸入一個“if”時,我知道模型后續(xù)不會只生成一行代碼,而是會展開成四五行代碼。因此,我會耐心地等待 2 秒,因為我覺得生成這些代碼可能需要這個時間。同樣地,如果我知道在某個情境下,模型只會生成一行代碼,并且這個過程可能非常迅速,比如 500 毫秒,那么我就會相應(yīng)地調(diào)整我的等待時間。如果模型在預(yù)定的時間內(nèi)沒有給出反饋,那么我就會停止等待。這是一種適應(yīng)。
另外,我還需要學(xué)會在何時給模型提供更好的推理上下文。之前也討論過,模型在某些情況下會表現(xiàn)得更加準(zhǔn)確,而在其他情況下則可能不盡如人意。比如,當(dāng)我在處理與業(yè)務(wù)高度相關(guān)的邏輯時,如果模型不了解具體的業(yè)務(wù)需求,那么它的預(yù)測結(jié)果很可能就不準(zhǔn)確。在這種情況下,我就會選擇自己編寫代碼,而不是等待模型的反饋。相反,如果編寫的是一段通用的算法模型,并且模型能夠通過函數(shù)名來推斷出我的需求,那么即使模型需要 3 秒、5 秒,我也會耐心地等待,因為模型生成的代碼通常會比我自己編寫的更加高效和準(zhǔn)確。
此外,還需要認識到模型并不是完美的,因此我們需要充分發(fā)揮模型的優(yōu)勢,同時避免它的缺點。有些任務(wù),比如找代碼、解釋代碼、讀代碼以及局部的重構(gòu)和改寫等,模型通常能夠比我更加高效地完成。而有些任務(wù),模型則可能無法勝任,這時就需要親自上陣。通過與模型的長期磨合,我們逐漸積累了這種經(jīng)驗。
總結(jié)起來,人和模型都有自己的擅長領(lǐng)域,而人擅長技術(shù)選型、任務(wù)規(guī)劃以及創(chuàng)造型的工作。
6. Focus, Let AI Run The Errands
那 AI 擅長什么呢?AI 模型擅長記憶和對細節(jié)的處理。它們能夠快速地記住大量的信息,幾個 T 的 Token 都是模型的記憶。此外,AI 還擅長處理一些瑣碎且細節(jié)豐富的工作,比如局部的代碼優(yōu)化、探索型的數(shù)據(jù)搜索、互聯(lián)網(wǎng)信息的搜集以及歸納總結(jié)等。
所以人類并不應(yīng)該與 AI 在這些方面競爭。相反,我們應(yīng)該充分發(fā)揮自己的優(yōu)勢,比如做規(guī)劃、進行創(chuàng)造性思考等,將重復(fù)勞動、瑣碎的事情交給 AI,這個稱之為 Focus,專注?!癓et AI run the errands”,即讓 AI 處理那些雜七雜八的事情。這是我認為當(dāng)真正改變了工作流以后,與AI協(xié)作的最好的關(guān)系。
7. 內(nèi)容總結(jié)
上圖中對我們的工具、企業(yè)和個人在 AI 發(fā)展上做的事情進行了總結(jié),在此不做贅述。
從未來的視角來看,我認為將無人駕駛作為一個比較對象是十分恰當(dāng)?shù)?。目前,無人駕駛技術(shù)處于 L2 與 L3 之間的發(fā)展階段,而我們的智能研發(fā)則尚處于 L0 與 L1 之間,未來一定會繼續(xù)發(fā)展,越往后“Transfer of responsibility”即它的職責(zé)將愈發(fā)向 AI 領(lǐng)域偏移。AI 將承擔(dān)越來越多的工作,而人類所需完成的事務(wù)則會相應(yīng)減少。我認為這是一個極為美好的前景,人類將得以投身于更多富有創(chuàng)新性的工作之中。因此,我們可以滿懷期待地憧憬未來,隨著我們不斷向右前行,最終人類將能夠?qū)W⒂诟邇r值的事業(yè)。
四、問答環(huán)節(jié)
Q1:分享中提到的了解全庫,將代碼邏輯繪制成圖形是純粹依賴于代碼生成,還是結(jié)合了業(yè)務(wù)上的需求文檔等?按案例中解讀的效果還是比較好的,如果單從代碼的邏輯流出發(fā),很可能得不出那樣比較細致的關(guān)系。
A1:我們現(xiàn)在的實現(xiàn)不需要依賴需求文檔,因為大部分產(chǎn)品的需求文檔質(zhì)量的不太高,還有可能是過時的。所以,首先我們是通過代碼之間的固定調(diào)用關(guān)系,通過語法解析生成代碼的知識圖譜,其次在代碼層面之上用大模型做代碼的解釋和增加注釋等工作。這些工作將建立自然語言與代碼之間的聯(lián)系,將兩者融合后,運用一系列綜合算法進行處理。例如,當(dāng)找到一個代碼塊時,會提取該代碼塊的解釋,并同時獲取其調(diào)用關(guān)系的圖譜以及圖譜的解釋。將這些信息整合后,再借助大型模型來生成該代碼塊整體流程的摘要。最終,將生成一個類似于之前所展示的圖,實際上這是一個通過 Mermaid 方法生成的圖,它將以一段 Mermaid 代碼的形式展現(xiàn)。
Q2:代碼關(guān)系抽取等工作是實時做的,還是已經(jīng)基于代碼庫離線處理好了?
A2:這些工作肯定是提前處理好的,代碼庫第一次打開時可能需要幾分鐘處理一遍,后續(xù)代碼修改則增量更新。
Q3:全項目進行向量化是在云端做的嗎?如何保障代碼的安全?
A3:這一環(huán)節(jié)確實是在云端完成的,首先它是安全的。我們將其分為兩個部分:第一部分是本地代碼傳輸?shù)皆贫诉^程中的網(wǎng)絡(luò)安全性,這一安全主要通過 https 協(xié)議來保障。若需進一步增強安全性,我們可以實施 https 協(xié)議的證書 Auth 校驗,以此防止通過私有證書進行劫持的風(fēng)險。這是傳輸部分的安全性。第二部分則是存儲環(huán)節(jié)的安全性。我們的方案是不直接存儲任何源代碼內(nèi)容,存下來的是 embedding 向量對應(yīng)的源代碼文件名和文件對應(yīng)的范圍,即代碼起始和結(jié)束的行號與列號,具體的代碼內(nèi)容我們并不存儲。在實際使用時,我們需要這段代碼時,會向客戶端發(fā)送一個請求,讓客戶端根據(jù)提供的信息找到相應(yīng)的代碼段。并且我們會通過一個哈希值進行校驗,以確認代碼內(nèi)容是否已被修改。如果校驗通過,再將這段代碼取回并供給模型使用。因此在存儲方面,我們通過不執(zhí)行任何落盤存儲操作來確保這些代碼本身不會因我們的安全風(fēng)險而泄露到外部。