ChatGPTでフィールドのテキストを要約・翻訳・タグ付けしてみる

kintone advent calendar 2024 の 6日目にエントリーしました、Spicaと申します。
早いもので今年ももうカレンダーの季節になりました。Qiitaへの寄稿も毎年のこの1回のみというのが続いてしまっていて個人的には猛省する季節でもあります。

さて今年のネタですが、いろいろ考えた結果ここはやはり**生成AI**であろうと。
それも**ChatGPT**との連携という最も基本的なところを実際に動かすことができるサンプルコードも書きつつ、まとめてこうと思います。

# やってみること
1. kintoneアプリのフィールドに保存されているテキストを要約する
2. 内容を表すタグを最大3つつける
3. 日本語以外で書いてある場合は翻訳する
4. 生成されるデータはJSONで受け取る

という処理を、ChatGPTのAPIを使って実現してみようと思います。
非常にシンプル。
ただ、キモになるのは **4** の **"JSONで受け取る"** かもしれません。プロンプトを工夫するだけ、といってしまえばそれまでなんですが、JSONで受け取ることができれば、kintoneアプリのフィールドに入れやすくなりますからね。
これはいろいろと応用が効くポイントかと思います。

# 事前準備
実際にデモを動かしてみたい場合はOpenAIのアカウントが必要になります。
また、APIを使いますので費用がかかります。
以前はお試しのチャージがあったんですが、残念ながら廃止されてしまったようです。最低でも $5 のチャージが必要です。
APIキーを発行するための身元証明ということでSMSメッセージを受け取れる電話番号の登録が必要になります。

