Web APIを通じてRからウェブサービスを利用する方法と、その例としてOpenAI APIを利用してChatGPTをRから利用する方法を述べる。

1 使用するパッケージ

今回使用するパッケージは以下のものである。

pacman::p_load(
  jsonlite,
  httr,
  tidyverse
)

2 WebとAPIの基礎

APIとはapplication programming interfaceの略で、アプリケーション同士が互いに情報を送信しあうためのインターフェース(入り口)である。 Webを通じて提供されるものをWeb APIという(Webを省略して単にAPIと呼ばれることも多い)。

2.1 そもそもWebとは?

Webの仕組みについて軽く述べておく。

2.1.1 Webページ

ウェブサイト(例えばGoogleのトップページ www.google.com )にアクセスする際は、ユーザーがブラウザ(Google Chromeなど)を通じてサーバーにWebページの情報を求めるリクエストを送り、サーバーが情報を返すという処理が行われる。 ブラウザのことをWebクライアントと呼ぶこともある。Webはこうしたクライアントとサーバーのやりとりで実現されている。


図:Webの仕組みの大まかなイメージ(出所:筆者作成)

なお、サーバーが返してくる情報についてもう少し具体的に述べると、Webページはhtmlという形式のテキストファイルを中心として構成されているため、サーバーはhtmlなどを送信する。 htmlは、Google Chromeで閲覧中のページを右クリックして「ページのソースを表示」のメニューをクリックしたときに出てくる

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
...(以下略)...

などと書かれているファイルである。

2.2 Web API

Webページの場合、基本的に人間が見ることを想定して開発されるものであるため、サーバーはhtmlなどを返す。 しかしWeb APIのユーザーは人ではなくアプリケーションであるため、基本的に機械にとって扱いやすいようなインターフェース(送受信するデータの形式)とする。


図:WebAPIの仕組みの大まかなイメージ(出所:筆者作成)

よく使われる形式のひとつはJSON形式である。JSONは次のような形式になっている。

{
  "status": "OK",
  "values": [100, 200, 300]
}

JSONはキーと対応するバリュー(値)の集合になっており、多くのプログラミング言語に用意されているキー・バリュー・ストア(Rだと名前付きのリスト list(key = "value")のこと)に変換して扱うことができる。例えば{jsonlite}パッケージを使うことでJSONをlistに変えることができる。

json_text <- '{
  "status": "OK",
  "values": [100, 200, 300]
}'

data <- jsonlite::fromJSON(json_text)
data["values"]
## $values
## [1] 100 200 300

2.3 HTTP

WebサーバーとはHTTPという通信方法を用いて通信する。

2.3.1 リクエストメソッド

リクエストを送る際はメソッドというものが9種類存在し、状況に応じて使い分けられている。 例えば情報を閲覧したい際はGETメソッドを使う事が多く、テキストなど比較的大きめの情報を送信したい場合はPOSTメソッドを使うことが多い。

Rの場合、{httr}パッケージを使うことでHTTP通信を行うことができる。

response <- httr::GET(url = "https://www.google.com")

2.3.2 レスポンス

HTTPリクエストに対するサーバーからのレスポンスにはステータスコード(status code)とボディ(body)が含まれる。

response$status_code  # ステータスコード
## [1] 200

ステータスコードは3桁の数字で、例えば次のようなものがある。

ステータスコード 名前 意味
200 OK 正常に通信できた
404 Not found ページが見つからない
500 Internal Server Error サーバー側で想定外のエラーが発生した

ボディはレスポンス内容の本文に相当するものであり、Webページであればhtml等が、Web APIであればJSON等が送られる。

content(response) # response body
## {html_document}
## <html itemscope="" itemtype="http://schema.org/WebPage" lang="ja">
## [1] <head>\n<meta content="世界中のあらゆる情報を検索するためのツールを提供しています。さまざまな検索機能を活用して、お探しの情 ...
## [2] <body bgcolor="#fff">\n<script nonce="jHuCV0hHYt3HF50DV2ftPQ">(function() ...

上記はGoogleのトップページ部分のhtml(をR側で見やすく表示したもの)である。

3 例1: 不動産取引価格情報取得API

利用者登録が不要で手軽に利用できるWeb APIの例として、国土交通省の土地総合情報システムAPIを使って試していく。

土地総合情報システムはアンケート調査によって得た不動産価格を公開しているWebサイトで、このAPIを利用することで不動産価格のデータをRから直接取得することができる。

APIの操作説明を見ると2つAPIが存在するが、今回は「不動産取引価格情報取得API」を利用する。

操作説明にはURLとパラメータをどう指定すればよいのかが書かれている。 パラメータとはURLの?以降にkey=value形式書かれるもので、httr::GET()ではqueryという引数にlist()形式で渡していく。

