まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

2024/09

先日、「Watsonx.AI の LLM をチューニングする」というブログを公開しました。IBM Cloud から提供されている IBM の生成 AI である WatsonX.AI の標準 LLM を、独自に与えるテキストデータによってチューニングを施した新しい LLM を作る、という内容を、その実行例を交えて紹介したものでした。

紹介した方法で作られた新しい LLM を REST API で使う方法についても紹介ページに含まれているのですが、API プログラミングが求められるとなると、まだ少しハードル高いですよね。となると、チューニング済み LLM は WatsonX.AI のプロンプトラボという機能/UIを使って使うことになる・・・ というのではあまりにも寂しいです。

そんなわけで、本日のブログエントリでは「チューニング済み WatsonX.AI LLM を Node-RED で使う」手順を紹介します。Node-RED はフローエディタを使った、いわゆるノーコード/ローコード開発ツールの代表的な1つです。この Node-RED であればプログラミングにあまり自信がない人でもデータの流れを理解していれば WatsonX.AI プログラミングができるようになる、かも、という狙いです。


【Node-RED の用意】
まず何はともあれ Node-RED 環境が必要です。既に Node-RED を使える環境がある場合はその環境を使ってください。環境がない場合はなんらかの方法で Node-RED 環境を用意する必要があります。

比較的一般的な方法は「手元のローカル PC に Node-RED をインストール」する方法です。Windows, Mac, ラズベリーパイなどに対応していて、管理者権限があればインストールすることができます:
https://nodered.jp/docs/getting-started/local

または「IBM Cloud の Code Engine を使ってインストールする」方法もあります。こちらはリソースの使い方によっては有料となってしまいますが、比較的安価にコンテナアプリケーションを実行することができる環境です。この環境で DockerHub 内の nodered/node-red を動かす、という方法です。

Code Engine を使う場合はアプリケーションの登録時に以下の設定をしてください。まず Code の種類は "Use an existing container image" を選択し、イメージ名は "nodered/node-red" と入力します:
2024092701


下にスクロールします。次にこの1アプリに使うリソース量を指定します。まず CPU とストレージは最小のものでいいと思います(多いほど値段が上がります)。注意が必要なのはその下の Autoscalling 設定です。この Max 値は 1 固定でいいのですが、問題は Min 値です。ここを 1 にするとアプリケーションは(最小インスタンス数=1、最大インスタンス数=1になるので)1つ起動しっぱなしになります。常に稼働しているため料金はそれなりにかかりますが、常に使える状態で待機していることになります。 一方、ここを 0 にすると(最小インスタンス数=0、最大インスタンス数=1になるので)アクセスがない時は消え、アクセスがあるとその場で1つ動き出します。アクセスのない時間帯の料金は(0インスタンスなので)かかりませんが、アクセスから実際にアプリケーションが動き出すまでのタイムラグがあり、また一度保存した状態があっても、アクセスがなく 0 インスタンスになってしまうと、次にアクセスした時には保存状態が残りません。いつでも使える状態がいいか、アクセスしない時間のコストをセーブするのがいいか、優先順位に合わせて選んでいただきたいのですが、以下の機能を試す間だけでも 1 に設定してインスタンスが落ちないようにしておくのがいいと思いました:
2024092702


そしてドメインマッピングの設定は "Public" を選択し、最後に "Create" ボタンで作成します:
2024092703


1~2分でアプリケーション(=Node-RED)が起動します。ステータスが "Ready" になったら "Test application" から "Application URL" を選択すると実際のアプリケーション(=Node-RED)にアクセスできます:
2024092705


アクセスに成功するとこんな感じの画面になり、ここから Node-RED が使えます:
2024092706


ここでは2つの方法を紹介しましたが、これ以外の方法(普通にパブリッククラウドにデプロイするとか・・)でももちろん構いません。何らかの方法で Node-RED が使えるようになっているという前提で以下の説明を続けます。


【watsonx.ai ノードのインストール】
Node-RED から Watsonx.AI を使えるようにするため、用意した Node-RED に拙作の watsonx.ai ノードを追加します:
2024093001


追加手順は以下です。まず Node-RED の右上のメニューから "Manage Pallete(パレットの管理)" を選択します:
2024092802


パレットの管理画面が表示されます。ここで "Install" タブに切り替え、検索ボックスに "watsonx" と入力します。すると(2024-09-28 時点では他の候補は見つからないのですが) "node-red-contrib-dotnsf-watsonx" という名前の公開ノードが見つかるはずです(2024-09-30 時点では最新バージョン 0.2.3 を公開しています)。この "Install" ボタンをクリックします:
2024092803


「インストールしていいか?という確認画面が表示されたら "Install" を押してください:
2024092804


インストールに成功すると、このような画面が表示されます:
2024092805


ダイアログをクローズして元の画面に戻ると、ノード一覧の function カテゴリ内に "watsonx.ai" と書かれたノードが追加されているはずです。これで Node-RED から比較的簡単な方法で watsonx.ai の生成 AI が使えるようになりました:
2024092806


【watsonx.ai 側のパラメータを確認】
準備の最後に、この後実際に Node-RED から Watsonx.AI を使う際にあたふたしないよう、watsonx.ai ノードに指定する各種パラメータの値をあらかじめまとめて調べておくことにします。

まず watsonx.ai ノードを左のパレット一覧から右のフロー部分にドラッグ&ドロップし、その後ダブルクリックしてください:
2024092807


