與ChatGPT結(jié)對編程實(shí)現(xiàn)代碼詳解
以終為始
按照上一篇的架構(gòu),我們整個程序最后寫完的運(yùn)行視圖大概是下面這個樣子的:
可以看出我們的實(shí)際結(jié)果比我們上一篇文章考慮的還要復(fù)雜:
- 因?yàn)锳I生成的API不接受批量prompt處理,所以我們需要展開負(fù)責(zé)表達(dá)簡單排列組合的DSL-b生成一批DSL-b',再由DSL-b'一對一轉(zhuǎn)成DSL-c
- 因?yàn)锳PI的限制,不是服務(wù)端生成文件讓我們?nèi)ハ螺d而,而是直接把base64給我們發(fā)回來,所以不得不自己存文件還得考慮太多了怎么分分文件夾
- 因?yàn)槲覀兊哪繕?biāo)是了解哪些關(guān)鍵字和其圖片好用,還得寫點(diǎn)信息到png里,不然光看個圖片能看出什么來?(其實(shí)AI也寫了一些進(jìn)去,但是畢竟是混編后的,不太好讀)。而由于nodejs技術(shù)欠缺,不得不還要調(diào)用python
但總的來說,它還是上文那個架構(gòu),只是每一層都只寫了一個節(jié)點(diǎn),畢竟個人用,也不需要那么多擴(kuò)展。而即便如此也是個挺復(fù)雜的結(jié)構(gòu),這么復(fù)雜的結(jié)構(gòu),自然不能一下生成出來,我們需要分塊實(shí)現(xiàn),那么我們的每一塊要怎么實(shí)現(xiàn)呢?有什么套路呢?這將是我們接下來的內(nèi)容,內(nèi)容比較長,這一篇我們先講生成代碼這一步的套路。
如何編寫一個節(jié)點(diǎn)
編寫主干邏輯
我們以dsl_translator這個節(jié)點(diǎn)為例,一開始,我只是這么提問(并沒有,我只是演示一個錯誤示范):
我想用nodeJS用下面的yaml描繪的數(shù)據(jù)結(jié)構(gòu)得到一個新的數(shù)組:
base: steps: 10 batch_size: 1 poly: - template_prompt: template: > a cat, ${ chara } ${ facial_expressions } meta: view_angle: - side_view, - front_view, faal_expressions: - (smile:1.5), - (smile:1.2), - smile,
果然,ChatGPT也不慣著我,給出了我下面的代碼:
//...... const newArray = []; data.poly.forEach(item => { const obj = { template: item.template_prompt.template, meta: item.template_prompt.meta }; newArray.push(obj); }); console.log(newArray);
我仿佛聽到AI在說:“你就說是不是數(shù)組吧,你要數(shù)組,我給你數(shù)組,沒毛病~”
遇到這種問題,只能反思是自己沒說清楚,那么該怎么說清楚呢?
我們這個程序是服務(wù)于AI畫圖的,其實(shí)我們也可以從AI畫圖中借鑒不少經(jīng)驗(yàn),比如說在AI畫圖中,有一個control net里的open pose技術(shù),我可以畫個骨架,就像下面這樣:
我就可以用這個骨架圖去畫個穿靴子的貓,拿著個刺劍什么的。這個技術(shù)告訴我們什么呢?我們可以通過描述一個骨架來控制AI生成的內(nèi)容。那么圖片可以,編程是不是也可以?
于是經(jīng)過一段時間的摸索,最后我寫出了下面的描述:
我想用nodeJS用下面的yaml描繪的數(shù)據(jù)結(jié)構(gòu)得到一個新的數(shù)組:
base: steps: 10 batch_size: 1 poly: - template_prompt: template: > a cat, ${ chara } ${ facial_expressions } meta: chara: - Abyssinian, - cat_in_boots, facial_expressions: - (smile:1.5), - (smile:1.2), - smile,
要求:
假設(shè)上面的yaml轉(zhuǎn)成json的轉(zhuǎn)換代碼我已經(jīng)寫完了
我需要遍歷poly下的所有的頂層元素
遍歷過程中,要處理template_prompt元素的子元素:
從template中讀取作為模版。
讀取meta中的屬性,因?yàn)閷傩钥赡苊看味疾灰粯?,是不確定的,所以不能硬編碼。
然后基于meta中的屬性,把template作為 string literal 解析,這個解析代碼我已經(jīng)有了,假設(shè)名為render_string_template,可以不實(shí)現(xiàn),留一個函數(shù)接口即可。
要遍歷組合meta中的屬性形成一個數(shù)組,比如chara 有兩個值,facial_expressions 有三個值,那么應(yīng)該生成2*3也就是六組屬性放入這個數(shù)組中,這個數(shù)組和template會被傳入render_string_template函數(shù),最后會獲得兩個prompt字符串
將生成的prompt字符串?dāng)?shù)組和template_prompt元素之外的其他元素合并成一個對象,要求在同一級別。prompt字符串?dāng)?shù)組有幾個元素,就會合并成幾個對象,并放入一個新數(shù)組中,我們稱之為ploys。
繼續(xù)遍歷,直到遍歷完所有頂層元素,所有元素都放入了polys中。polys是一個一維數(shù)組。
將ploys中的每一個元素與base中的屬性合成一個新的對象,base的屬性展開與prompt屬性同級,當(dāng)ploys中的每一個元素的屬性名與base中的屬性名相同時,覆蓋base中的屬性。這些新對象組合出的數(shù)組就是我要的數(shù)組
可以看到,我在之前的prompt的后面加了很多的描述,其中的2-6就是用自然語言講了我期望這個函數(shù)實(shí)現(xiàn)整個過程的大概邏輯。這個手法就很像Open Pose。通過這個操作,我得到了我想要的代碼,畢竟這個邏輯不復(fù)雜。
邊界劃分
上面prompt的要求里除了表達(dá)了主干邏輯,其實(shí)也用了一些其他的技巧。比如“1. 假設(shè)上面的yaml轉(zhuǎn)成json的轉(zhuǎn)換代碼我已經(jīng)寫完了”,“把template作為 string literal 解析,這個解析代碼我已經(jīng)有了,假設(shè)名為render_string_template,可以不實(shí)現(xiàn),留一個函數(shù)接口即可。”
這個呢有點(diǎn)像control net里的seg技術(shù),他就是把圖片上的東西都分區(qū)域,就像下面這樣:
然后再生成的就靠譜很多,甚至你還可以對其中特定部分進(jìn)行針對性的描述。這個技術(shù)就很適合用來控制AI生成的注意力焦點(diǎn)。
那么在我們這個例子,上面提到的那些描述就起到了seg的作用。他可以讓我們不用在一個prompt描述里編寫所有的細(xì)節(jié),這對于復(fù)雜的邏輯很有幫助。因?yàn)榧幢闶且粋€小節(jié)點(diǎn),其實(shí)邏輯也可能有點(diǎn)復(fù)雜的,就像這里面,我們可以把meta數(shù)據(jù)與tempalte合并的功能延遲實(shí)現(xiàn),只描述與他的交互,使生成的程序先把參數(shù)扔給它。有的時候,我們想要使用特定的庫,也可以用這個方式,比如我在另外一個場景下是這么實(shí)現(xiàn)的這個效果:
1. 讀取文件的fs,要使用const fs = require('fs/promise')引入。
2. 用js-yaml庫解析yaml。
通過這樣的技巧,我們就可以把代碼進(jìn)行進(jìn)一步的分解,讓我們更容易描述清楚我們想要部分的業(yè)務(wù)邏輯,而且也可以給我們節(jié)省點(diǎn)算力,畢竟ChatGPT生成的時候如果太長也會中斷,你讓他繼續(xù)的話,有時候也續(xù)不上。這個技巧就可以讓他專注于你希望他專注的地方,從而提高表現(xiàn)力。
功能迭代
上面的寫完之后呢,我發(fā)現(xiàn)一個問題,這個DSL還不是我想要的最終形態(tài),對于同一套模板,我可能需要多種meta,因?yàn)橛行傩缘慕M合是沒有意義的,我也不想浪費(fèi)我寶貴的GPU。那么我們就不能太暴力的讓它窮舉所有的組合,我要針對同一個tempalte給他不同的屬性組合,所以meta的值就必須是個列表,大概如下所示:
base: steps: 10 batch_size: 1 poly: - template_prompt: template: > a cat, ${ chara } ${ facial_expressions } meta: - chara: # 這里改成了數(shù)組 - Abyssinian, - cat_in_boots, facial_expressions: - (smile:1.5), - (smile:1.2), - smile,
于是現(xiàn)在我們就面臨一個問題:改代碼。而這個時候就體現(xiàn)出我們之前拆任務(wù)的價值了,因?yàn)楦鱾€模塊都是隔離的,那就沒有什么改代碼,重新生成一份就好了,所以我改了改要求:
要求:
假設(shè)上面的yaml轉(zhuǎn)成json的轉(zhuǎn)換代碼我已經(jīng)寫完了
我需要遍歷poly下的所有的頂層元素
遍歷過程中,要處理template_prompt元素的子元素:
從template中讀取作為模版。
讀取meta中的屬性,因?yàn)閷傩钥赡苊看味疾灰粯?,是不確定的,所以不能硬編碼。
然后基于meta中的屬性,把template作為 string literal 解析,這個解析代碼我已經(jīng)有了,假設(shè)名為render_string_template,可以不實(shí)現(xiàn),留一個函數(shù)接口即可。
要遍歷組合meta中的每一個屬性組形成一個數(shù)組,
每一個屬性組可能只需要看做一個對象,當(dāng)且僅當(dāng)每一個屬性值都為單值
每一個屬性組可能也需要展開,當(dāng)且僅當(dāng)任何一個屬性值有多值,比如 chara 有兩個值,facial_expressions 有三個值,那么應(yīng)該生成2*3也就是六組屬性放入這個數(shù)組中,這個數(shù)組和template會被傳入render_string_template函數(shù),最后會獲得兩個prompt字符串
將生成的prompt字符串?dāng)?shù)組和template_prompt元素之外的其他元素合并成一個對象,要求在同一級別。prompt字符串?dāng)?shù)組有幾個元素,就會合并成幾個對象,并放入一個新數(shù)組中,我們稱之為ploys。
繼續(xù)遍歷,直到遍歷完所有頂層元素,所有元素都放入了polys中。polys是一個一維數(shù)組。
將ploys中的每一個元素與base中的屬性合成一個新的對象,base的屬性展開與prompt屬性同級,當(dāng)ploys中的每一個元素的屬性名與base中的屬性名相同時,覆蓋base中的屬性。這些新對象組合出的數(shù)組就是我要的數(shù)組
改完要求,配上上面的DSL直接扔給了它,得到了我們想要的代碼。
所以在今天這個時刻,有些重構(gòu)工作突然變得沒有那么大價值了,因?yàn)樵趧澏ê玫倪吔缋?,重寫比重?gòu)更快,盡管兩次輸出的代碼并不一樣,但是他們的功能是一樣的。
總結(jié)一下
我們首先概述了整個程序的最終運(yùn)行視圖,以及在實(shí)現(xiàn)過程中遇到的挑戰(zhàn)。然后我們以dsl_translator節(jié)點(diǎn)為例,講解了一些與ChatGPT結(jié)對編程的一些套路:
首先是編寫主干邏輯,通過描述我們期望函數(shù)實(shí)現(xiàn)的邏輯,類似于Open Pose中的骨架圖,以控制AI生成的內(nèi)容。我發(fā)現(xiàn)很多人讓ChatGPT工作的時候總想給一句話需求,軟件還是很復(fù)雜的,必要的話,我們還是要把主干邏輯寫出來。程序員有多討厭一句話需求,我想我不用多說了,到咱們自己提需求的時候,可不能忘本。
接著我們介紹了通過類似于Control Net中的seg技術(shù),我們將代碼進(jìn)行更細(xì)致的分解,這樣的技巧可以使AI專注于我們希望專注的地方,同時讓我們自己也更容易描述我們想要的業(yè)務(wù)邏輯,既節(jié)省算力,也能提升AI的表現(xiàn)。畢竟GPT-4還沒有廣泛使用,3000字還是個門檻。其實(shí)即便是廣泛使用了,我們不要忘了,AI這個東西生成總是不太穩(wěn)定的,如果你切得足夠小,成功率總是要高得多,而且你可以靠反復(fù)生成來達(dá)成某種自動化,這個呢,我們下一篇文章討論。
接著我們面對了一個需要改代碼以加入新功能的場景,因?yàn)槲覀冎耙呀?jīng)進(jìn)行了設(shè)計(jì),既然已經(jīng)拆分好了模塊,重新生成一份代碼來實(shí)現(xiàn)新的功能,比改代碼更快。第一次使用這個技巧的時候,我想起了流量地球里550C接入的時候,發(fā)出的提示音:正在生成底層操作系統(tǒng)。是的,對于AI來說,重寫比重構(gòu)更快。這個點(diǎn)極大地動搖了軟件工程中好多具體實(shí)踐的根基,從這一刻起,面面俱到的文檔勝過了可以工作的代碼。具體到行為上的變化就是,我現(xiàn)在寫代碼都會有意識的保存自己對AI提問的prompt,并且我會在每個節(jié)點(diǎn)的根路徑上記錄下完善的技術(shù)文檔。
到此為止,我們把與ChatGPT結(jié)對寫代碼中,怎么第一次把代碼寫出來這一步的主要技巧給講的差不多了。但其實(shí)這還遠(yuǎn)遠(yuǎn)不夠,正如沒有ChatGPT的時代我們就知道的,第一次把代碼寫出來所占軟件開發(fā)的成本少得可憐,軟件開發(fā)成本的絕大部分都是在后續(xù)迭代中花掉的。那么對于一個節(jié)點(diǎn)也是一樣的,盡管我們展示了一種重寫優(yōu)于重構(gòu)的新思路,但這一篇也僅僅講了個思路,AI生成的不穩(wěn)定性依然是懸在我們心頭上的一把刀,所以我們接下來會講講更多的工程實(shí)踐來確保我們持續(xù)寫出可信的代碼。
以上就是與ChatGPT結(jié)對編寫實(shí)現(xiàn)代碼詳解的詳細(xì)內(nèi)容,更多關(guān)于ChatGPT編寫代碼的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
ts?類型體操?Chainable?Options?可鏈?zhǔn)竭x項(xiàng)示例詳解
這篇文章主要為大家介紹了ts?類型體操?Chainable?Options?可鏈?zhǔn)竭x項(xiàng)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09PureScript與JavaScript中equality設(shè)計(jì)的使用對比分析
這篇文章主要為大家介紹了PureScript中的equality與JavaScript中的equality設(shè)計(jì)對比分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11TypeScript使用strictnullcheck實(shí)戰(zhàn)解析
這篇文章主要為大家介紹了TypeScript使用strictnullcheck實(shí)戰(zhàn)解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08TypeScript十大排序算法插入排序?qū)崿F(xiàn)示例詳解
這篇文章主要為大家介紹了TypeScript十大排序算法插入排序?qū)崿F(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02TypeScript使用noImplicitAny實(shí)戰(zhàn)解析
這篇文章主要為大家介紹了TypeScript使用noImplicitAny實(shí)戰(zhàn)解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08