見出し画像

デモアプリのコードも公開 中央省庁向け生成AIワークショップ

デジタル庁のFact & Data Unit所属かつAI 活用推担当者の大杉直也です。ChatGPTやBard等の生成AIが話題ですが、皆さんご利用されてますか?

2023年6月26日に国家公務員を対象に、生成AIを中央省庁の働き方改革に活用する方法を探るワークショップを開催しました。

デジタル庁では「誰一人取り残されない、人に優しいデジタル化を。」をミッションに掲げ、日本の行政DXを変革する仲間を募集しています。今回の取組もその一部です。ぜひ採用ページをご覧ください。

本記事では2023年6月26日に実施された「中央省庁向け生成AIワークショップ」で用いた資料類やそこで得られた知見などを報告いたします。
記事の最後にはデモアプリのコードも公開しています。

このワークショップでは、官僚の生産性向上を目的としたため、文章を扱う業務を念頭に置きました。そこで、生成AIには「テキストを入力したらテキストを出力する」ものだけを用いました。生成AIにはテキスト以外にも画像を入出力するものも有名ですが、本ワークショップでは画像は対象外としました。


イベントの概要

中央省庁(いわゆる霞が関)の全職員むけに公募を行い、当日は19省庁から43人が本ワークショップに参加しました。この43人は生成AIの事前知識も役職もバラバラでした。

この43人を、事前に募集した生成AI活用アイデアの方向性で分類し、5人1チーム(全部で9チーム)を作りました。アイデアの方向性が似た人同士でチームを作ったため、ほとんどの場合、初対面の方同士でチームが作られました。

本ワークショップは広めの会議室の一室で、10時開始17時終了の一日がかりで行いました。

ワークショップの様子の画像
デジタル庁の入るオフィスの一室で43人が集まるワークショップ。空気感的にも物理的にも熱量の高いイベントとなりました

ワークショップは導入編と実践編の2つで構成されました。
導入編
1.参加者間のアイデア共有
2.専門家による生成AIに関する講義
3.講義を踏まえた参加者間のフィードバック

実践編
1.生成AIを導入する際の技術面に関する講義
2.参加者のアイデアが実現可能かを検討するための実際に生成AI試行
3.生成AI試行結果の共有

前半の導入編 (10時から13時)ではいわゆるアイデアソンを行いました。
そこではチーム内での自己紹介から始まり、テキスト生成AIでどのようなことができるかの解説をし、最後にチーム内でどのアイデアを午後のハンズオンの対象にするかを決めました。

お昼休憩をはさんだ後半の実践編 (14時から17時30分)ではハンズオンを行いました。
そこでは実際にテキスト生成AIにどのような入力をできるかの解説をし、実際に生成AIの技術検証環境(デモアプリ)をさわってみて、アイデアの実現可能性について検討を行いました。この後半の実践編をデジタル庁で内製したため、本記事ではこちらの内容を手厚めに紹介いたします。

最後に、今後のデジタル庁での取り組みへの参考にするため、参加者に対して事後アンケートをとりました。

その結果、「今回のワークショップを自組織の他の方にどの程度勧めたいと思いますか?」という設問に対し、10点満点で平均点が9点以上(有効回答数31)となり、参加者にとって満足度の高いイベントであったようです。

同じ設問で「アイデアソンしかなかった場合」と「ハンズオンしかなかった場合」についてもアンケートをとったところ、それぞれ平均点が6点台と7点台でした。

アイデアソンとハンズオンの両方あったことで、より満足度の高いイベントになったと考えています。

ハンズオンで用いたコンテンツ

ハンズオンで用いたコンテンツはデジタル庁で内製したため、本記事で公開いたします。
ハンズオンでは、「生成AIをこう使えばいい!」を本格的なシステム開発の前に「職員が独力で試行錯誤し、各自が検証できる状態になること」を目的としました。

AIプロジェクトに予算をつけて本格的に始める前に、「そもそもできそうか」「作るためにどんなデータが必要か」「本格活用の注意点はどこか」などを可能な限り明確にします。
ムダな投資を抑制し、より品質の高い成果物を得られるようになることが目標です。

講義資料

https://www.digital.go.jp/assets/contents/node/information/field_ref_resources/5896883b-cc5a-4c5a-b610-eb32b0f4c175/82ccd074/20230725_resources_ai_outline.pdf