すると以下のようなダイアログが表示されます。これが watsonx.ai ノードに指定するパラメータです:
2024092808


これらに指定するパラメータを確認しましょう。まずは watsonx.ai のプロンプトラボ画面を表示し、そこで指定するモデルをチューニングで作成したもの(下図では "kkimura-foreign-exchange-llm-v1")に設定します:
2024092809


まず画面右の "View Code" というボタンをクリックすると、このプロンプトで実行する問い合わせを Curl で実行する時の指定内容が表示されます。この中に Location と Deployment ID の情報が含まれています:
2024092811


例えば curl で指定する URL が以下のような値の場合、、
curl "https://jp-tok.ml.cloud.ibm.com/ml/v1/deployments/(deployment_id)/text/generation?version=2023-05-29"

青字の ".ml.cloud.ibm.com" の前にある文字列 "jp-tok" が Location の値です。また "deployments/" と "/text" の間にある赤字の部分にある文字列が Deployment ID です。

なお API Key は IBM Cloud ダッシュボードで作る API Key の値です。またチューニング LLM を使う場合、Project ID と Model ID はともに指定不要です(指定されたままでも構いませんが、Deployment ID が指定されている場合は無視されます)。

これらの値を先ほどのダイアログ内に指定し、最後に "Done" ボタンをクリックします:
2024092812


これでパラメータの準備も完了しました。


【Node-RED から watsonx.ai のチューニング済み LLM にアクセスする】

Node-RED のフローを完成させましょう。watsonx.ai ノードの左側に inject ノードを、右側に debug ノードを配置し、これらを繋げます:
2024092813


inject ノードをダブルクリックしてダイアログを開き、"msg.payload" の種類を文字列に(図で az と書かれている指定)、文字列の内容を "与えられた質問に対する答を計算して簡潔に答えてください。\\\\n\\\\n1 ドルは 144.3 円です。 10 ドルは何円ですか?" と入力して、Done をクリックします(msg.topic は使いません。×をクリックして削除しても構いません):
2024092814


ここまで準備できたら、Node-RED 画面右上の "Deploy" ボタンをクリックして、このフローをデプロイします:
2024092815


そして inject ノード左のボタンをクリックすると指定した文字列が watsonx.ai ノードに渡され、Deployment ID で指定されたチューニング済み LLM で処理され、その結果が debug ノードに渡されます。debug ノードに渡った結果は画面右の debug ペイン(虫のマーク)で確認することができます:
2024093000

 
まだチューニングが充分ではないので、期待していたような出力結果ではありませんが、Watsonx.AI のチューニング済み LLM を Node-RED からも使うことができる、ということが確認できました。

というわけで改めて、WatsonX.AI 用 Node-RED カスタムノードがチューニング済み LLM にも対応できましたので、よかったら使ってください:
2024093001



IBM Cloud から提供されている生成 AI である Watsonx.AI の LLM をチューニングする機会があったので、その内容や手順、そして気になるチューニングのコストなどをまとめました。あくまで一つの例ですが、参考になればと思っています。


【Watsonx.AI の LLM チューニング】
本記事で後述する内容は 2024-09-25 時点のものです。この時点では Watsonx.AI の LLM チューニングはいわゆる「プロンプト・チューニング」のみ可能です。

LLM の基になる非構造テキストデータを大量に再学習、するようなチューニングではなく、あくまで「プロンプトエンジニアリングをベースとしたチューニング」です。詳しくは後述しますが、ベースとなる LLM に対して、入力例と出力例を複数与えてプロンプトに出力の方向性を指示するような改良を LLM に加える、というものです。プロンプトエンジニアリングで与えるサンプル部分のトークン数が多くなって1回の問い合わせコストが高くなってしまうことを防ぐことがメリットとなるものです。


【どんなチューニングをするか】
当然ですが、「チューニング前の LLM では期待した返答にならないような問い合わせ」をチューニングして改良することを試みます。というわけで、まずはチューニング前の granite-13b-instruct-v2 モデルを使って、フリーフォームで以下のプロンプトを試してみました(2024-09-25 時点での試行結果です):

「1 ドルは 140.1 円です。 10 ドルは何円ですか?」
2024092501


このプロンプトに対して「生成」ボタンで問い合わせすると「140.1 円です。」という返答が返ってきました。うーん、価格を問い合わせていることは理解できているようですが、こちらが期待した計算結果にはなっていませんでした:
2024092502


そこで計算方法含めてプロンプトでガイドしてみます。「フリーフォーム」から「構造化」に変えます(この時に LLM モデルが切り替わることがあるので、改めて granite-13b-instruct-v2 モデルを選びます)。そして "例" と書かれた箇所を「新規テスト+」ボタンで行を増やしながら以下のように3行入力します。与えられた情報を使って、答を計算して返答するような例をいくつか指示してみました:
入力出力
11 ドルは 140 円です。 10 ドルは何円ですか?1 ドルが 140 円なので、 10 ドルは約 140 * 10 = 1400 円です。
21 ドルは 140.1 円です。 20 ドルは何円ですか?1 ドルが 140.1 円なので、 20 ドルは約 140.1 * 20 = 2802 円です。
31 ドルは 140.3 円です。 30 ドルは何円ですか?1 ドルが 140.3 円なので、 30 ドルは約 140.3 * 30 = 4209 円です。

2024092503


そして "試行" の入力欄に以下を入力し、テキストを生成させます:
1 ドルは 139.1 円です。 10 ドルは何円ですか?
2024092504