res <- GET(
  # APIのURL
  url = "https://www.land.mlit.go.jp/webland/api/TradeListSearch",
  # クエリパラメータ
  query = list(
    from = 20224, # 何年の第何四半期以降の取引を取得するか
    to = 20231,   # 何年の第何四半期までの取引を取得するか
    area = 13     # 都道府県コード(東京は13)
  )
)

まずステータスコードを確認する。200であればリクエストに成功している。

res$status_code
## [1] 200

レスポンスからbodyを取り出す。

body <- content(res)

結果はbody$dataに含まれているため、取り出してデータフレームに変換する

rows <- data.frame()
for (row in body$data) {
  rows <- bind_rows(rows, as.data.frame(row))
}

このようなデータが取得できた

# 最終的に得られたデータ
rows %>% tibble()
## # A tibble: 10,563 × 27
##    Type         MunicipalityCode Prefecture Municipality DistrictName TradePrice
##    <chr>        <chr>            <chr>      <chr>        <chr>        <chr>     
##  1 中古マンシ…  13101            東京都     千代田区     飯田橋       13000000  
##  2 中古マンシ…  13101            東京都     千代田区     飯田橋       44000000  
##  3 中古マンシ…  13101            東京都     千代田区     飯田橋       70000000  
##  4 宅地(土地と… 13101            東京都     千代田区     飯田橋       130000000 
##  5 中古マンシ…  13101            東京都     千代田区     飯田橋       150000000 
##  6 中古マンシ…  13101            東京都     千代田区     一番町       190000000 
##  7 中古マンシ…  13101            東京都     千代田区     一番町       350000000 
##  8 宅地(土地と… 13101            東京都     千代田区     一番町       430000000 
##  9 中古マンシ…  13101            東京都     千代田区     一番町       52000000  
## 10 中古マンシ…  13101            東京都     千代田区     一番町       47000000  
## # ℹ 10,553 more rows
## # ℹ 21 more variables: FloorPlan <chr>, Area <chr>, BuildingYear <chr>,
## #   Structure <chr>, Use <chr>, Purpose <chr>, CityPlanning <chr>,
## #   CoverageRatio <chr>, FloorAreaRatio <chr>, Period <chr>, Renovation <chr>,
## #   Region <chr>, LandShape <chr>, Frontage <chr>, TotalFloorArea <chr>,
## #   Direction <chr>, Classification <chr>, Breadth <chr>, PricePerUnit <chr>,
## #   UnitPrice <chr>, Remarks <chr>

3.1 (参考)公的統計のAPI

e-StatRESASといった公的統計のサイトはAPIを公開している。 これらも{httr}パッケージでアクセスすることが可能である。

例えば、矢内(2016)はe-Stat APIにhttrパッケージでアクセスする方法を解説している。

しかし、APIによってはhttpリクエストを送ってbodyを整形する処理があらかじめ関数にまとめられパッケージとして提供されていることもある。 例えばe-Stat APIであれば{estatapi}パッケージが存在し、R側から関数を呼び出す形でAPIにアクセスし、tibbleのデータフレームで結果を返してくれるようになっている。こうしたパッケージを使うことでAPIをより手軽に扱うことができる。 (ただし、こうしたパッケージは公式のものもあれば第三者が公開しているものもあるため、有償APIを使う際は注意する)

e-statやRESASの他にも行政のAPIは多数存在し、e-Gov APIカタログなどで検索することができる。

4 (参考)例2:OpenAI API

ChatGPTはWebサイトだけでなくWeb APIも提供されているため、ChatGPTを例にとっていく。 利用登録が必要でハードルがやや高いため、参考として載せておく。

OpenAI APIは本来は有料のサービスであるが、アカウントを作成してから3ヶ月間は18ドル分の無料枠があり、気軽に使うことができる。 (なお、有料と言ってもGPT-3.5は1000トークンあたり0.002ドル程度とかなり安い)

4.1 利用登録

OpenAIにログインして右上の自分のプロフィール部分をクリックして開くメニューの「Veiw API Keys」をクリックする。 すると自分のAPIキー(APIで本人認証するための鍵となる情報)の管理画面が表示される。 まだキーを作ったことがなければ何も存在しない。


図:https://platform.openai.com/account/api-keys の画面

下部の「+Create new secret key」ボタンをクリックして新規作成する。 名前の入力欄が出てくるが任意の設定項目なので無視して先に進んでも問題ない。

作成するとキー(半角英数字の羅列)が出てくるため、コピーしておく。 (APIキーが万一流出してしまうと第三者に好き勝手にAPIを使われてしまうため、扱いには注意する。もしクレジットカード情報をOpenAI に登録して有料ライセンスを保有している場合は特に慎重に扱うこと)

4.2 Rで実践

環境変数の設定