この資料は、後述のハンズオンで利用するOpenAI社のGPTモデルの活用方法を伝えるために作りました。
純粋なエンジニアリング的な内容よりも、前提知識の説明や手引きやコツについての解説を重視しました。

当日は、後述のデモアプリを使ったライブデモを中心に説明しました。この資料はほぼ当時の内容のまま、となります。AIの進歩は速いので、すでに古くなった内容も含まれる可能性がありますが、ご了承ください。

GPT API用のチャット風インターフェースのデモアプリ


チャット風インターフェースのデモアプリをスクリーンショットした画像
チャット風インターフェースのデモアプリのスクリーンショット。実態は、Azure API Manager経由でAzure OpenAI Service を叩くだけの静的なHTML。「GPTへの入力フォーム」に記載し、「gptに聞く」ボタンを押すと、GPTのAPIの結果が返ってきます。system promptや過去のuser/assistant promptも直接編集でき、APIに送るpromptの試行錯誤が可能です。また、ラジオボタンから利用するモデルを切り替えたり、APIリクエストに伴う費用をリアルタイムで計算する、などの工夫も行っております

このデモアプリを使い、生成AIでアイデアが実現可能かの検証を行いました。生成AIが技術的にも概念的にも新しいものなので、やはり実際に動くものを自ら触ってみない限り、なかなか理解は進みません。

このアプリは今回のワークショップの間に合計220回も使われました。積極的に利用されたことが伝わってきます。
一方でワークショップ期間中のインフラコストは、全体で1,000円以内におさまりました。簡単なものですがデモアプリのソースコード(1枚のHTML)を本記事の末尾に掲載しました。

最終成果物のシート

チャット風インターフェースのデモアプリのスクリーンショット。実態は、Azure API Manager経由でAzure OpenAI Service を叩くだけの静的なHTML。「GPTへの入力フォーム」に記載し、「gptに聞く」ボタンを押すと、GPTのAPIの結果が返ってきます。system promptや過去のuser/assistant promptも直接編集でき、APIに送るpromptの試行錯誤が可能です。また、ラジオボタンから利用するモデルを切り替えたり、APIリクエストに伴う費用をリアルタイムで計算する、などの工夫も行っております  このデモアプリを使い、生成AIでアイデアが実現可能かの検証を行いました。生成AIが技術的にも概念的にも新しいものなので、やはり実際に動くものを自ら触ってみない限り、なかなか理解は進みません。  このアプリは今回のワークショップの間に合計220回も使われました。積極的に利用されたことが伝わってきます。一方でワークショップ期間中のインフラコストは、全体で1,000円以内におさまりました。簡単なものですがデモアプリのソースコード(1枚のHTML)を本記事の末尾に掲載しました。  最終成果物のシート  最終成果物をまとめるシートの画像
最終成果物をまとめるシート。実際はこれをもとにした大きい模造紙を用意し、そこに直接書き込んだり付箋を貼ったりしました。付箋や記入文章はあくまでイメージで実際に用いられたものではありません。付箋も模造紙に対してもっと小さいバランスでした。アナログな方法ですが「チームみんなで書き込める」「同時にたくさんの人が見られる」ためには効率がよいやり方でした。このシートの考案・制作は内閣人事局の方が行いました。

このワークショップを通じて以下のようなアイデアが検証されました。

  • 国際会議における議事録の要約作成と次回会合の対応方針の原案検討

  • 英語の議事録を入力し、日本語による要約を作成し、対応が必要な事項を抜粋させる。さらに立場の表明例を入力し、次回会合における日本の対処方針の原案を作成させる

  • 国民からの問い合わせ対応のためのチャットボット

  • 国民からの電話、メールなどによる問い合わせについて、Webページの案内などによる回答案の作成や想定問答の作成を行わせる

  • 会議の模様作成の自動化

  • 録音した音声の文字おこし版から、フィラーワードの削除、誤変換の指摘、今後の課題の抽出などを行わせ、配布に耐えうるものにする

他にも、以下のようなアイデアが検証されました。

  • 法律分野における生成AIの活用

  • 調達仕様書の文書校正

  • 国会業務における答弁の原案作成

  • 政策企画立案の事例レポート作成

  • マニュアル作成及び問い合わせ対応(内部職員向け)など

総じて、いきなり人手を介する部分をゼロにする、はなかなか難しく、いきなりのコスト削減効果はなかなか見込めなさそうでした。一方で、作文業務に原案の提示や校正などを生成AIに行わせることで、品質の向上にはかなりの可能性が感じられました。

