R資料處理-用R丟多個問題給ChatGPT並儲存答案 [Stack Overflow練題002]

R資料處理-用R丟多個問題給ChatGPT並儲存答案 [Stack Overflow練題002]

更新於 發佈於 閱讀時間約 10 分鐘
若想知道怎麼在R裡面透過API向ChatGPT一次提問多個問題,並且將每個問題與各自的答案對應整理好,請看今天的練習。

前言

ChatGPT正式推出已經滿2年了(2022年12月推出),這2年間許多公司也推出類似的服務,使生成式AI與生活更加緊密連結。

相信大家多多少少在工作、娛樂、創作或搜尋資訊等各方面使用過ChatGPT,或至少剛推出時有嚐鮮一下。如果最近還有持續在用,應該會發現比當初的版本有了許多進步,尤其是生成回覆的時間大概縮短了兩輩子這麼多。

雖然回覆時間已經很快了,但若有多個問題要問,並想把答案都整理好,一問一答的複製貼上流程是不是稍嫌冗長呢?例如想把作業的題目一口氣輸入……例如想直接整理下面這個表格:

| 國家 | 人口 | 首都 |
--------------------
| 美國 | | |
| 巴西 | | |
| 日本 | | |
| 南非 | | |
| 德國 | | |

有沒有辦法直接完成呢?


題目

這次在Stack Overflow看到的提問是使用者想透過API,在R裡面直接向ChatGPT提問並且把答案整理進表格,提問原帖在此。

提問帖作者展示了「單獨提出問題」的話,可以正常運作。

raw-image

但是如果想要把兩個問題同時丟給ChatGPT並儲存答案則會失敗。更簡單的說法,提問者想要自動完成下面這個命名為test的表格:

> test
x y
1 Is Arkansas in the United States?
2 Is Arkansas in India?

其中x是整理在一起的2個問題,而y則想要放置答案。


解答

解答-1. 透過套件與API向ChatGPT提問

這個問題其實是資料結構的問題,只是套了一個「向ChatGPT提問」的主題。本質上來說,可以分解成3個部分:

  1. 建立data.frame
  2. 產生一個vector
  3. 把vector指派為data.frame的一個新欄位


但要給出一個「能完全重現」的解答,我們還是要按部就班進行。因此第一步先看看怎麼使用套件與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了,建立完成會看到下面的視窗。

raw-image

注意這邊是唯一一次能看到完整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美元都可以用很久。


解答-2. 批次提問並將解答儲存進表格

設定完成,可以透過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家族都有基本認識,畢竟這邊若要展開又是另一篇長文了……如果對這些略過的部分有疑問,以後再另外做個分析講解吧!

avatar-img
窄視野的生活觀察
0會員
3內容數
留言
avatar-img
留言分享你的想法!