談?wù)剬?duì)話式軟件開(kāi)發(fā)
利用大語(yǔ)言模型生成代碼是改變生活的方式之一,但與它們就軟件開(kāi)發(fā)過(guò)程進(jìn)行交談也同樣重要。Jon Udell繼續(xù)探索大語(yǔ)言模型為程序員帶來(lái)的價(jià)值。
譯自 Let’s Talk: Conversational Software Development 。
以下是啟動(dòng)了一次非常成功的人工智能ChatGPT交互的提示。
PROMPT:
I’ll give you a test script and the output it is testing, and ask about strategies to improve the test, OK? Here’s the script.
#!/bin/bash
set -eu
list_tickets() {
local output
output=$($command $subcommand run list_tickets 2>&1) || {
echo "Command failed with error: $output"
exit 1
}
echo "$output"
}
output=$(list_tickets)
count=$(echo "$output" | jq -r '.tickets_list | fromjson | length')
# Check if count is greater than 0
if [[ $count -gt 0 ]]; then
echo "PASS: list_tickets count is greater than 0."
else
echo "FAIL: list_tickets count is 0 or less."
fi
一次過(guò)路人的見(jiàn)解
該提示是一個(gè)有意設(shè)置的樸實(shí)版本的bash腳本,我設(shè)想的腳本。我本來(lái)也打算在最初的提示中提供樣本輸出,但忘記這么做了?;叵肫饋?lái),不提供輸出而先讓ChatGPT專注于腳本本身可能是一個(gè)好主意。它的一些觀察包括:
RESPONSE:
While you’ve used set -eu to exit on any error or unset variable usage, consider using set -euo pipefail to also capture failures in any part of a pipeline.
這是一個(gè)很好的關(guān)于在編碼時(shí)如何從LLM中隱性地獲取知識(shí)的例子。我之前對(duì)bash中的嚴(yán)格模式有一些了解,但不完整。因此我們暫停了一會(huì)兒來(lái)探索這個(gè)話題,我加深了知識(shí),并記下考慮使用其他-o參數(shù) —— 尤其是 -x(xtrace) 和 -n(noexec) 用于調(diào)試。我們剛剛開(kāi)始處理手頭的實(shí)際任務(wù),但這個(gè)小小的繞道本身就很有價(jià)值。
讓我們來(lái)談?wù)凧SON
我現(xiàn)在回頭提供了樣本輸出:
PROMPT:
“tickets_list”: “[{\”associates_rdb\”:null,\”association_type\”:null,\”cc_email\”:{\”cc_emails\”:[],\”fwd_emails\”:[],\”reply_cc\”:[],\”tkt_cc\”:[]},\”created_at\”:\”2023-10-12T12:15:00+01:00\”,\”custom_field\”:{\”cf_reference_number_2939992\”:null},\”datetime_tc01\”:null,\”datetime_tc02\”:null,\”datetime_tc03\”:null,\”deleted\”:false,\”delta\”:true,\”description\”:\”world\”,\”description_html\”:\”\\u003cdiv\\u003eworld\\u003c/div\\u003e\”,\”dirty\”:0,\”display_id\”:17,\”due_by\”:\”2023-10-16T22:00:00+01:00\”,\”email_config_id\”:153000020300,\”frDueBy\”:\”2023-10-12T22:00:00+01:00\”,\”fr_escalated\”:true,\”group_id\”:null,\”id\”:153002218563,\”int_tc01\”:null,\”int_tc02\”:null,\”int_tc03\”:null,\”int_tc04\”:null,\”int_tc05\”:null,\”internal_agent_id\”:null,\”internal_group_id\”:null,\”isescalated\”:false,\”json_tc01\”:null,\”long_tc01\”:null,\”long_tc02\”:null,\”long_tc03\”:null,\”long_tc04\”:null,\”long_tc05\”:null,\”nr_due_by\”:null,\”nr_escalated\”:false,\”nr_reminded\”:false,\”owner_id\”:null,\”parent_ticket_id\”:null,\”priority\”:1,\”priority_name\”:\”Low\”,\”product_id\”:153000018092,\”requester_id\”:153002362554,\”requester_name\”:\”Jon Udell\”,\”requester_status_name\”:\”O(jiān)pen\”,\”responder_id\”:null,\”responder_name\”:\”No Agent\”,\”sl_escalation_level\”:null,\”sl_manual_dueby\”:null,\”sl_merge_parent_ticket\”:null,\”sl_product_id\”:null,\”sl_skill_id\”:null,\”sl_sla_policy_id\”:null,\”sla_state\”:0,\”source\”:2,\”source_name\”:\”P(pán)ortal\”,\”spam\”:false,\”st_survey_rating\”:null,\”status\”:2,\”status_name\”:\”O(jiān)pen\”,\”subject\”:\”hello\”,\”ticket_type\”:null,\”to_email\”:null,\”to_emails\”:null,\”trained\”:false,\”updated_at\”:\”2023-10-12T22:05:04+01:00\”,\”urgent\”:false},{\”associates_rdb\”:null,\”association_type\”:null,\”cc_email\”:{\”bcc_emails\”:[],\”cc_emails\”:[],\”fwd_emails\”:[],\”reply_cc\”:[],\”tkt_cc\”:[]},\”created_at\”:\”2023-10-12T04:41:12+01:00\”,\”custom_field\”:{\”cf_reference_number_2939992\”:null},\”datetime_tc01\”:null,\”datetime_tc02\”:null,\”datetime_tc03\”:null,\”deleted\”:false,\”delta\”:true,\”description\”:\”Hello there, Our Report metrics over the last week is at zero and can’t be correct? Are you facing any issues?\”,\”description_html\”:\”Hello there, Our Report metrics over the last week is at zero and can’t be correct? Are you facing any issues?\”,\”dirty\”:0,\”display_id\”:6,\”due_by\”:\”2023-10-16T22:00:00+01:00\”,\”email_config_id\”:153000020300,\”frDueBy\”:\”2023-10-12T22:00:00+01:00\”,\”fr_escalated\”:false,\”group_id\”:153000077019,\”id\”:153002214584,\”int_tc01\”:null,\”int_tc02\”:null,\”int_tc03\”:null,\”int_tc04\”:null,\”int_tc05\”:null,\”internal_agent_id\”:null,\”internal_group_id\”:null,\”isescalated\”:false,\”json_tc01\”:null,\”long_tc01\”:null,\”long_tc02\”:null,\”long_tc03\”:null,\”long_tc04\”:null,\”long_tc05\”:null,\”nr_due_by\”:null,\”nr_escalated\”:false,\”nr_reminded\”:false,\”owner_id\”:null,\”parent_ticket_id\”:null,\”priority\”:1,\”priority_name\”:\”Low\”,\”product_id\”:153000018092,\”requester_id\”:153002465401,\”requester_name\”:\”Adam Doe\”,\”requester_status_name\”:\”O(jiān)pen\”,\”responder_id\”:null,\”responder_name\”:\”No Agent\”,\”sl_escalation_level\”:null,\”sl_manual_dueby\”:null,\”sl_merge_parent_ticket\”:null,\”sl_product_id\”:null,\”sl_skill_id\”:null,\”sl_sla_policy_id\”:null,\”sla_state\”:0,\”source\”:1,\”source_name\”:\”Email\”,\”spam\”:false,\”st_survey_rating\”:null,\”status\”:2,\”status_name\”:\”O(jiān)pen\”,\”subject\”:\”Issues with reports\”,\”ticket_type\”:\”Bug\”,\”to_email\”:null,\”to_emails\”:null,\”trained\”:false,\”updated_at\”:\”2023-10-12T04:41:13+01:00\”,\”urgent\”:false}]”
}
在另一個(gè)情景下,我可能會(huì)花時(shí)間優(yōu)化格式化這個(gè) JSON 輸出。但是 LLM —— 像編譯器和解釋器 —— 不關(guān)心這些。更棒的是,它們可以容忍會(huì)使傳統(tǒng) JSON 格式化程序死機(jī)的語(yǔ)法。如果你把上面的例子粘貼到傳統(tǒng)格式器,會(huì)遇到兩個(gè)問(wèn)題。首先,如果不用花括號(hào)包裹就不是有效的 JSON。其次, tickets_list 的值不會(huì)是 JSON 對(duì)象,而是字符串,因?yàn)樵撝凳?JSON 的字符串化表示。非常神奇的是,LLM “知道” 你的意圖并做出正確反應(yīng):
RESPONSE:
{
"tickets_list": [
{
"associates_rdb": null,
"association_type": null,
"cc_email": {
"cc_emails": [],
"fwd_emails": [],
"reply_cc": [],
"tkt_cc": []
},
"created_at": "2023-10-12T12:15:00+01:00",
"custom_field": {
"cf_reference_number_2939992": null
},
...
}
]
}
這是非常瑣碎的東西,但它是可能影響你的工作流程并浪費(fèi)最好留給高級(jí)功能的認(rèn)知資源的東西。在這一點(diǎn)上,我還進(jìn)行了我稱之為可解釋性測(cè)試: LLM是否解釋了1) 代碼片段需要被花括號(hào)包裹,以及2) 嵌套的值需要被解析?以下是結(jié)果。
工具 | llm 提供者 | 解釋需要使用{} | 解釋tickets_list需要解析 |
chatgpt | openai | 否 | 是 |
copilot chat | 開(kāi)放ai | 否 | 是 |
claude | anthropic | 否 | 否 |
cody | anthropic | 否 | 否 |
這引起我的興趣,因?yàn)殡m然我知道 Copilot Chat 使用 OpenAI ,Cody 使用 Anthropic,但這不總是明顯的。各自的聊天界面增強(qiáng)了提示,使回復(fù)不同于從底層 LLM 獲得的回復(fù)。但在這個(gè)例子中,工具似乎與它們各自的界面行為一致。
讓我們來(lái)討論測(cè)試策略
ChatGPT 對(duì) set -euo pipefail 的觀察只是我們可以探索的多個(gè)方向之一,但我把話題引回到了手頭的任務(wù)上: 完善我在最初提示中包含的測(cè)試腳本。該腳本測(cè)試的是 Freshdesk 票務(wù)系統(tǒng)的輸出結(jié)果,而天真的測(cè)試只是檢查我們正在測(cè)試的工具是否可以調(diào)用 Freshdesk 的 API 并返回多于 0 張票。更好的測(cè)試會(huì)是什么樣的呢?ChatGPT 的建議包括檢查優(yōu)先級(jí)和狀態(tài)是否與預(yù)期值匹配、空值或長(zhǎng)列表的票務(wù)是否被優(yōu)雅處理,以及所有日期字段是否存在并包含有效日期。
我選擇了最后一種方法,ChatGPT 相應(yīng)地修改了測(cè)試腳本。這需要編寫(xiě)一個(gè)正則表達(dá)式來(lái)匹配日期,并在每張票的一組日期字段中循環(huán)應(yīng)用該正則表達(dá)式。這感覺(jué)太復(fù)雜了,所以我要求將范圍限制為僅檢查一個(gè)日期字段: created_at。即使這樣仍然感覺(jué)太復(fù)雜,所以我們舍棄了循環(huán),將測(cè)試簡(jiǎn)化為僅檢查單張票中的有效日期。之后我們轉(zhuǎn)向了下一個(gè)話題。
讓我們來(lái)討論如何將測(cè)試推廣到一個(gè)測(cè)試套件
需要測(cè)試的用例有:
- 列出 Freshdesk 票據(jù)
- 創(chuàng)建新 Freshdesk 票據(jù)
- 刪除 Freshdesk 票據(jù)
- 檢索特定 Freshdesk 票據(jù)的詳細(xì)信息
- 更新 Freshdesk 票據(jù)
- 向 Freshdesk 票據(jù)添加注釋
我很少使用 bash 函數(shù),但我覺(jué)得在這里它們可能會(huì)很有用,所以當(dāng)看到一個(gè)將 bash 腳本結(jié)構(gòu)化為與六個(gè)測(cè)試用例對(duì)應(yīng)六個(gè)函數(shù)的回復(fù)時(shí),我很高興?!翱雌饋?lái)不錯(cuò),”我說(shuō),“請(qǐng)給我一個(gè)完整的構(gòu)建如此結(jié)構(gòu)化的版本,并包含第一個(gè)測(cè)試?!?/p>
此時(shí),一個(gè)在我身邊觀看的同事想知道是否對(duì) LLMs 使用“請(qǐng)”和“謝謝”會(huì)帶來(lái)更好的結(jié)果。這打敗了我!我只是因?yàn)楦杏X(jué)很好才這樣做。這是一種危險(xiǎn)的人格化嗎?也許吧。但即使它們是非人類,我也看不出欺負(fù)我的助手有任何益處。
然后同一位同事問(wèn)及另一種從表格驅(qū)動(dòng)測(cè)試的替代策略。我們問(wèn):
PROMPT:
We are wondering if it would be better to have a data structure for these tests instead of a set of loose functions, what do you think are the pros and cons?
回應(yīng)提到了優(yōu)點(diǎn)(可維護(hù)性、重用性、并行執(zhí)行、一致的日志記錄、清晰度)和缺點(diǎn)(額外開(kāi)銷(xiāo)、復(fù)雜度、遷移時(shí)間、學(xué)習(xí)曲線)。它以這段總結(jié)結(jié)束:
RESPONSE:
Given that your test suite might grow or change over time, adopting a structured approach earlier can be beneficial. However, it’s essential to weigh the initial time investment against the long-term benefits. If you think your test suite will remain relatively small and static, the current loose function setup might suffice. But if you anticipate growth or frequent changes, the structured approach will pay off in the long run.
由于我們處于快速原型設(shè)計(jì)階段,還沒(méi)有準(zhǔn)備好對(duì)此測(cè)試策略進(jìn)行長(zhǎng)期投資,所以我們選擇保持松散函數(shù)的方式。但我們都覺(jué)得花幾分鐘考慮替代方法是非常有價(jià)值的。
讓我們來(lái)討論持久化票據(jù) ID 的方法
要編寫(xiě)的下一個(gè)測(cè)試是創(chuàng)建票據(jù)的測(cè)試。ChatGPT 建議存儲(chǔ)票據(jù)的 ID 以在其他測(cè)試中使用,以測(cè)試讀取、更新和向票據(jù)添加注釋的功能。并且它編寫(xiě)了一個(gè)將 ID 持久化到文件的函數(shù)。這讓我感到疑惑,并要求評(píng)估在文件和變量之間保存的權(quán)衡取舍。這里是對(duì)回復(fù)的摘要,以表格形式進(jìn)行了修訂(感謝 ChatGPT)。
保存于文件 | 保存于變量 | ||
優(yōu)點(diǎn) | 缺點(diǎn) | 優(yōu)點(diǎn) | 缺點(diǎn) |
持久性 | 速度較慢 | 速度快 | 揮發(fā)性 |
進(jìn)程間通信 | 復(fù)雜度高 | 簡(jiǎn)單 | 限于單個(gè)腳本/進(jìn)程 |
適合大數(shù)據(jù) | 需清理 | 無(wú)文件依賴 | 內(nèi)存限制 |
并補(bǔ)充了這個(gè)結(jié)論:
RESPONSE:
If you’re writing a single script, and the data doesn’t need to persist beyond the script’s execution, a variable might be simpler and faster.
我甚至沒(méi)有考慮基于文件的替代方法。當(dāng) ChatGPT 使用那種方法時(shí),它促使我考慮并討論不同方案的權(quán)衡取舍。由于我最終做了與使用 LLM 之前完全一樣的事,這次繞路就是無(wú)謂的嗎?我不這么認(rèn)為??紤]替代方案總是有價(jià)值的。這次繞路花費(fèi)的時(shí)間很少,且雖然它沒(méi)有改變我最終的做法,但過(guò)程感覺(jué)很有價(jià)值。
在為 get_ticket 函數(shù)編寫(xiě)測(cè)試時(shí),我們也有過(guò)類似的交互。檢查獲取的票據(jù) ID 是否與我們存儲(chǔ)的 ID 匹配就足夠了嗎?ChatGPT 在效率和徹底性之間劃清了界限,列出了進(jìn)行更徹底檢查的選項(xiàng),并建議對(duì)于基本的冒煙測(cè)試,優(yōu)先考慮效率是有意義的。同樣,我已經(jīng)會(huì)做更簡(jiǎn)單的事了,所以這并沒(méi)有改變?nèi)魏螙|西。但盡管我知道我們當(dāng)然是在進(jìn)行冒煙測(cè)試,大聲說(shuō)出來(lái)還是顯得很有幫助。
讓我們來(lái)討論 Bash
這里有一個(gè)交互極大地改變了結(jié)果。我們向測(cè)試套件添加了第三個(gè)測(cè)試,但只運(yùn)行了前兩個(gè)。出了什么問(wèn)題?調(diào)試過(guò)程與使用 LLM 之前一樣,但 ChatGPT 可以更快地在代碼中插入打印語(yǔ)句,因此速度快了很多。在進(jìn)行幾次這種暴力試錯(cuò)法后,我們?nèi)匀粺o(wú)法通過(guò)第二個(gè)測(cè)試。
然后我想到問(wèn): "關(guān)閉 set -euo pipefail 會(huì)有意義嗎?" 當(dāng)我這樣做時(shí),腳本就完整運(yùn)行了 - 即所有 6 個(gè)測(cè)試都運(yùn)行了 - 但第二個(gè)測(cè)試在本應(yīng)成功的情況下失敗了。隨后我恍然大悟。我問(wèn): "我們應(yīng)該從 test_create_ticket 返回 $TICKET_ID 嗎?" ChatGPT 提醒我一個(gè)我讀過(guò)但從未遇到過(guò)的行為:
RESPONSE:
In the context of bash scripts, when you return a number from a function, it’s analogous to an exit code.
我的天?。?duì)了!我們需要該函數(shù)設(shè)置全局變量為所創(chuàng)建票據(jù)的值,但返回零以避免過(guò)早退出,同時(shí)保持嚴(yán)格模式仍然生效。這是最后的突破;在那之后,一切順利。
當(dāng)橡皮鴨說(shuō)話時(shí)
我不斷回到這一系列文章的第一個(gè)主題: 當(dāng)橡皮鴨說(shuō)話時(shí)。大聲思考總是有幫助的。理想情況下,你可以與一個(gè)人類伙伴一起這樣做。橡皮鴨雖然是個(gè)糟糕的替代品,但遠(yuǎn)勝于一無(wú)所有。
與 LLM 對(duì)話不像這些選擇中的任何一個(gè),這完全是另一回事;我們都在努力弄明白它如何發(fā)揮作用。要求 LLM 編寫(xiě)代碼,神奇地出現(xiàn)代碼?這明顯是一個(gè)改變生活的事情。與 LLM 就你們合作編寫(xiě)的代碼進(jìn)行交談?我認(rèn)為這同樣是一個(gè)不太明顯但同樣深刻的改變生活的事情。