また、国会答弁のように一度限りのものよりも、なんども繰り返し同じ文章を参照する必要性が高い業務(ガイドラインに対する問い合わせなど)は、より高い効果が見込めそうです。
本イベントをやって「楽しかった」「勉強になった」だけに終わらせることなく、実際の成果につなげるために、引き続き、行政による生成AI活用のサポートを行っていく予定です。

技術についての補足説明

なぜOpenAIのGPTモデルを用いたか?

ワークショップ開催時点では、(1)実際の業務改善の検討に十分な品質であり、(2)コスト含めて利用しやすい、の2点を満たすものがOpenAIのGPTモデルしかなかったためです。このブログ執筆時点(2023年7月)では、GPT以外のテキスト生成AIのモデルも様々な組織が研究・開発を行い、ものすごい勢いで進歩しているため、OpenAI社以外のモデルも選択肢に入れて検討しております。

ただし利用可能なOpenAIのGPTモデルは海外サーバで動くものしかなく、要機密情報を送ることはNGという整理になったため、本ワークショップではAIへの入力に公開情報のみを扱いました。

なぜハンズオンではChatGPTではなくGPTのAPIを用いたか?

実際に業務改善に組み込むためには、(1)独自のデータベースとの統合などの扱うデータの自由度、(2)既存のチャットツールやメールシステムの活用などのインターフェースの自由度が求められる可能性があります。それではChatGPTでは要件を満たさないからです。

また、検証とシステム開発ができる限り地続きにし、開発途中でも「最低限の挙動は動く」状態で開発できれば、一般に効率がよいとされているので、それ意識しての職員が直接APIをさわれる状態を目指しました。

さらに、GPT APIの方がassistant promptの明示的な指定などができ、やりたい処理に対する自由度が高いといった理由もあります。
最も重要な点は、特定のサービスに未来永劫囲い込まれる心配を減らすことです。GPT APIであれば、今後他社の生成AIのAPI利用や自前サーバで生成AIを動かす場合に、置き換えが比較的容易だと考えています。

最後に

私個人の感想となりますが、今回のワークショップの企画や諸々の技術要素の支援を行い、気が付いたことをざっくばらんに書きます。自然な感情の発露を心がけるため多少文体が乱れていますが、ご了承ください。

  • やるぞ!というトップ(河野大臣)の号令があったおかげで、なにかと話がスムーズに進んだ

  • また、私含め実行担当者が小さくまとまろうとする中で、河野大臣による「もっとこうした方がいいのでは?」という問題提起は、結果的にイベントをより良いものにするために重要であった

  • デジタル庁のガバメントクラウドやセキュリティ担当の方々が柔軟に対応してくれたため、デモアプリなどの開発タスクに集中して着手できた

  • 関係者も論点も多い中での、行政官の方々の論点整理や筋道の通し方の圧倒的な速度や品質を間近で見られたことにより、非常に学びになった

  • 最後のワークショップの会場確認などを除き、基本リモートワークができたおかげで非効率な移動時間なしに業務に集中できた

  • 当日のTV取材依頼を含むメディア対応などの突然のお願いを、快く引き受けかつ的確に対応してくれた広報の方々には、さすがのプロフェッショナルだと感心した

  • 行政での仕事ということで、普段はなかなか接触がない民間の方々(今回ではMicrosoft社やOpenAI社の技術者)と情報交換の機会が多く得られた

  • なによりも中央省庁での仕事は、スケールがとても大きいものが多く、やりがいもとても大きい

と、ほめてばかりですが、やはりしんどいこともまぁまぁあります。仕事なので仕方なし、とは思うものの、エンジニアとしてするべきことがたくさんあります。一緒に働けるエンジニアを募集中です。

OpenAI社のShane Gu氏とデジタル庁のエンジニアとの間の情報交換したときの写真
OpenAI社のShane Gu氏とデジタル庁のエンジニアとの間の情報交換したときの写真。OpenAI社が今まで自分が見聞きした民間企業とはまた違う方向性・世界観をもった独特な組織ということもあり、とても刺激になりました

Appendix(デモアプリのコード公開)

今回のワークショップで使用したデモアプリのコードを掲載します。
APIキーなどをご自身の環境に合わせて、ご自由に活用してください。

<!DOCTYPE html>  

<html lang="ja">  