これで「1 ドルが 139.1 円なので、 10 ドルは約 139.1 * 10 = 1391 円です。」と答えてくれることを期待していました。が、結果は・・・ 計算や数値は期待通りなのですが、"Thornton" って何?? ともあれ、数字を計算して答えることや、その計算方法は理解してもらえている・・・と思います:
2024092505


というわけで、この方向で為替計算を教えるような LLM チューニングを試みてみます。


【プロジェクトと Tuning Studio の準備】
改めて、ここからが LLM チューニングの紹介内容です。

まずは LLM チューニングを行うための準備といえる作業をいくつか実行します。その1つが「プロジェクトの準備」です。既に Watsonx.AI のプロジェクトを作成済みの人もいると思いますが、もし環境的に問題なければ新しいプロジェクトを1つ作ってから LLM チューニングを行うことを推奨します(そうすると後述で紹介するチューニングコストを確認しやすいため)。もちろん既存プロジェクトを選択してチューニングしても構いません。

プロジェクトを新規に作成する前に現在の環境で作成されているプロジェクトの一覧を確認します。 Watsonx.AI の画面左上のハンバーガーメニューをクリックします:
2024092506


このメニューから「すべてのプロジェクトの表示」を選択します:
2024092507


現在までに作成されたプロジェクトの一覧が表示されます。ここから1つ選択して、そのプロジェクト内でチューニングを実行することも可能です。 プロジェクトを新規に作成するには右上の「新規プロジェクト」ボタンをクリックします:
2024092508


新規に作成するプロジェクトの情報を入力します。まずはプロジェクトの名前を入力します:
2024092509


このプロジェクトで利用するストレージサービス(Cloud Object Storage)のインスタンスを選択します。1つ選ぶか、新規に作成して指定します。最後に「作成」ボタンが有効化されたら「作成」します:
2024092510


既存プロジェクトを選択した場合はそのプロジェクトが、プロジェクトを新規作成した場合は作成されたプロジェクトが開きます。この画面内でプロジェクト内で使ったリソース量が表示されます。新規作成した場合はこの時点ではゼロになっているので、この後のチューニング作業でどの程度増えているか確認しやすいと思っています:
2024092511



【Tuning Studio によるチューニングの手順】
Watsonx.AI では LLM をチューニングするためのオンラインツール "Tuning Studio" を利用することができます。このオンラインツールを使って、チューニング元の LLM モデルに自分で与えた学習データを使ったチューニングを施した、新しい LLM モデルを作ることができます。

では以下に実際に Tuning Studio を使って LLM をチューニングしていく手順を紹介します。

プロジェクト画面を開きます(作ったばかりなせいか、リソース消費量の表示がおかしくなっています。この時点では実際は全てゼロのはず)。ここから実際にプロンプト問い合わせを実行するプロンプト・ラボにも移動できますが、今回は LLM モデルのチューニングを行う「ラベル付きデータを使用したファウンデーション・モデルの調整」を選択します:
2024092512


最初にチューニングそのものに適当な名称、説明、タグを付与し「作成」ボタンをクリックします:
2024092513


するとチューニングのアセットが作られ、そのチューニング内容を指定する画面になります。まずはチューニング元になる LLM (言語モデル)を選択します。「基礎モデルの選択」ボタンをクリックします:
2024092514


ここで選択できる LLM の一覧が表示されます。2024-09-25 時点ではここで選択できる LLM は "fran-t5-xl-3b" か "granite-13b-instruct-v2" のいずれかでした(これを知っていたので、上述のプロンプトでも granite-13b-instruct-v2 を使ってプロンプトを試していました)。ここから1つ選んでチューニングするわけですが、今回は IBM の "granite-13b-instruct-v2" を選択しました:
2024092515

※実際のチューニングではここでベースとなる LLM を何にするかによってもチューニングの結果は異なることになります。一般的にはここで選択する LLM は「プロンプトエンジニアリングを試した時点で相性の良さそうな LLM を選ぶ」のが良いとされています。


選択した LLM の説明文が表示されます。これで問題なければ「選択」をクリックします:
2024091209


チューニング元 LLM の選択ができました。すると画面右に「トレーニング・データの追加」と書かれたファイルアップロード画面が表示されます。ここで追加用チューニングデータを指定してチューニングを行うことができます。追加用チューニングデータのフォーマットが分からない場合は(青枠の)「テンプレートのプレビュー」ボタンをクリックします:
2024092516


「テンプレートのプレビュー」をクリックし、"JSON" タブを選択すると以下のような画面が表示されます。"input" と "output" の要素を含む JSON が配列になっていて、この input にプロンプト例、output に問い合わせ結果例が含まれているようなデータを作る、ということのようでした(最大で 10000 サンプル、200MB。これ以外に配列の "[" , "]" を抜いた JSONL 型のデータも使えるようです):
2024092517