これらのそこそこ高いようなそうでないような、ちょっと試すにはやっぱり高いハードルを超えましたら、APIキーの発行作業をしていきましょう。
とりあえずソースコードだけ見せてよ、という場合は [カスタマイズ用のコード](#カスタマイズ用のコード)へどうぞ。

## OpenAI のシークレットキーを発行する
[OpenAI](https://platform.openai.com/) にログインしたらページ右上の歯車アイコンから[設定画面](https://platform.openai.com/settings/organization/general) へ。
ページ左のメニューから **API keys** を押下。
![qiita_001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67365/2b5e88b7-3bc9-2206-b1f2-a33fee1f3a40.png)

### アカウントのverify
APIキーを発行するのに身元証明のため、アカウントのverifyが必要です。具体的にはSMSが受け取れる電話番号を登録して認証をします。

認証ができればAPIキーが発行できますので、
![qiita_002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67365/38294e71-1848-7a18-cf9b-8c43fb0bc926.png)
を押します。

### APIキーの詳細設定
発行するAPIキーの詳細を設定するダイアログが開くので、名前をつけてプロジェクトを選択。付与するAPIの権限を選んでいきます。
Permissions は Restricted を選択。
今回使うエンドポイントは 
**/v1/chat/completions**
なので、これを含むブロックの Write を有効にして、**Create secret key** を押します。
![qiita_003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67365/9b8a30b3-02ac-24e1-26e7-f3f1d2aff640.png)

### 発行されたシークレットキーを控えましょう
シークレットキーが表示されるのは、このときだけです。
![qiita_004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67365/525ca4c6-e9b2-683f-8ef6-b0617f09abfe.png)

# デモ用kintoneアプリの構成
kintoneアプリを作っていきます。
こんなアプリでデモを行っていきます。
- 「本文」：**複数行文字列フィールド**
    要約の元となるテキストを入力するフィールドです
- 「タグ1」「タグ2」「タグ3」：**1行文字列フィールド**
    生成されるタグがそれぞれに入力されます
- 「要約」：**複数行文字列フィールド**
    要約されたテキストが入力されるフィールドです
![Qiita_005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67365/e2987fb5-135f-433f-c6e3-901f519e9cfa.png)

作成したらコードをJavasSriptファイルとしてカスタマイズにアップロードします。

# カスタマイズ用のコード
「本文」フィールドに要約したいテキストを入力してレコードを保存すると、ChatGPTが要約し、タグを3つ作ってそれぞれフィールドに入力する、という概要のスクリプトです。

カスタマイズ用のJavaScriptコードとして動作します。
注意点として、発行したAPIキーをハードコーディングする箇所があるので、実際に使ってみたい場合でもそのまま**本番使用は控えて**、あくまでデモとしてご利用ください。
```javascript
(() => {
    'use strict';

    // フィールドコード
    const BASE_TEXT_FIELD = '本文'
    const PUT_TAG1_FIELD = 'タグ1'
    const PUT_TAG2_FIELD = 'タグ2'
    const PUT_TAG3_FIELD = 'タグ3'
    const PUT_GENERATE_TEXT_FIELD = '要約'

    // APIキーとエンドポイント
    const SECRET_KEY = '＜発行したAPIキー＞'
    const API_ENDPOINT = 'https://api.openai.com/v1/chat/completions'


    // 保存イベントトリガー
    kintone.events.on(['app.record.create.submit', 'app.record.edit.submit'],
    async (event) => {
        const record = event.record

        // validation
        if (!record[BASE_TEXT_FIELD] || !record[BASE_TEXT_FIELD].value) {
            alert('本文がありません')
            return event
        }

        const TextBody = record[BASE_TEXT_FIELD].value

        // ChatGPT に送信されるプロンプト（※コード説明箇所１）
        const content_body =
`#条件
<本文データ> の文章に対して以下の処理を行ってください：
    1. 文章を日本語に翻訳してください
    2. 翻訳後のテキストを最大 500 文字で要約してください
    3. 翻訳後のテキストに基づき、内容を端的に表す1～3つのタグを生成してください
    4. 以下のフォーマットのJSON形式で出力してください

#出力するJSONフォーマット
{
  "generated_text": "要約した最大500文字のテキスト",
  "tags": ["タグ1", "タグ2", "タグ3"]
}

#本文データ
${TextBody}`

        try {
            // OpenAI API呼び出し（※コード説明箇所２）
            const response = await fetch(API_ENDPOINT, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${SECRET_KEY}`
                },
                body: JSON.stringify({
                    model: 'gpt-3.5-turbo',
                    messages: [
                        { role: 'user', content: content_body }
                    ]
                })
            })

            const result = await response.json()

            // APIレスポンスの処理（※コード説明箇所３）
            if (response.ok && result.choices) {
                const json_str = result.choices[0].message.content

                try {
                    const json = JSON.parse(json_str)
                    record[PUT_GENERATE_TEXT_FIELD].value = json.generated_text
                    record[PUT_TAG1_FIELD].value = json.tags[0] || ''
                    record[PUT_TAG2_FIELD].value = json.tags[1] || ''
                    record[PUT_TAG3_FIELD].value = json.tags[2] || ''
                } catch (error) {
                    console.error(error)
                    alert('JSONのパースに失敗しました。')
                }
            } else {
                console.error(result)
                alert('要約の生成に失敗しました。')
            }
        } catch (error) {
            console.error(error)
            alert('要約の生成中にエラーが発生しました。')
        }

        return event
    })
})()
```

## コード説明１ プロンプト
ChatGPTに送信されるプロンプトを作っています。```${TextBody}``` の箇所は「本文」フィールドの文章が入ります。
いわゆる深津式の書き方です。
特に色をつけない文章の要約ですのでペルソナは与えていません。
出力形式を明示的に「JSONフォーマットで」と指定しています。こうすることでJSONにパースできるテキストデータを出力してくれるようになります。

```javascript
`#条件
<本文データ> のデータに対して以下の処理を行ってください：
    1. 入力データを日本語に翻訳してください
    2. 翻訳後のテキストを最大 500 文字で要約してください
    3. 翻訳後のテキストに基づき、内容を端的に表す1～3つのタグを生成してください
    4. 以下のフォーマットのJSON形式で出力してください

#出力するJSONフォーマット
{
  "generated_text": "要約した最大500文字のテキスト",
  "tags": ["タグ1", "タグ2", "タグ3"]
}

#本文データ
${TextBody}`
```

## コード説明２ OpenAIにリクエスト
APIキーをBearer認証ヘッダーで送信しています。
modelはChatGPTのどのエンジンを使用するか、を指定します。
今回はデモということで```gpt-3.5-turbo```にしました。指定するモデルによってリクエスト料金が変わります。また画像や音声を出力するモデルもあります。

```javascript
const response = await fetch(API_ENDPOINT, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${SECRET_KEY}`
    },
    body: JSON.stringify({
        model: 'gpt-3.5-turbo',
        messages: [
            { role: 'user', content: content_body }
        ]
    })
})
```

## コード説明３ 生成されたJSONテキストをパース
APIのレスポンスを処理する部分です。
```result.choices``` は配列で、たいていの場合データはひとつですが、たまに2つの候補を出力されることがあります。```result.choices[0]```として常に1つめを採用する形です。
JSON出力を指定していますので、メッセージはJSONにパースできるテキストとなっているはずです。
いちおうエラーハンドリングしつつ、オブジェクトに変換。
取得できたデータをフィールドに入力しています。

```javascript
// APIレスポンスの処理（※コード説明箇所３）
if (response.ok && result.choices) {
    const json_str = result.choices[0].message.content

    try {
        const json = JSON.parse(json_str)
        record[PUT_GENERATE_TEXT_FIELD].value = json.generated_text
        record[PUT_TAG1_FIELD].value = json.tags[0] || ''
        record[PUT_TAG2_FIELD].value = json.tags[1] || ''
        record[PUT_TAG3_FIELD].value = json.tags[2] || ''
    } catch (error) {
    ...
```

## 使ってみた結果１
wikiの記事、[技術的特異点](https://ja.wikipedia.org/wiki/%E6%8A%80%E8%A1%93%E7%9A%84%E7%89%B9%E7%95%B0%E7%82%B9) から引用したテキストを保存したらこのようになりました。
![Qiita_006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67365/bb94e542-4860-e38b-ce7e-04e9446ae510.png)

## 使ってみた結果２
英語wikiの記事、[Singularity_(software)](https://en.wikipedia.org/wiki/Singularity_(software)) からの引用でも試してみました。ちゃんと指定通り翻訳もしてくれてますね。
![Qiita_007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67365/226f536c-d452-d3c3-6039-d5ae56ff6435.png)

# まとめ
生成AIを使ったkintone連携としてはとても基礎的な使い方と思います。
実際に運用する場合はAPIキーを秘匿する必要があるので、バックエンドでAPIリクエストをする形にしないとですが、使用する生成モデルを変更することで画像や音声を出力することも可能となっていますので、プロンプトの書き方次第でほんとにいろいろなことができるかと思います。

---

さて最後にいつもの宣伝です。

[『機能拡張スタンダードAll-In』](https://spica.tokyo/bluelaunch)というプラグインを販売しておりますー。
レイアウトを作ってPDF出力したり、サブテーブルのSUMIFなども可能な自動計算とか、レコード検索の入力ボックス設置などいろいろできるプラグインとなっております。
2017年のリリース以来ありがたいことに多くの方にご利用いただきまして、機能追加を重ねております。
お問い合わせなど不要でご試用いただけます。

そんな感じでーす。