<head>  

    <meta charset="UTF-8">  

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>公開用ChatGPT風HTML</title>

    <script>  

        async function sendData() {  

            let elements = document.getElementsByName('model');

            let len = elements.length;

            let modelName = '';

            for (let i = 0; i < len; i++){

                if (elements.item(i).checked){

                    modelName = elements.item(i).value;

                }

            }

            document.getElementById("send").setAttribute("disabled", true);

            let modelVer = "Enter your gpt3.5 model name"; //Azure OpenAI Serviceだとデプロイしたモデル名を自分でつけるため

            if (modelName==="4.0"){

                modelVer = "Enter your gpt4 model name";

            }

            else if (modelName==="4.0(32k)"){

                modelVer = "Enter your gpt4(32k) model name";

            }

            const url = "Enter your azure openai service endpoint" + modelVer + "/chat/completions?api-version=2023-03-15-preview"; //デジ庁内の利用ではOpenAI ServiceではなくAPIMのエンドポイントを利用。OpenAI APIの場合はmodel種類はURLではなくパラメータで送る

            const input = document.getElementById("input").value;  

            const system = document.getElementById("system").value;  

            const apiKey = "Enter your api key"; // ここにAPIキーを入力してください  

            var message = [

                {"role": "system","content":system}

            ]

            const prompts = document.getElementsByClassName("prompt");

            var i_last = 1;

            for (let i=0; i< prompts.length-2; i+=2){

                if (prompts.item(i).value.length>0){

                    message.push({"role": "user","content":prompts.item(i).value});

                    message.push({"role": "assistant","content":prompts.item(i+1).value});

                    i_last = i_last+1

                }

            }

            message.push({"role": "user","content":input})

            const requestOptions = {  

                method: "POST",  

                mode: "cors",

                headers: {  

                    "Content-Type": "application/json",  

                    "Ocp-Apim-Subscription-Key": apiKey,

                    "Access-Control-Allow-Origin": "*",

                    cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached

                    credentials: 'omit', // include, *same-origin, omit,

                    "Host":"Enter your azure openai service endpoint"

                },  

                body: JSON.stringify({

                                    "messages": message,

                                    "temperature": 0

                                        })

            };

            try {

                const response = await fetch(url, requestOptions);  

                if (!response.ok) {  

                    throw new Error(`HTTP error! status: ${response.status}`);  

                    }

                const data = await response.json();

                console.log(data);

                document.getElementById("input"+i_last).value = input;

                document.getElementById("output"+i_last).value = data["choices"][0]["message"]["content"];

                let cost = data["usage"]["total_tokens"] * 0.002 * 1/1000; //3.5の価格。今後値下がりするかも

                if (modelName==="4.0"){

                    cost = (data["usage"]["prompt_tokens"] * 0.03 * 1/1000 + data["usage"]["completion_tokens"] * 0.06 * 1/1000);

                }

                else if (modelName==="4.0(32k)"){

                    cost = (data["usage"]["prompt_tokens"] * 0.06 * 1/1000 + data["usage"]["completion_tokens"] * 0.12 * 1/1000);

                }

                document.getElementById("usage").innerText = "このリクエストの利用料金: " + cost + " USD";  

                document.getElementById("send").removeAttribute("disabled");

            }

            catch (error) {

                alert(`Fetchエラー: ${error.message}`);

            }

        }  

    </script>  

</head>  

<body>  

    <details>

        <summary>使い方</summary>

        <h3>使用上の注意</h3>

        <li>各環境にあわせた使用上の注意点を書く</li>

        <h3>操作方法</h3>

        <li>GPTへの入力フォームにGPTに聞きたいことを書き、「gptに聞く」ボタンを押してください。すると画面下のassistant responseの箇所にGPTからの応答が入ります</li>

        <li>過去に入力した文字列はuser promptという箇所に残ります。これを消さずに、再度GPTへの入力フォームから送信すると前回の会話をふまえた反応が返ります</li>

        <li>system promptを工夫することでGPTへの応答をある程度制御できます。詳細は講義資料やGPT APIのドキュメントなどをご参照ください</li>

        <li>2種類のモデルをラヂオボタンから選ぶことができます。4の方が一般に賢いとされていますが、価格が高くレスポンスも遅いです</li>

    </details>

    <p> GPT

        <label><input type="radio" name="model" value="3.5" checked>3.5</label>

        <label><input type="radio" name="model" value="4.0">4.0</label>

        <label><input type="radio" name="model" value="4.0(32k)">4.0(32k)</label>

    </p>

    <p id="usage">このリクエストの利用料金: 0 USD</p>

    <div>

        <label for="input">GPTへの入力フォーム</label><br>

        <textarea id="input" name="input" rows="10" cols="140">This is a pen.

