若想知道怎麼在R裡面透過API向ChatGPT一次提問多個問題,並且將每個問題與各自的答案對應整理好,請看今天的練習。
ChatGPT正式推出已經滿2年了(2022年12月推出),這2年間許多公司也推出類似的服務,使生成式AI與生活更加緊密連結。
相信大家多多少少在工作、娛樂、創作或搜尋資訊等各方面使用過ChatGPT,或至少剛推出時有嚐鮮一下。如果最近還有持續在用,應該會發現比當初的版本有了許多進步,尤其是生成回覆的時間大概縮短了兩輩子這麼多。
雖然回覆時間已經很快了,但若有多個問題要問,並想把答案都整理好,一問一答的複製貼上流程是不是稍嫌冗長呢?例如想把作業的題目一口氣輸入……例如想直接整理下面這個表格:
| 國家 | 人口 | 首都 |
--------------------
| 美國 | | |
| 巴西 | | |
| 日本 | | |
| 南非 | | |
| 德國 | | |
有沒有辦法直接完成呢?
這次在Stack Overflow看到的提問是使用者想透過API,在R裡面直接向ChatGPT提問並且把答案整理進表格,提問原帖在此。
提問帖作者展示了「單獨提出問題」的話,可以正常運作。
但是如果想要把兩個問題同時丟給ChatGPT並儲存答案則會失敗。更簡單的說法,提問者想要自動完成下面這個命名為test的表格:
> test
x y
1 Is Arkansas in the United States?
2 Is Arkansas in India?
其中x是整理在一起的2個問題,而y則想要放置答案。
這個問題其實是資料結構的問題,只是套了一個「向ChatGPT提問」的主題。本質上來說,可以分解成3個部分:
但要給出一個「能完全重現」的解答,我們還是要按部就班進行。因此第一步先看看怎麼使用套件與API(直接擷取提問者的發文)。
### load current ChatGPT package: https://github.com/jcrodriguez1989/chatgpt
install.packages("chatgpt")
library(chatgpt)
### load GPT API
Sys.setenv(OPENAI_API_KEY = "XXX")
首先是安裝與讀取套件,這部分沒有問題,直接複製並執行程式碼即可。第二步則是要取得API key,讓R可以跟ChatGPT「對話」。(關於API的介紹可以看這篇文章)
要取得API,要前往OpenAI的API網站,並登入平常使用ChatGPT的帳號。點選「Create new secret key」後再輸入一些名稱等基本設定,就可以成功建立新的API了,建立完成會看到下面的視窗。
注意這邊是唯一一次能看到完整API key的機會,關閉之前務必先複製下來,若不慎遺失了的話就要再建立新的來用了。這邊拿到的API key就是用來取代前面提問者的程式碼Sys.setenv(OPENAI_API_KEY = "XXX")
這一段裡面的XXX。
接下來就可以正式開始用ask_chatgpt()
提問了!
……很遺憾,直接使用的話會得到:You exceeded your current quota, please check your plan and billing details.
這個錯誤訊息……是的,使用API要以流量計費,因此必須先在API網站內的Billing頁面,點選「Add payment details」新增付費方式(信用卡資訊)。API的費用相關資訊可以參考這篇文章,計價方式非常划算,加值最少的5美元都可以用很久。
設定完成,可以透過R向ChatGPT提問後,接著正式處理今天的提問(終於!)。首先我們看看原提問者是哪裡出錯:
### write into dataframe
test <- data.frame(x = c("Is Arkansas in the United States?", "Is Arkansas in India?"))
### ask_chatgpt
test <- test %>%
mutate(y = cat(ask_chatgpt(x)))
首先是不應該使用cat()
這個函式,它的用途是直接把要的文字貼到terminal上,而貼出來的東西只能給人類的眼睛讀,對程式來說不是一個物件。比較以下兩種結果:
> class(cat(ask_chatgpt("Is Arkansas in the United States?")))
*** ChatGPT input:
Is Arkansas in the United States?
Yes, Arkansas is a state located in the United States.[1] "NULL"
> class(ask_chatgpt("Is Arkansas in the United States?"))
*** ChatGPT input:
Is Arkansas in the United States?
[1] "character"
用class()
要求R提供資料的類型,這邊的意思是要求R告訴我們ChatGPT對這一題的答案,也就是:"Yes, Arkansas is a state located in the United States."這一句話是什麼資料型態,cat()
得到的結果是"NULL"
,連物件都不是無法判斷。注意[1]後面的文字才是程式的回答,因為cat()
會強迫把文字貼到terminal上,導致ChatGPT的回答先貼完後,程式才作答。另一方面,不使用cat()
的話就會回答"character"
,也就是字串。
提問者會使用cat()
應該是因為套件作者的範例有用這個函式,如果只是單獨提問的話這可以得到最簡潔的結果,回傳的答案連""都不會有,但若要將答案進一步處理的話就不能這樣使用。
單純拿掉cat()
?結果仍然不對。
test <- test %>%
mutate(y = ask_chatgpt(x))
Error in `mutate()`:
ℹ In argument: `y = ask_chatgpt(x)`.
Caused by error in `gpt_get_completions()`:
! list(message = "Invalid type for 'messages[9].content[0]': expected an object, but got a string instead.", type = "invalid_request_error", param = "messages[9].content[0]", code = "invalid_type")
Run `rlang::last_trace()` to see where the error occurred.
這邊的原因則是ask_chatgpt()
預設你要放一個且只有一個問題進去,但是mutate()
相當於把整個欄位(test的x)都放進去。
我們必須「一個一個地」提問才行,那是不是應該用迴圈呢?用迴圈可以達成目的,但這邊只有兩個問題還好,當項目一多時迴圈會跑到天~長~地~久。若需要執行的步驟可以用apply
家族處理,使用apply
家族才是比較好的作法。
test <- data.frame(
x = c("Is Arkansas in the United States?", "Is Arkansas in India?")
)
test$y <- sapply(test$x, ask_chatgpt)
這段先建立只有一個欄位(x)的data.frame,接著用sapply()
把ask_chatgpt()
這個函式套用到test$x
裡面的每一個物件,其結果會是一個跟test$x
一樣長的vector,再把這個vector指派成data.frame的另一個欄位(y)。
惡作劇完成!
非常簡單的程式碼,但要說明每一個步驟卻變成這麼長的文章。而且文章的內容已經預設大家對R、資料結構、資料類型、apply
家族都有基本認識,畢竟這邊若要展開又是另一篇長文了……如果對這些略過的部分有疑問,以後再另外做個分析講解吧!