このようなフォーマットでチューニング用データを集めて指定してチューニング、、を行うことになります。今回は為替計算を正しく行ってもらうことが目的なので、以下のようなデータを用意してみました:
[
  {
    "input": "1 ドルは 140 円です。 10 ドルは何円ですか?",
    "output": "1 ドルが 140 円なので、 10 ドルは約 140 * 10 = 1400 円です。"
  },
  {
    "input": "1 ドルは 140 円です。 20 ドルは何円ですか?",
    "output": "1 ドルが 140 円なので、 20 ドルは約 140 * 20 = 2800 円です。"
  },
  {
    "input": "1 ドルは 140 円です。 30 ドルは何円ですか?",
    "output": "1 ドルが 140 円なので、 30 ドルは約 140 * 30 = 4200 円です。"
  },
  :
  :

為替のレートを与えた時の「〇〇ドルは何円ですか?」という質問に答えられるようにするためのインプットとアウトプットのペアを大量に用意してチューニングし、実際のレートと質問を与えた時に(プロンプトで工夫しなくても)正しく答えることができるようにするための JSON 配列型の学習データを用意しました(実際には異なる計算もできるような例が含まれています。興味ある方は全部見てください)。

この架空のデータファイル(training-fx.json)をトレーニングデータとして公開することにします。LLM チューニングを体験する目的で、直接ダウンロードして使っていただいても構いません:
https://raw.githubusercontent.com/dotnsf/watsonx.ai-tuning/refs/heads/main/training-fx.json

そして Tuning Studio 画面の「参照」ボタンから、用意した training-fx.json ファイルを指定してアップロードします:
2024092518


アップロードが完了すると「トレーニングデータの追加」の準備はできたことになります:
2024092519


必要であれば「パラメーターの構成」ボタンをクリックして、チューニング時のパラメータを変更します:
2024092520


パラメータを調整する画面が表示されます。必要に応じてパラメータを調整して「保存」します(必要なければ「キャンセル」します):
2024092521


最後にプロンプトの初期化内容を指示します。今回の場合は「与えられた条件から計算して額を答えてほしい」ことが分かっているので、初期化方法に「テキスト」、その内容には「以下は入力された質問に対する出力のサンプルです。サンプルと同様に与えられた質問に対する答を計算して簡潔に答えてください。」と入力します:
2024092522


更に下にスクロールするとタスクの種類を指定する箇所があります。今回の例では「計算して答える」という「プロンプトに含まれない情報を生成する」ものなので、「生成」を選択します。 ここまで選択すると画面右下の「チューニングの開始」ボタンが有効になります。 ここをクリックしてチューニングを開始します:
2024092523


このような画面になってチューニングが開始されます。ここからはチューニングが完了するまで、ひたすら待ちます:
2024092524


チューニングが完了すると以下のような画面になります(先ほどのデータでは 16 分でした)。一般的には(表示されている)損失関数は右肩下がりになるのがいいと言われていますが、ちょっと
2024092525


このチューニングした LLM を実際に使えるようにするにはデプロイする必要があります。画面下部(見えない場合は下にスクロール)の「新規デプロイメント」をクリックします:
2024092526


名前(必須)と説明等を入力します。続きがあるので下にスクロールします:
2024092527


展開コンテナは「このプロジェクト」を選択し、デプロイメント・サービス提供社名を(適当に)入力します。最後に「作成」ボタンをクリックしてデプロイを開始します:
2024092528


少し待つと以下のような画面に切り替わります。このデプロイメントのリンクをクリックします:
2024092529


デプロイメントの様子が分かります。デプロイメントが完了するまで少し時間がかかります。ステータスが「進行中」から「デプロイ済み」になるまで待ちます:
2024092530


「デプロイ済み」になれば、チューニングした LLM はデプロイされ、実際に使える状態になったことを意味します:
2024092531


【チューニング済み LLM を使う】
チューニングされた LLM がデプロイ済みになっていれば、外部からの API 呼び出しや、プロンプトラボなどから実際に使うことができます。先ほどのページの「API リファレンス」タブにはエンドポイント URL や cURL コマンドなどで使う場合のコマンドが表示されています。 ここではプロンプトラボから試す方法を紹介します。といっても「Prompt Lab で開く」ボタンをクリックするだけです:
2024092532


するとプロンプトラボの画面が表示されます。この時、LLM は自分がチューニングしたモデルになっていることを確認します:
2024092533

基盤モデルの一覧を表示した場合でも、このチューニング済みモデルが表示され、選択可能になっていることを確認してください:
2024092534


改めてプロンプトラボを「フリーフォーム」で、LLM は自分がチューニングしてデプロイしたモデルを選択した状態にします:
2024092535


そしてプロンプトとして以下のように入力します(このブログを書いている最中の実際のドル円相場の数字です):
与えられた質問に対する答を計算して簡潔に答えてください。

1 ドルは 144.3 円です。 10 ドルは何円ですか?
2024092536


もともとは何も計算してくれなかったプロンプトです。これを「生成」します:
2024092537


・・・うーん、まだ 100 点とはいえないか。想定していた答とは違いますが、とりあえず計算はしてくれるようになりました。このプロンプトはチューニング前だと正しい数値を返すこともなかったものなので、いくらかの進展はあったと言えるんじゃないかと思いますが、うーん。。。

まあ、今回のチューニングデータは説明目的でサイズもまだまだ小さく、サンプルとしては不十分であった可能性は高いものです。もうちょっと色んなパターンに対応したり、数を増やしたりすることで制度上がるんだろうか? この辺りはまだ未検証です。 ただ Watsonx.AI の LLM チューニングがどういったものなのか、そのための Tuning Studio をどう使うのか、という点ではある程度理解しやすいものではなかったか、と思っています。


【チューニングのコスト】
Watsonx.ai の LLM を Tuningu Studio でチューニングする際のコストも気になるところです。チューニングコストは与えるサンプルデータ量によっても変わるので事前に具体的なコストを予測計算するのは難しいのですが、以下にチューニングコストの考え方・計算方法についてまとめておきます。 

なお、以下の説明に使っている数字部分は 2024 年 9 月 25 日時点のものであることに注意してください。実際にコストを見積もる際には自分の契約しているプランや為替レートを含めた最新情報・最新価格を参照して再度計算するべきであることに留意ください。

まず覚えておいていただきたいことは「LLM チューニングの価格は CUH(Capacity Unit / Hour : 容量単位時間)によって決まる」ということです。

要は、
 1 どのようなスペックのサーバーを
 2 どのくらいの時間使って
チューニングしたか、という計算能力と計算量によって計算で決まる、という意味です。

どのようなスペックのサーバーを使って LLM チューニングしているか、という情報はこちらを参照して調べます:
https://dataplatform.cloud.ibm.com/docs/content/wsj/getting-started/wml-plans.html?context=wx&audience=wdp#cuh-metering


この中の「資産タイプ別 CUH 消費率」という項目内の「基礎モデルの調整実験(watsonxのみ)」と書かれた所が LLM チューニング時に使われるサーバーで、その CUH は(2024/09/25 時点で)43 となっています:
2024091211


この 43 というのが(1時間の)チューニング時に利用されるサーバーのスペックを表す数字だと思ってください。この数字だけだとどんなスペックか想像しにくいですが、他の数字よりもかなり大きめの数字になっているので、それなりのサーバーが使われる、という理解でいいと思います。 ただあくまで1時間の数字です。チューニングに必要な時間が 30 分ならこの半分に、2時間ならこの倍になります。

そして、1 CUH あたりでいくらかかるのか、というのは Watsonx.AI 作成時の Watson Machine Learning サービスインスタンスのプランによって変わります。例えば Essential Plan の場合、1CUH あたりの額は(2024/09/25 時点で)75.7146 円となっています:
2024091201


これらの数値より、仮にチューニングにかかる時間が1時間であったとすると、そのチューニングコス
  1(H) * 43(CUH) * 75.7146 (JPY) = 3255.7278 (円)
ということになります。 あくまで 2024/09/25 時点の Essential Plan での価格および為替レートに基づいた1時間あたりの数字なので、実際の価格は自分のプラン、チューニングデータ量に合わせて再度計算が必要になる点に注意してください。

そして、では今回の(上述のチューニングデータを使っての)チューニングでどれだけの CUH を消費したか、という数値はチューニング後のプロジェクト画面に反映されています。私が実際に上記データを使ってチューニングした結果は以下のようになっていました:
2024092538


11.8 CUH とのことでした(あくまで私が試した時の結果です)。これはつまり、
 11.8(CUH) * 75.7146 (JPY) = 893.43228(円)
ということになりました。 実際、このチューニングでは不十分だったと思っているので、あまり参考にならない数字かもしれませんが、「試しに1回軽くチューニングすると千円くらい」というのが1つの目安に・・・なりますかね? 繰り返しますが、あくまで目安の数字であることに注意してください。


【まとめ】
今の時点でまだあまり外部情報のない Tuningu Studio を使ってみました。2024-09-25 現在ではプロンプト・チューニングだけが提供されている状況で、「膨大なトークン数を消費してコストがかさむのを防ぐためのチューニング」が主目的になるものと考えられます。 こういうコスト削減方法もあるんですねー。




Node-RED 環境に標準で提供されている標準ノードの1つに template ノードがあります(下図赤枠):
2024091001


この template ノードは次のノードへ送る msg.payload (など)の値をテンプレートをベースに、一部を変数化して生成するノードです。HTML の一部を変数化して扱いたいとか、最近だと生成 AI のプロンプトを作成する際のテンプレートとしても便利に使えるので、今後活用の機会があるかもしれません。

ところで、この template ノードの標準記法として設定されているのが mastache (「マスタッシュ」)記法というものです:
2024091002


この mastache 記法でどんなことができるのか調べてみました。以下で3つの方法を紹介しますが、とりあえず以下のようなシンプルなフローを作っておきます。inject -> function -> template -> debug の各ノードを一直線につないだだけのシンプルなフローです:
2024091003


このうち inject ノードは単に処理をスタートさせるためのスイッチとしてだけ使うのであまり意味はありません(このノードの左にある部分をクリックするとフローが実行されます)。また最後の debug ノードはデフォルトで msg.payload の内容を画面右側のデバッグウィンドウに出力するのですが、これもデフォルトのままで大丈夫。つまりこの2つは特に設定変更しません。

そして2つ目の function ノードをダブルクリックして編集状態にして、以下の内容を記載します:
msg = {
  payload: {
    value0: "0",
    value1: 1,
    value2: "<b>2.0</b>"
  },
  value1: 1,
  value2: 2.2,
  array1: [ 2, 3, 5, 7, 11, 13, 17, 19 ],
  array2: [
    { name: "apple" },
    { name: "orange" },
    { name: "grape" }
  ]
}
return msg;
2024091004


この3つのノード(inject, function, debug)はこの後特に変更しません。 以下で mastache 記法にちうて3つの方法を紹介しますが、全て上述の内容の msg を受け取る仮定として、 template ノードのみ変更して動作を確認していきます。


【テンプレート内に1つの値を指定する場合】
まずはシンプルな例を紹介します。template ノードを開き、以下の内容を記入してください:
payload.value0 = {{payload.value0}}
payload.value1 = {{payload.value1}}
payload.value2 = {{payload.value2}}

value1 = {{value1}}
value2 = {{value2}}
2024091101


そしてこの状態でフローをデプロイし、inject ノードをクリックしてフローを実行します。すると画面右のデバッグペインには以下のように表示されます:
payload.value0 = 0
payload.value1 = 1
payload.value2 = &lt;b&gt;2.0&lt;&#x2F;b&gt;

value1 = 1
value2 = 2.2

2024091102


msg オブジェクトの一部が変数としてテンプレートの中を埋めて出力されていることがわかると思います。普通の変数であれば {{ と }} でメッセージ内の値を指定することで表示できそうですね。

ちょっと気になる点があるとすれば "payload.value2" の値でしょうか。そのまま HTML で使えるように一部文字がエスケープされた状態になっています。このエスケープが不要である場合は {{ と }} ではなく、 {{{ と }}} で囲うようにするだけです。試しに template の内容を以下のように書き換えてみます:
payload.value0 = {{payload.value0}}
payload.value1 = {{payload.value1}}
payload.value2 = {{{payload.value2}}}

value1 = {{value1}}
value2 = {{value2}}
2024091103


そしてフローを再デプロイし、inject ノードで処理を実行すると、今度は文字列がエスケープされずにそのまま表示されます:
2024091104


【テンプレート内に配列値を指定する場合】
テンプレート内に1つ1つの値を個別に直接指定する方法は上述のような方法でした。では「配列」を扱う場合、テンプレート内にどのように記述するべきでしょうか? 今回の場合だと msg.array1 が配列になっているので、これをどのように列挙するか? というケースを考えます。

その答がこれです。template の内容を以下のように変更します:
{{#array1}}
{{.}}
{{/array1}}

2024091105


この template でデプロイして inject ノードをクリックして処理が実行されると、デバッグ画面に下図のような結果(msg.array1 の配列の内容が1行ずつ表示)が出力されます:
2024091106


このようにテンプレート内で {{#配列名}} と {{/配列名}} で括り、その中で {{.}} と指定すると配列内の各要素が表示されました。


【テンプレート内に JSON 配列を指定する場合】
最後にテンプレート内で「 JSON オブジェクトの配列」を扱う場合を考えます。 今回の場合だと msg.array2 が JSON 配列になっているので、これをどのように列挙するか? というケースを考えます。

その答がこれです。template の内容を以下のように変更します:
{{#array2}}
{{name}}
{{/array2}}

2024091107



この template でデプロイして inject ノードをクリックして処理が実行されると、デバッグ画面に下図のような結果(msg.array2 の JSON 配列の name 要素が1行ずつ表示)が出力されます:

2024091108

このようにテンプレート内で {{#配列名}} と {{/配列名}} で括り、その中で {{要素名}} と指定すると JSON 配列内の特定の要素を表示できました。


【まとめ】
Node-RED のテンプレートとしての template ノードはあまり使う機会がなかったのですが、うまく使うとプロンプトエンジニアリングのプロンプト作り(サンプル作り)のテンプレートに使えそうだな、という印象を受けています。この方法でサンプルを列挙するようなプロンプトを生成して、その結果を拙作の watsonxai ノードhttps://flows.nodered.org/node/node-red-contrib-dotnsf-watsonxai)に渡して問い合わせする、といった使い方ができるのではないかと考えています:
2024091109


mastache 記法はここで紹介した内容以外にもいろいろな記法があります。詳しくは以下のページを参照ください。とてもわかりやすく、自分の理解にも使わせていただいたページです。


【参照】
mustache記法について簡単にまとめてみた

CouchDBCloudant というデータベースに対してブラウザの JavaScript から(API サーバーなどを使わずに)直接アクセスできるような SDK を作ってみました。


CouchDB CRUD SDK について】
CouchDB は現在は Apache CouchDB としてオープンソースで開発されている JSON ドキュメント型の NoSQL データベースです。そして Cloudant はその CouchDB を IBM からのサポート付きで IBM Cloud から提供されているものです。要するに CouchDB と Cloudant は実体としては同じものです。ということもあって、以下では両方を指す言葉として "CouchDB" という表現をします(Cloudant を使う場合は "Cloudant" と読み替えてください):
2024090900


CouchDB には REST API が提供されています。したがって CORS に注意する必要はありますが、ウェブブラウザの JavaScript とこの REST API だけでデータベースやドキュメントの読み書き更新削除(=CRUD、Create Read Update Delete)といった処理そのものは可能です。ただ REST API を実行するのはパスやパラメータの指定で直感的に分かりにくい点があったり、検索方法や CouchDB の特徴ともいえる添付ファイル対応(バイナリデータ対応)の REST API は更に複雑だったりするので、私自身も使うたびに調べる必要に迫られたりと、少々使いにくい点があると感じていました。そういった使い勝手の点も含めて分かりやすい JavaScript での SDK を提供できないかと思いついて作ってみたものです。

作成した CouchDB CRUD SDK 自体は README.md 含めて GitHub で公開しています:
https://github.com/dotnsf/couchdb-crud-sdk


SDK の説明(英語)も GitHub に含まれる SDK.md で紹介していますが、せっかくなのでこのブログでは日本語で使い方を紹介します。


CouchDB CRUD SDK を使う前提条件】
前提条件というか、当然条件というか、使う対象となる CouchDB(Cloudant) サーバーが必要です。この CouchDB サーバーはインターネットからアクセスできる必要はありませんが、クライアントとなるウェブブラウザを使う PC がインターネットに接続していない場合は後述の CDN が使えないため、SDK の JavaScript ファイルをあらかじめダウンロードするなどしておく必要があります。

またここで用意する CouchDB サーバーはユーザー ID とパスワードで(ベーシック認証で)アクセスできるように構築されている必要があります。API キーを使う方法には未対応です。

自由に使える CouchDB サーバーを持っていない場合に備えて、以下では2つの方法で CouchDB サーバーを用意する方法を紹介します:

(1)docker のコンテナとして用意する
自分の PC に docker をインストールしてある場合であれば以下のコマンドを実行するだけで CouchDB サーバーを localhost:5984 で(ユーザーID=user、パスワード=pass で)動かすことができます:
$ docker run -d --name couchdb -p 5984:5984 -e COUCHDB_USER=user -e COUCHDB_PASSWORD=pass

(2)IBM Cloud のアカウントをお持ちであれば、lite plan という無料枠の範囲内で使える Cloudant サーバーを1アカウントにつき1つだけ使うことができます。ちなみに Cloudant の lite plan で使える容量は 1GB です(その他の条件は下図参照)。バイナリデータなどの膨大な容量のファイルを添付したりしなければ、そこそこ使えるサイズだと思っています:
2024090800


IBM Cloud 内の Cloudant サーバーを作成した場合のデータベースサーバーのホスト名、ユーザーID、パスワードなどの情報は「サービス資格情報」タブ内の下図の変数として確認することができます:
2024090802


なお、この方法で Cloudant サーバーを用意する場合は認証方法として "IAM and legacy credential" を選択してください(この "legacy credential" がベーシック認証です。"IAM" のみだとベーシック認証には非対応です)
2024090801


IBM Cloud のアカウントをこれから作る場合はこちらから登録してください(アカウント作成時にクレジットカードの登録が必要ですが、有料サービスを使わなければ課金対象にはなりません)。

もう1つの前提条件として外部の JavaScript から CouchDB サーバーを利用できるように CORS を正しく設定している必要があります。CouchDB の設定画面内で CORS タブから CORS が enalbed になっていることを確認してください。また CORS 対象のオリジンについては "All domains" を選択するか、"Restrict to specific domains" を選択した上で、この後動かすことになるウェブページのオリジン(URL の "スキーマ://ホスト:ポート" の部分)を指定してください。ここが正しく設定できていないと CORS の制約にかかって JavaScript SDK が正しく実行できなくなります:
2024090802


CouchDB CRUD SDK のロードと初期化】
実際にデータの読み書きをする前に SDK ライブラリをロードして初期化する必要があります。その流れを説明します:

まずブラウザ内に以下の1行を加えて、CDN の SDK ライブラリをロードします:
<script src="https://dotnsf.github.io/couchdb-crud-sdk/couchdb-crud-sdk.js"></script>

インターネットに接続していない環境で使う場合は上記 URL から couchdb-crud-sdk.js をあらかじめダウンロードして、HTML と同じフォルダにコピーするなどして(その場合は src="./couchdb-crud-sdk.js" と指定)対応してください。

次にユーザー ID、パスワード、CouchDB サーバーのベース URL を指定して CouchDB_CRUD_SDK クラスのインスタンスを初期化します:
var cdb = new CouchDB_CRUD_SDK( username, password, base_url );

これでインスタンス変数 cdb の初期化が完了しました。この変数を使うことで CouchDB サーバー内のデータを読み書きできます。


【CouchDB CRUD SDK を使ったデータベースの作成と削除と一覧取得】
上述の初期化まで完了していればインスタンス変数(cdb)のメソッドでリモートの CouchDB に対する各種処理が実行できます。例えば CouchDB のデータベースに関しては以下のようなメソッドが用意されています:
//. データベース一覧取得
var result0 = await cdb.readAllDbs();

//. データベース新規作成
var result1 = await cdb.createDb( 'newdb' );

//. データベース削除
var result2 = await cdb.deleteDb( 'newdb' );

後述のドキュメント向けメソッドについてもいえることですが、メソッドの実行結果は成功した場合は
{
  status: true,
  result: (実行した結果のオブジェクトや配列)
}

といった JSON が返されます。また失敗した場合は
{
  status: false,
  error: (失敗した原因の内容を示すオブジェクトや文字列)
}

という JSON になります。status の true/false で成功か失敗かを判断し、成功の場合は(必要であれば)result の内容を参照、失敗の場合は error の内容に失敗の理由が記載されているのでそれぞれ参照して処理することができます。 なお、cdb のメソッドは全て非同期(async)関数として定義されている点に注意してください。


【CouchDB CRUD SDK を使ったドキュメントの操作】
次にデータベース内のドキュメントに関しては以下のようなメソッドが用意されています:
//. データベース内のドキュメント一覧取得
var result3 = await cdb.readAllDocs( 'db' );

//. ドキュメント新規作成
var result4 = await cdb.createDoc( 'db', doc );

//. ドキュメント一件取得
var result5 = await cdb.readDoc( 'db', 'doc_id' );

//. ドキュメント更新
var result6 = await cdb.updateDoc( 'db', 'doc_id', doc );

//. ドキュメント削除
var result7 = await cdb.deleteDoc( 'db', 'doc_id' );

createDoc や updateDoc のパラメータで指定されている doc オブジェクトは
{
  _id: 'doc_id',
  name: "ジュース",
  price: 120
}

といった JSON オブジェクトです(_id の値はいわゆるドキュメント ID です。新規作成時であれば指定されていてもされていなくても構いません、指定がない場合は自動的に付与されます)。

CouchDB の特徴的な機能を使ったメソッドについても紹介しておきます。例えば以下のようなメソッドも用意されています:
//. 特定ドキュメントの全リビジョン取得
var result8 = await cdb.readAllRevisions( 'db', 'doc_id' );

//. 添付ファイルをドキュメントとして保存する
var result9 = await cdb.saveFile( 'db', 'doc_id', 'selector', 'filename' );

上は特定の ID ('doc_id')を持ったドキュメントの全リビジョンを取得するメソッドです。CouchDB はドキュメントの更新履歴が全て(自動的に)リビジョンとして記録されています。つまりドキュメントを新規作成し(リビジョン1)、一度更新して(リビジョン2)、更に更新(リビジョン3)した場合、最新データはリビジョン3ですが、過去のリビジョンも全て記録されていて、取り出すことができます。上の readAllRevisions() メソッドは特定ドキュメントの全リビジョンを一度の取り出すメソッドです。

下は添付ファイルを JSON データの一部として記録する場合のメソッドです。例えば
<input type="file" id="attachment_file"/>

のような HTLM 要素を使って添付ファイルを読み込もうとした場合であれば、'selector' は "#attachment_file" となります(jQuery などのセレクタだと思ってください)。'filename' は指定されていればそのファイル名で保存されます('filename' 指定がなかった場合は実際のファイル名で保存されます)。なお 'doc_id' が指定されている場合はその ID のドキュメントを更新し、'doc_id' が指定されていない場合は新規にドキュメントを作成します。 この関数を使って(バイナリデータなどの)添付ファイルもブラウザの JavaScript でリモートデータベースに格納/更新できるようになります。またこの添付ファイルの更新記録もリビジョンとして記録されます。

一応ここに挙げたメソッドで一通りの読み書き更新削除まではカバーしていると思いますが、その他全てのメソッドを(最新情報として)参照したい場合はこちらを参照ください(英語です):
https://github.com/dotnsf/couchdb-crud-sdk/blob/main/SDK.md


【CouchDB CRUD SDK のサンプル】
この CouchDB CRUD SDK を比較的汎用的に使ったサンプルアプリケーションを GitHub Pages に用意しておきました。docker コンテナであっても CouchDB/Cloudant 環境があれば試しにアクセスすることができます:
https://dotnsf.github.io/couchdb-crud-sdk/


上記 URL にアクセスすると以下のような画面が表示されます:
2024090901


上部に3つのテキストフィールドがあり、左からユーザーID、パスワード、CouchDB の URL (docker であれば http://localhost:5984 など)を入力し、最後に Login ボタンをクリックします:
2024090902


入力した内容が正しい場合は指定された CouchDB にアクセスし、画面左にデータベースの一覧が表示されます。またログインフィールドが画面から消えます(ログインフィールドが残っている場合はログインに失敗しています)。この画面から新しくデータベースを作成(Create DB)することもできます:
2024090903


データベースを1つ選択すると、そのデータベース内のドキュメント一覧が画面右側に表示されます。この部分からは選択したデータベースの削除(Delete DB)、ドキュメントの新規作成(Create Doc)や、既存ドキュメントの参照(Show Doc)、変更(Edit Doc)、削除(Delete Doc)といったアクションを実行できます:
2024090904


以下は "Create Doc" ボタンをクリックしてドキュメントを新規作成している画面です:
2024090905


ドキュメントの内容は JSON 形式であればどのようなフォーマットでも指定可能です。最後に "Save" で保存します:
2024090906


(わかりにくいかもしれませんが)新たに1つドキュメントが追加されました(赤枠):
2024090907


このドキュメントの横にある "Show Doc" ボタンをクリックするとドキュメントの内容が表示されます。実際に入力した JSON の内容に加えて、"_id" や "_rev" といったキーとその値が追加されているのがわかります。これらが(自動的に付与された)ドキュメント ID や、そのリビジョンです。同様にして編集や削除も可能です:
2024090908


このサンプルアプリでできることはこの程度ですが、ブラウザの JavaScript だけでリモートのデータベース内を読み書きできていることがわかると思います。もちろんデータベースを読み書きする REST API があれば(CORS の設定もできていれば)普通にできることではあるんですが、REST API を意識しなくても関数を呼び出すだけでできるようにしたのはまあまあ便利かなと自分でも思っています。API サーバーなしでも動くフロントエンドアプリケーションの SDK という位置付けであり、それが実現できているので現に GitHub Pages でこのサンプルが作れているわけです。

サンプルアプリの内容を見たい場合はこちらを参照ください(index.html, viewer.js, viewer.css の3つのファイルで作られているウェブアプリケーションです):
https://github.com/dotnsf/couchdb-crud-sdk/tree/main/docs


【最後に】
・・・というものを作って公開してみました。ローカルストレージではなくリモートデータベースをブラウザの JavaScript だけで(比較的簡単に)操作できることと、CouchDB の持つリビジョン管理機能や添付ファイル管理まで含めて操作できる点が特徴的なライブラリかな、と思っています。

CouchDB や Cloudant といったデータベースに馴染みのない人もいるかもしれませんが、この記事をきっかけに知ったり興味持ったりしてもらうことがあれば嬉しいです。


このページのトップヘ