まずAPIキーの扱いについて述べる。APIキーのような機密情報はソースコード中に直接書き込まないことが望ましいため、環境変数(environment variable)に設定してRから読み込むことにする。環境変数はOSなどプログラムを動かす環境に設定した変数であり、環境変数を利用することでプログラム側(R側)ではあらかじめ定義済みの変数を呼び出すだけでよいことになる。

環境変数を定義する方法は複数あり、例えばSys.setenv(変数名="value")のように書いてその場で実行したり、ホームディレクトリに.Renvironファイルを作ってそちらに設定したりする。 今回は簡易的にSys.setenv()を使う方法を紹介する。Rのスクリプトではなく、RStudioの左下の「Console」タブに、次のように打ち込んで実行する。

Sys.setenv("OPENAI_API_KEY"="(取得したAPIキー)")

なお環境変数を読み込むときはSys.getenv("変数名")と書けばよい。

APIの呼び出し

OpenAIのChatAPIのドキュメントを見るとPOSTメソッドを使用する必要があることがわかる。 POSTリクエストを送るには、httr::POST()を使用すればよい。

まず、先にリクエストボディを作成しておく。APIがJSON形式を受け取る仕様であるため、list()で記述していく。

body <- list(
  # 使用する言語モデルを指定する
  model = "gpt-3.5-turbo",

  # メッセージを指定する
  messages = list(
    # role: user ならユーザーがAIに対して発したメッセージを意味する
    # content: メッセージ本文
    list(role="user", content="Hello!")
  )
)
res <- POST(
  url="https://api.openai.com/v1/chat/completions",
  # 認証情報などの付加的な情報
  config = add_headers(
    "Content-Type" = "application/json", # JSON形式で送ることを明示
    "Authorization" = str_c("Bearer ", Sys.getenv("OPENAI_API_KEY")) # 認証情報
  ),
  body = body,     # リクエストの本文
  encode = "json"  # リクエストはjsonで送るため、list形式で渡している
)

ステータスコードが200の場合、成功している。 401や403が返ってくる場合はAPIキーの情報をうまく渡せていない可能性が高いため、コードを今一度確認したほうがよい。

成功すると次のように返答が返ってくる。

body <- content(res)
body$choices[[1]]$message$content %>% cat()
Hello! How can I assist you today?

役割の指定

role = "system"としてシステム側からの指示を与え、ChatGPTの役割を明確化したり、どのように返答するかの例を与えたりすることもできる。

body <- list(
  model = "gpt-3.5-turbo",
  messages = list(
    # role: systemならシステムがAIに対して与えた指示になる
    list(role="system", content="あなたは英語の先生です。メッセージ内の英語に誤りがあれば訂正し、日本語で解説をしてください。"),
    list(role="user", content="This is pen.")
  )
)
res <- POST(
  url="https://api.openai.com/v1/chat/completions",
  # 認証情報などの付加的な情報
  config = add_headers(
    "Content-Type" = "application/json", # JSON形式で送ることを明示
    "Authorization" = str_c("Bearer ", Sys.getenv("OPENAI_API_KEY")) # 認証情報
  ),
  body = body,     # リクエストの本文
  encode = "json"  # リクエストはjsonで送るため、list形式で渡している
)
body <- content(res)
body$choices[[1]]$message$content %>% cat()
This is a pen.

「This is pen.」という表現には誤りがあります。正しくは、「This is a pen.」と言います。英語では、不特定の物を表す場合は冠詞「a」(または「an」)を使います。したがって、この文では「a pen」という具体的な物の名前の前に「a」を置く必要があります。

4.3 APIキーの無効化

APIキーの不正利用を防ぐ手段のひとつは、使わなくなったキーを無効化して使えなくしておくことである。 必要になればすぐに新しいキーを作れるので、一通り試し終わったら無効化しておくことを推奨する。

まず、再びAPI keysページに向かう。 API keyはゴミ箱のアイコンをクリックすれば削除できるが、2つ以上のキーが存在する状態でなければ無効化できない制限があるようなので、「Create new secret key」から再度新たなキーを作り、今回作った方を削除すればよい。

(参考)ChatGPTに外部のツールを操作させる

ChatGPTからの応答を人間に向けたフォーマットではなく機械に向けたフォーマットとして、ChatGPT自身に外部のツールを操作させる方法も存在する。その方法の一つが、ChatGPTのAPIへのリクエストボディにfunctionsという情報を記入し、操作したいツールの情報を記載することである。

Function calling and other API updates

上記のページの例では、「What’s the weather like in Boston right now?」という質問に対して、

  1. 質問文に応じた天候情報取得APIのリクエストボディ(JSON形式)をChatGPTに出力させる
  2. そのJSONを利用してAPIを呼び出し、天候情報を取得する
  3. 天候情報APIのレスポンス(JSON)をChatGPTに送信し、人間がわかりやすいように自然言語で返答させる

という3ステップを経て最終的に「The weather in Boston is currently sunny with a temperature of 22 degrees Celsius.」と返答させている。