を和訳して</textarea>  

        <button id="send" onclick="sendData()">gptに聞く</button>  

    </div>  

    <div>

        <p class="system_p">

            <label for="system">system prompt</label><br>

            <textarea id="system" name="sytem" rows="10" cols="140">you are helpful assistant for Japanese Goverment</textarea>

        </p>

        <p class="user_p">

            <label for="input1">user prompt</label><br>

            <textarea id="input1" class="prompt" name="user" rows="10" cols="140"></textarea>

        </p>

        <p class="output_p">

            <label for="output1">assistant response</label> <br>

            <textarea id="output1" class="prompt" name="assistant" rows="10" cols="140"></textarea>  

        </p>

        <p class="user_p">

            <label for="input2">user prompt</label><br>

            <textarea id="input2" class="prompt" name="user" rows="10" cols="140"></textarea>

        </p>

        <p class="output_p">

            <label for="output2">assistant response</label> <br>

            <textarea id="output2" class="prompt" name="assistant" rows="10" cols="140"></textarea>  

        </p>

        <p class="user_p">

            <label for="input3">user prompt</label><br>

            <textarea id="input3" class="prompt" name="user" rows="10" cols="140"></textarea>

        </p>

        <p class="output_p">

            <label for="output3">assistant response</label> <br>

            <textarea id="output3" class="prompt" name="assistant" rows="10" cols="140"></textarea>  

        </p>

        <p class="user_p">

            <label for="input4">user prompt</label><br>

            <textarea id="input4" class="prompt" name="user" rows="10" cols="140"></textarea>

        </p>

        <p class="output_p">

            <label for="output4">assistant response</label> <br>

            <textarea id="output4" class="prompt" name="assistant" rows="10" cols="140"></textarea>  

        </p>

        <p class="user_p">

            <label for="input5">user prompt</label><br>

            <textarea id="input5" class="prompt" name="user" rows="10" cols="140"></textarea>

        </p>

        <p class="output_p">

            <label for="output5">assistant response</label> <br>

            <textarea id="output5" class="prompt" name="assistant" rows="10" cols="140"></textarea>  

        </p>

        <p class="user_p">

            <label for="input6">user prompt</label><br>

            <textarea id="input6" class="prompt" name="user" rows="10" cols="140"></textarea>

        </p>

        <p class="output_p">

            <label for="output6">assistant response</label> <br>

            <textarea id="output6" class="prompt" name="assistant" rows="10" cols="140"></textarea>  

        </p>

        <p class="user_p">

            <label for="input7">user prompt</label><br>

            <textarea id="input7" class="prompt" name="user" rows="10" cols="140"></textarea>

        </p>

        <p class="output_p">

            <label for="output7">assistant response</label> <br>

            <textarea id="output7" class="prompt" name="assistant" rows="10" cols="140"></textarea>  

        </p>

        <p class="user_p">

            <label for="input8">user prompt</label><br>

            <textarea id="input8" class="prompt" name="user" rows="10" cols="140"></textarea>

        </p>

        <p class="output_p">

            <label for="output8">assistant response</label> <br>

            <textarea id="output8" class="prompt" name="assistant" rows="10" cols="140"></textarea>  

        </p>

        <p class="user_p">

            <label for="input9">user prompt</label><br>

            <textarea id="input9" class="prompt" name="user" rows="10" cols="140"></textarea>

        </p>

        <p class="output_p">

            <label for="output9">assistant response</label> <br>

            <textarea id="output9" class="prompt" name="assistant" rows="10" cols="140"></textarea>  

        </p>

        <p class="user_p">

            <label for="input10">user prompt</label><br>

            <textarea id="input10" class="prompt" name="user" rows="10" cols="140"></textarea>

        </p>

        <p class="output_p">

            <label for="output10">assistant response</label> <br>

            <textarea id="output10" class="prompt" name="assistant" rows="10" cols="140"></textarea>  

        </p>

    </div>

   

</body>  

</html>  

◆これまでの「デジタル庁Techブログ」の記事は以下のリンクをご覧ください。

デジタル庁の中途採用に関する情報はこちら。