サイオステクノロジー(Tech.Lab)のブログ - TECH PLAY

TECH PLAY

サイオステクノロジー(Tech.Lab)

サイオステクノロジー(Tech.Lab) の技術ブログ

672

ホワイトペーパーを公開しています! Elastic PortalではElasticに関するニュースやTechブログ等を公開しています。 先日、「Elasticsearchを使った簡易RAGアプリケーションの作成」というホワイトペーパーを作成しました。この内容は、2024年9月から12月に Elastic Portal内のTechブログ で紹介した情報をまとめたものです。 Elasticsearchのインデックス作成から、RAGアプリケーションの作成方法まで解説しています。 ホワイトペーパーは、 こちらよりダウンロード できます。ElasticsearchやRAGに興味がある方は、ぜひご覧ください。 また、ホワイトペーパーで紹介したRAGアプリケーションのソースコードは、下記のGitHubリポジトリで公開しています。こちらもぜひ参考にしてください。 [GitHubリポジトリリンク] https://github.com/sios-elastic-tech/white-papers/tree/main/2024-12-simple-rag 次回のブログでは、ホワイトペーパーの内容に関連した技術的な情報をさらに深掘りして紹介する予定です。 Elastic Portal で公開をお待ちください。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Elasticsearchを使った簡易RAGアプリケーションの作成 first appeared on SIOS Tech. Lab .
第1章: Difyとは? 生成AIが注目され始めて早数年。ChatGPTの登場が世間を賑わしたのは記憶に新しいですね。その後も、DALL-EやGPT-4など、様々な生成AIが登場しました。これらの生成AIは、それぞれの特徴を持ち、様々な用途に活用されています。ChatGPTなどの生成AIの技術は、LLM(Large Language Model)と呼ばれ、膨大なテキストデータから学習し、高度な言語理解を実現する技術であり、自然言語処理の分野で大きな注目を集めています。 そして、LLMを利用したアプリも登場しました。特に大きな注目を集めているのはRAGやAIエージェントでしょう。RAGは、外部のデータベースや文書から関連情報を取得して回答の精度を向上させる技術、そしてAIエージェントはLLMを活用してタスクの実行や意思決定を自律的に行う仕組みです。これらのアプリは、LLMを利用して、高度な自然言語処理を実現しています。 今までは、このようなLLMアプリを開発するためには、Pythonなどのプログラミング言語を用いて、ゼロから開発することが一般的でした。しかし、Difyは、このようなLLMアプリを簡単に開発できるオープンソースのプラットフォームです。Difyを利用することで、プログラミングの知識がなくても、簡単にLLMアプリを開発することができます。まさに「AIの民主化」を実現し、誰もがAIを活用できるようになる、それがDifyなのです。 第2章: Difyの特徴 Difyには、以下のような特徴があります。 簡単な操作性 何と言ってもDifyの最大の特徴はこれに尽きるでしょう。プログラミングをしなくても、ブラウザ上のGUIからポチポチするだけで、RAGやAIエージェントを簡単に作成することができます。これにより、プログラミングの知識がないユーザーでも、簡単にLLMアプリを開発することができます。 豊富な機能 RAGやAIエージェントを作成できるのはもちろん、Difyには多彩な機能が搭載されています。例えば、OpenAIだけでなく、GeminiやCohereなど、さまざまなAIモデルを利用することができます。また、RAGなどでドキュメントを検索する技術であるベクトル検索やリランカーにも対応しています。 さらに特筆すべきは、Difyが提供するワークフローの豊富なブロックです。これらのブロックには、条件分岐や繰り返し処理、APIの呼び出し、データ変換、ファイル操作など、あらゆる操作を実現するためのものが揃っています。例えば、ユーザーの入力に応じて異なるAIモデルを切り替える分岐処理や、検索結果を整理して特定の形式に変換するデータ処理を簡単に組み込むことができます。この柔軟なブロックの仕組みにより、業務の自動化や高度なAIアプリケーションを、プログラミングの知識がなくても直感的に構築することが可能です。 Difyは、その強力なブロック機能によって、ユーザーのニーズに応じた自由自在なアプリケーション作成をサポートします。まさに、制限のないAIアプリケーション構築のプラットフォームと言えるでしょう。 様々な実行環境 Difyには、コミュニティ版、クラウドサービス、Premiumの3つの実行環境が用意されています。   ■ コミュニティ版 ユーザーが用意したサーバーやローカルPCなどで実行できる環境です。ソースコードから起動することも、Docker Composeを使って起動することも可能です。機能の制限はなく、無償で利用できます(別途インフラの費用は発生します)。 ■ クラウドサービス Difyを提供するベンダーで管理するインフラ上で動作するサービスです。月額利用料を支払うだけで、コミュニティ版のようにDifyを実行するインフラを用意する必要がなく、手軽にDifyを利用できます。また、コミュニティ版のようにインフラの管理や運用を気にする必要がありません。Sandbox、PROFESSIONAL、TEAM、ENTERPRISEの4つのプランがあり、それぞれのプランに応じて機能や利用料金が異なります。 ■ Premium AWSのAMI(Amazonマシンイメージ)で提供され、ワンクリックでEC2に展開することができます。コミュニティ版のように機能に制限はなく、さらにコミュニティ版よりももっと簡単に導入することができます。AWSを契約しているのであれば、検討の価値があるでしょう。 では、それぞれの詳細な説明を見ていきましょう。 第3章: Difyの実行環境 Difyの実行環境について、詳細に説明します。 コミュニティ版 以下の方法で簡単にDocker Composeを使ってDifyを起動することができます。Docker及びDocker Comoposeがインストールされている必要があります。 $ git clone https://github.com/langgenius/dify.git $ cd dify/docker $ cp .env.example .env $ docker compose up -d ソースコードからも起動が可能です。開発する場合は便利ですが、まずはDocker Composeを使って起動して、Difyの操作感を体験してみるのがおすすめです。 クラウドサービス クラウドサービス版のそれぞれのプランの機能や料金は以下の通りです(2025年1月17日現在)。 機能 Sandbox Professional ($59/月) Team ($159/月) Enterprise (要問合せ) メッセージクレジット 200 メッセージ 5,000 メッセージ/月 10,000 メッセージ/月 無制限 選択可能なモデル OpenAI/Anthropic/Llama2/Azure OpenAI/Hugging Face/Replicate 開発チームメンバー数 1 3 無制限 無制限 作成可能なアプリの数 10 50 無制限 無制限 ベクトルストレージの容量 5MB 200MB 1GB 無制限 アップロードできるドキュメント数 50 500 1000 無制限 ドキュメント一括アップロード 利用不可 対応 対応 対応 メッセージリクエスト 500/日 無制限 無制限 無制限 注釈返信の制限 10 2000 5000 無制限 ログの履歴 15日間 無制限 無制限 無制限 カスタムツール 利用不可 10 無制限 無制限 サポート コミュニティによるサポート メールによるサポート 優先メール&チャットサポート 専用のSlackチャンネル、電話、メールによるサポート ロゴの変更 変更不可 可能 対応 対応   それぞれの項目については以下のとおりです。 ■ メッセージクレジット メッセージクレジットとは、LLMへリクエストを送るときに必要なものです。例えば、gpt-3.5-turboを使用した1回のAIとのやり取りの場合は1クレジット表示されます。gpt-4の場合は20クレジットとなります。例えば、Professionalプランでは、5,000 メッセージ/月なので、gpt-4の場合は、250回のAIとのやり取りが可能です。もし、メッセージクレジットを使い切った場合には、OpenAIやAzure OpenAI Serviceを契約し、APIキーを取得してDifyに登録する、もしくは翌月になったらまた5,000メッセージ/月が利用できるようになります。 つまり、メッセージクレジットはDify側が無料もしくは月額利用料の範囲内で提供してくれるLLMの利用回数のことです。 注意いただきたいのがSandboxプランですが、これは「200メッセージ/月」ではなく、Sandboxプラン全体で「200メッセージ」です。つまり、200メッセージを使い切ったら、そのSandboxプランでは、翌月になってもLLMを利用することができません。この場合も同様に、OpenAIやAzure OpenAI Serviceを契約し、APIキーを取得してDifyに登録する必要があります。 ■ 選択可能なモデル Difyでは、OpenAIやHugging Face、Replicateなど、さまざまなAIモデルを利用することができます。 ■ 開発チームメンバー数 Difyの管理画面にアクセスして、開発を行うメンバーの登録上限数になります。Difyの管理画面から公開したアプリの利用ユーザーではないことに注意してください。そもそも、Difyにはアプリを利用するユーザーを事前に登録する必要はありません(Difyにはアプリにアクセスするユーザーを認証する機能はなく、その仕組みはDifyとは別に用意してあげる必要があります)。 ■ 作成可能なアプリの数 Difyの管理画面にアクセスして、作成できるアプリの数になります。 ■ ベクトルストレージの容量 Difyに登録したドキュメントがチャンク化され、ベクトル化されたデータが保存される領域のことです。ベクトルストレージの容量が不足すると、新しいドキュメントを登録することができなくなります。 ■ アップロードできるドキュメント数 Difyに登録できるドキュメントの数になります。ドキュメントは、テキストファイルやPDFファイルなど、様々な形式で登録することができます。ドキュメントの詳細については後述します。 ■ ドキュメント一括アップロード Difyに登録するドキュメントを一括でアップロードする機能です。この機能を使うことで、手動で1つずつドキュメントを登録する手間を省くことができます。 ■ メッセージリクエスト APIまたはウェブセッションを介したアプリからのすべてのメッセージが含まれます。(LLMリソースの使用量は含まれません) ■ 注釈返信の制限 注釈返信の制限は、その名の通り注釈返信の制限になります。注釈返信とは、チャットボットやチャットフローなどにて、ユーザーの質問から回答する際、OpenAIなどの生成AIに問い合わせて回答するのではなく、あらかじめ登録されたQAのペアをもとに回答する機能です。生成AIへのリクエストがないので、料金も節約でき、回答も早くなります。注釈返信の制限は、そのあらかじめ登録できるQAのペアの数になります(注釈返信については後ほど詳ししく説明します)。 ■ ログの履歴 ユーザーがアプリケーションを操作したログを残す期間になります。Free以外は無期限に残すことができます。 ■ カスタムツール DifyにはAIエージェントに与える標準ツールが多数用意されていますが、それだけでは足りない場合があります。そこで、Difyでは、ユーザーが独自のツールを作成してAIエージェントに組み込むことができます。Web APIの形式で提供されているAPIのOpenAPI仕様書をアップロードすることで、Difyのツールとして利用することができます。そのカスタムツールの登録数の制限です。Freeはカスタムツールを利用することができませんので、AIエージェントを作成する場合は、Freeの利用を慎重に検討してください。 ■ サポート Freeの場合はDiscord等によるコミュニティサポート、それ以上のプランではベンダーによるサポートを受けることができます。 ■ ロゴの変更 アプリ起動時のログ(下図赤枠参照)を変更することができます。 ■ LLM負荷分散 複数のAPIキーを事前に登録しておき、LLMを経のアクセスごとに異なるAPIキーを使ってLLMにアクセスする機能になります。 ■ SSO シングルサインオンを行う機能です。Difyはアプリに対する認証の機能を持っていません。外部のIdPによる認証を行う必要があります。SSOを利用することで、Difyに登録されたユーザーが、外部のIdPで認証を行い、Difyにセキュアにアクセスすることができます。 現在のところ、この機能を使うことができるのはEnterpriseプランのみです。SAMLとOpenID Connectに対応しています。 Premium Premiumは、AWSのAMI(Amazonマシンイメージ)で提供され、ワンクリックでEC2に展開することができます。コミュニティ版のように機能に制限はなく、さらにコミュニティ版よりももっと簡単に導入することができます。AWSを契約しているのであれば、検討の価値があるでしょう。 単一のEC2インスタンス上に展開されますので、冗長化やセキュリティなどは自分で考慮する必要があります。本当にサクッと使えるPoC環境として利用するのが望ましいと考えます。 第4章: 実行環境の選定基準 様々な環境の中で、一体どの環境を使えばいいか迷うと思います。そこで比較表を作成しました。 項目 コミュニティ版 クラウドサービス Premium 機能 すべての機能が無制限に利用可能 エンタープライズプラン以外は機能に制限あり すべての機能が無制限に利用可能 メンテナンス バージョンアップ、セキュリティパッチ等のメンテナンスは管理者が行う必要あり サービス側で実施 バージョンアップ、セキュリティパッチ等のメンテナンスは管理者が行う必要あり データの機密性 オンプレミスやパブリッククラウド上に構築することで、管理者自らがデータの機密性を保持 サービス側で機密性を担保 オンプレミスやパブリッククラウド上に構築することで、管理者自らがデータの機密性を保持 サポート Discordなどによるコミュニティサポート Freeプランはコミュニティサポート、それ以上はベンダーによるサポート メールでのベンダーによるサポート(AWSインフラ部分はAWSサポート) 費用 ソフトウェア利用料は無償(インフラの費用は別途必要) 有料(月額課金) ソフトウェア利用料は無償(AWSの費用は別途必要) スケーラビリティ スケールするためのインフラを用意する必要あり 負荷に応じてクラウド側でスケール スケールするためのインフラを用意する必要あり 以上の比較表を参考にして、自分の目的に合った実行環境を選定してください。 本当にざっくりいいますと、以下のような選定基準になります。 ■ コミュニティ版 メンテナンスに手間をかけてもいいから、データをオンプレミスやパブリッククラウド上に構築して、データの機密性を保ちたい。 ■ クラウドサービス データの機密性よりもメンテナンスを楽にしたい。 ■ Premium AWS持っていて、かつサクッとPoC環境つくりたい。 第5章: アプリケーションの種類 Difyには、様々な種類のアプリケーションを作成することができます。以下に、Difyで作成できる主なアプリケーションの種類を紹介します。 チャットボット Difyを使って作成できる最も基本的なアプリケーションの一つがチャットボットです。複雑な処理が必要なく、ユーザーからの質問に対して適切な回答を返すだけの場合、このチャットボットを使うのが適切です。 チャットフロー チャットボットよりも複雑な処理が必要な場合、チャットフローを使うのが適切です。例えば、質問の内容によって、異なるドキュメントを回答の参照元としたい場合や、外部のAPIを実行するなど独自の処理を入れたい場合は、チャットフローを使うのが適切です。 テキストジェネレーター 入力したプロンプトに基づいて、テキストを生成するアプリです。記事やブログの作成に適しています。一括して複数のテキストを生成する機能もありますので、記事を大量生産する場合に適しています。 エージェント AIエージェントを作成する機能です。プロンプトとツールを与えるだけで簡単にAIエージェントを作成することができます。AIエージェントは、ユーザーの入力に応じて自動的にタスクを実行したり、意思決定を行ったりすることができます。 ツールについては「インターネットを検索する」「QRコードを作成する」といったDify標準のものから、独自のツールを追加することもできます(後述しますが、独自ツールはAPIとして公開されている必要があります)。 ワークフロー LLMを使ったアプリケーションをノーコードで作成できます。例えば、画像にある情報をテキストとして抽出する機能を持つアプリケーションがその一例です。APIとしても公開することが可能ですので、ワークフローで作成したアプリケーションは、他のアプリケーションからも利用することができます。 第6章: Difyのインストール まずは、Difyを動かす環境を用意しないと始まりません。 Difyの実行環境 で紹介したコミュニティ版をインストールします。仮想マシンや自分のPCで動作することができ、ほぼすべての機能を制限なく利用できます(一部SSOなどクラウドサービスのエンタープライズプランでないと使えない機能もあります)。 クラウドサービスのSANDBOXプランも無料で使えます。メッセージクレジットもあるので、OpenAI等の外部のモデルの用意が不要に思えますが、200メッセージクレジットはすぐに使い切ってしまうので、どのみち外部のモデルは必要になります。 さらにSANDBOXプランは、カスタムツールの利用が不可となっております。カスタムツールがないと、AIエージェント作成の際、Difyで標準で用意されているツールのみの利用となり、非常に限定的な用途のAIエージェントしか作成できません。 よって、今回はコミュニティ版をインストールして、Difyを動かす環境を用意します。 ■ コミュニティ版のインストール 自分のPCなどの物理マシンもしくはAzureやAWS上の仮想マシンを用意して、Docker Composeを使ってDifyを起動します。以下のコマンドを実行してください。 $ git clone https://github.com/langgenius/dify.git $ cd dify/docker $ cp .env.example .env $ docker compose up -d ■ 管理者アカウントの設定 最初の管理者アカウントを設定します。以下のURLにアクセスしてください。 http://[ DifyをインストールしたマシンのIPアドレス ] 以下の画面が表示されますので、次のように入力し、最後に「セットアップ」をクリックします。 メールアドレス: 自分のメールアドレス ユーザー名: 管理者アカウントのユーザー名 パスワード: 管理者アカウントのパスワード   ■ ログイン 管理者アカウントの設定が完了すると、以下の画面が表示されます。先ほど設定したメールアドレスとパスワードを入力してログインしてください。 第7章: 簡単なチャットボットを作ってみよう ここからは、実際にDifyでチャットボットやワークフローを作りながら、Difyを学んでいきましょう。まずは、簡単なチャットボットを作成してみます。 モデルの設定 Difyは、LLMを利用したアプリケーションをノーコードで簡単に開発することができます。よって、もちろんDifyには何らかのLLMに接続する必要があります。それは、OpenAI、Azure OpenAI Service、Gemini、cohereといったものがあります。Difyではその接続対象のLLMのことを「モデル」と呼び、まずはそのモデルを設定する必要があります。   ■ モデル設定の画面を開く 画面右上のユーザー名をクリックして、「モデル」をクリックします。   ■ 追加対象のモデルを選択する 「モデルプロバイダー」をクリックし、追加対象のモデルを選択します。ここでは、Azure OpenAI Serviceを選択します。   ■ モデルを追加する 必要な情報を入力して、最後に「保存」をクリックします。ここではAzure OpenAI Serviceのモデルを追加する場合ですが、他のモデルの場合は入力項目が異なりますので、適宜必要な項目を入力してください。ただ、どのモデルにも共通して言えることですが、チャットボットを作る場合には、「Model Type」は「LLM」を選択してください。これは、チャットボットのように自然言語処理を中心としたタスクを処理する場合、LLM(大規模言語モデル)が最適なモデルタイプであるためです。 チャットボットの作成 モデルの設定が完了したら、次はチャットボットを作成します。 ■ アプリの種類選択画面を開く 「最初から作成」をクリックします。   ■ アプリの種類を選択する どのアプリを作成するかを選択する画面が開きます。ここではチャットボットを作成したいので、「チャットボット」を選択します(①)。 「アプリのアイコンと名前」にはアプリの名前を入力します。ここでは「簡単なチャットボット」と入力します(②)。 「説明」はアプリの説明となります。任意で入力してください(③)。 最後に、「作成する」をクリックします(④)。   ■ 質問を入力する 以下のような画面が表示されます。質問を入力してエンターを押します。   ■ 回答を確認する 質問に対する回答が表示されます。これで簡単なチャットボットが構築できました。   ■ アプリを実行する 次はアプリを実行してみます。今まで実施したのはいわば開発環境のアプリ開発作業であり、ここでは開発したアプリを実行してみます。 画面右上の「公開する」をクリックして(①)、「アプリを実行」をクリックします(②)。   ■ アプリを開始する アプリが起動します。アプリを開始するには、「Start Chat」をクリックします。   ■ アプリを使ってみる 以下のような画面が表示されます。質問を入力してエンターを押すと、回答が表示されます。 Difyを使うと、このように簡単にAIチャットボットを作成することができます。次は、より複雑なアプリケーションを作成していきましょう。 第8章: 高機能なチャットボットを作ってみよう 先ほど作成したチャットボットをさらに高機能にしてみましょう。以下の機能を追加していきます。 システムプロンプトによるキャラ付け 変数の利用 画像の解析 システムプロンプトによるキャラ付け システムプロンプトによって、AIにキャラ付けを行うことができます。「手順」のところで、システムプロンプトを設定してみましょう。 ここでは、「赤ちゃんのような言葉遣いで回答してください。」というシステムプロンプトを設定します。 すると回答が、赤ちゃんのような言葉遣いで返ってきます。 変数の利用 変数を利用すると、会話の最初にユーザーが入力した情報を利用することができます。 システムプロンプトによるキャラ付け で作成したシステムプロンプトに変数を利用してみましょう。 ■ 変数を追加する 「追加」をクリックして(①)、「短文」を選択します(②)。 変数は様々な種類があります。状況に応じて適切な変数を選択してください。 短文: 1行のテキスト 段落: 複数行のテキスト 選択: セレクトボックス形式の選択フォーム 数値: 数値型の変数   ■ 設定画面を表示する 変数名や項目必須などを設定する画面を表示するために、「設定」をクリックします。   ■ 各種設定を行う 変数を利用するための様々な設定を行います。以下のように入力して最後に「保存」をクリックします。 変数名: 変数の名前を入力します。ここでは「input」と入力します(①)。 ラベル: 変数のラベル名を入力します。ここでは「キャラ」と入力します(②)。 最大長: 入力可能な最大文字数を入力します。ここでは「48」と入力します(③)。 必須: 必須項目かどうかを選択します。必須にすると、ユーザーが入力しないと会話が開始できません。ここではチェックをします(④)。     ■ システムプロンプトを設定する システムプロンプトを以下のように設定します。 {{input}}のような言葉遣いで回答してください。 {{input}}の部分には先程定義した変数の値が入ります。「各種設定を行う」で定義舌変数名を、システムプロンプトで定義した変数名(input)は同じでなければなりません。   ■ アプリを実行する アプリを実行して、変数を利用したチャットボットを試してみましょう。画面上部に先ほど付与した変数名である「キャラ」が表示されているので、ここに例えば今回であれば「オネエ」と入力してみます(①)。そして、「いちごは何色ですか?」と質問してみます(②)。   ■ 動作を確認する 質問すると、以下のようにオネエ言葉で回答してくれます。 データの流れは以下のようになっています。 変数を定義すると、会話開始時にユーザーへの入力フォームが表示され、ユーザーが入力した値が変数に格納されます(①)。 会話を開始すると、ユーザー入力した値(ここでは「オネエ」)が、システムプロンプトの{{input}}に代入されます(②)。 最終的に、「オネエのような言葉遣いで回答してください。」というシステムプロンプトが出来上がり、AIはその指示に従い回答します(③)。 画像の解析 Difyは、画像の解析も行うことができます。ここでは、画像の解析を行う機能を追加してみましょう。この機能を利用するには、GPT-4oなどのマルチモーダルLLM(画像とテキストを同時に処理できるLLM)を利用する必要があります。 ■ ビジョンを有効にする 「ビジョン」のところにあるトグルボタンをオンにして、ビジョンを有効にします。   ■ 画像をアップロードする ピンの形のアイコンをクリックして(①)、「ローカルアップロード」をクリックします(②)。ローカルのPCからファイルをアップロードすることができます。 ちなみに、画像の解析は、画像のURLを指定して行うこともできます。その場合は、「URLを入力⋯」と表示されているテキストボックスにURLを入力してください。   ■ プロンプトを入力する 画像をアップロードすると、以下のような画面が表示されます。プロンプトを入力してエンターを押します。   ■ 解析結果を確認する プロンプトを入力すると、以下のような画面が表示されます。画像の解析結果が表示されます。 画像の中身を解析できていますね!! 第9章: RAGを作ってみよう ここからは、より高度なアプリケーションを作成していきます。生成AIの今ナウくて熱いユースケースであるRAG(Retrieval Augmented Generation)を作成してみましょう。 そもそもRAGってなに? OpenAIやAzure OpenAI Serviceは、インターネットなどによって公開されている膨大な量の情報を収集し、モデルをトレーニングして、そのモデルに基づき回答を生成しています。 しかしながら、自社が保有している情報をベースに回答を生成したいというユースケースはあると思います。例えば、社内の就業規約に関するQAなどです。育児休業の申請方法は、企業によってその方法が違うのはもちろんですし、そしてそのような情報は社内にてクローズドに管理されている就業規約に書かれていることがほとんどです。そんな就業規約のような独自データに基づいた回答を生成AIがしてくれたら、とっても便利だというのは言うまでもありません。今までセコセコ就業規約を検索して調べてたのが、生成AIに「育児休業の申請方法ってどうすればいいの?」と聞くだけで、回答が返ってくるなんて、とってもステキです。 実現方法として真っ先に思いつくのは、モデルに独自データを追加して学習させることです。一般的には「モデルの微調整」と言われたりしていまして、Azure OpenAI Serviceにもそういうサービスがあります。ただし、モデルの微調整は大変な作業です。モデルの微調整は、学習済みのモデルに追加の情報やデータを組み込むことで、その性能や反応を調整するプロセスなのですが、新しいデータセットの用意、学習の設定やパラメータ調整、そして再学習の実行など、多くの手間と時間がかかります。マイクロソフトも「モデルの微調整は最後の手段」と言っています。 そこで、モデルの微調整の代わりに、RAG(Retrieval Augmented Generation)の手法が推奨されます。時間やコストのかかるモデルの微調整をするのではなく、既存のドキュメントを外部データベースに登録し、ユーザーの質問と関連のあるドキュメントを外部データベースから取得します。そして、その内容に基づき、LLMに回答を指示することで、独自データに基づいて回答させるという手法です。 チャンク化とベクトルデータベース RAGの仕組みに詳しい方は、この節は読み飛ばしていただいて結構です。ここでは、チャンク化とベクトルデータベースに付いて説明します。 ベクトルデータベース 従来の検索は、検索対象の言葉がどのくらいドキュメントに含まれるかを数えることで行われていました。これを全文検索と呼ぶこととします。全文検査k検索は、厳密には、TF-IDF(Term Frequency-Inverse Document Frequency)という手法を使って、単語の重要度を計算して、検索結果の並び替えを行うなど、複雑な処理が内部では行われています。 この手法だと、微妙な言葉の揺らぎや、同じ意味の言葉の違いなどを考慮することが難しいです。そこでベクトル検索という技法が使われます。 ベクトル検索は、全文検索検索と比べて、より意味を理解することができます。たとえば、全文検索では「リンゴ」と入力すれば、「リンゴ」が書かれたページを見つけますが、ベクトル検索なら「フルーツ」や「健康食品」のような関連する言葉のページも見つけられます。これはベクトル検索が単語の意味を把握し、それに基づいて検索するからです。そのため、もっと広い範囲の情報を見つけやすくなり、また、同じ意味の言葉が違う言語で書かれていても、ベクトル検索ではうまく検索できることが多いです。これにより、より幅広い情報にアクセスしやすくなります。 さて、ここでベクトル検索における比較を理解しやすくするために、例として「甘み」と「価格」という2つの要素を用いて、いくつかの食べ物について考えてみましょう。まず、各食べ物について、どれだけ甘いかという「甘み」、そしていくらかかるかという「価格」に基づいて数値を割り当てます。この数値を使って、それぞれの食べ物を2次元のグラフにプロットします。 このグラフ上で、各食べ物は点として表され、点の位置はその食べ物の「甘み」と「価格」の数値によって決まります。例えば、リンゴは甘さが中程度で価格も手頃なため、中間の位置に点が来ます。一方で、レモンはあまり甘くないため、甘みのスケールでは低い位置に、価格が低いため価格のスケールでも低い位置に点が来ます。 次に、これらの点をベクトルとして考え、原点から各点へと向かう線分を描きます。これがそれぞれの食べ物の「特徴ベクトル」です。ベクトルの角度が小さいほど、2つの食べ物は特徴が似ていると言えます。つまり以下の図でθ1とθ2を見てみます。 リンゴとイチゴは、甘みも価格も似ているためθ1は、小さい角度になります。一方でリンゴとハバネロは価格こそ似ていれど、甘み大きく異なるため、θ2は大きい角度になります。 この角度を計算することにより、これらの果物がどの程度似ているのかを比較しますが、コンピューターは分度器を持っていないので、角度を比較するための計算式が必要となります。そこでコサイン類似度を使います。ベクトルAとベクトルBの内積をベクトルAの長さとベクトルBの長さの積で割ったものになります。数式に表すと以下になります。 コサイン類似度は、2つのベクトルがどれだけ同じ方向を向いているかを測る数値で、1に近いほど似ていると言えます。 では、リンゴとイチゴのコサイン類似度を求めてみます。 0.999となり1にかなり近く、リンゴとイチゴは非常に似ているということがわかります。 では、θ2つまりリンゴとハバネロはのコサイン類似度はどうでしょうか?価格はそこそ近いですが、甘みは大きくかけ離れています。まぁ、イチゴに比べればハバネロはとても辛いので感覚的にわかりますが、先程のコサイン類似度を使って数値として求めてみます。 リンゴとイチゴのコサイン類似度よりも1から遠い値になりました。なるほど、やっぱりリンゴとイチゴのほうがお互い似ていて、リンゴとハバネロはあんまり似ていないということがデータでわかりました。 このように、コサイン類似度を計算することで、グラフ上の位置の違いを超えて、2つの食べ物がどれだけ「特徴」が似ているかを数値で表すことができるのです。 今までは「甘み」と「価格」という2つの次元で類似度を計算していましたが、この考え方は自然言語処理においても応用可能です。自然言語の場合、単語や文書を表すために、何千から何万にも及ぶ多次元ベクトルを使用します。これらの高次元ベクトルには、言語の複雑な特徴が埋め込まれており、文書や単語間の意味的類似度を計算する際に使用されます。この方法により、テキスト間の意味的な距離を定量的に捉えることができます。 前置きが長くなりましたが、このベクトル化されたデータを格納するのものが、「ベクトルデータベース」となります。先の例で言えば、リンゴやイチゴ、ハバネロの特徴ベクトルをデータベースに格納しておき、ユーザーが「リンゴ」と入力したときに、リンゴの特徴ベクトルを取り出し、他の特徴ベクトルとのコサイン類似度を計算して、最も近いものを返すということができます。 環境変数を見る限りでは、コミュニティ版のDifyは以下のベクトルデータベースに対応しているようです。非常に多様な種類に対応していますね。 Weaviate (デフォルトではこれ) Qdrant Milvus MyScale Relyt PGVector PGVecto-RS Chroma OpenSearch TiDB Vector Oracle Tencent Vector ElasticSearch AnalyticDB Couchbase VikingDB OceanBase Upstash Vector Lindorm 以下の環境変数を参考にしました。 https://github.com/langgenius/dify/blob/main/docker/.env.example チャンク化 RAGではドキュメントをベクトル化して、ベクトルデータベースに格納します。このベクトル化する際に、ドキュメントを小さな単位に分割することがあります。この小さな単位を「チャンク」と呼びます。チャンク化する理由は、Azure OpenAI ServiceやOpenAIで提供される文章をベクトル化するEmbeddings APIのトークン数の制限に引っかからないようにするためです。このAPIはリクエストあたりのトークン数に上限があり(たとえばtext-embedding-ada-002では8191トークン)、そのためテキストを細かく分割する(チャンク化する)必要があります。 また、RAGではLLMに対して、ユーザーの質問に加えて、外部データベースからドキュメントを取得し、そのドキュメントに基づいてユーザーの質問に回答してと指示を出します。こうすることで、ユーザーが保持する独自の情報をベースに回答できるようになります。 このときに、LLMに送るドキュメントのサイズにもまた限界があります。例えば、ものすごいページ数の多いドキュメントをいっぺんにLLMに送ってもエラーになります。つまり、APIのリクエストサイズにも制限があるのです。 ここでもドキュメントのチャンク化が有効になります。ドキュメントをチャンク化し、細かいサイズに分割し、ユーザーの質問に関連のある必要なドキュメントの断片だけをお送ることでAPIの制限を回避することができます。 またチャンク化する際にオーバーラップという処理を施します。チャンクのオーバーラップとは、テキストを小さな部分(チャンク)に分割する際に、隣接するチャンク間で一部のテキストが重複するようにすることです。 チャンクのオーバーラップにより、バラバラになった文章を関連のある一つの文章としてAIが認識できるようになります。これにより、テキストの意味が途中で切断されるのを防ぎ、チャンク間での文脈の連続性が保たれます。結果として、検索や自然言語処理の精度が向上し、AIによる回答生成がより正確かつ関連性の高いものとなります。オーバーラップは、テキストをチャンクに分割する際に重要な情報が欠落するのを防ぐと同時に、バラバラだった文章を一つの関連性のある文章として認識するのに役立ちます。このように、チャンクのオーバーラップは、テキストの処理と理解を効率的に行うための重要な手法となります。 イメージにしますと以下のようにドキュメントを分割します。それぞれのチャンクが重なっているところがオーバーラップしている部分です。 ナレッジ 先ほどRAGは外部データベースにドキュメントを格納することがキモだと説明しました。Difyは、そのドキュメントを「ナレッジ」という単位で論理的にグループ化します。 開発チームメンバーがドキュメントをファイル単位でDifyに登録します。そのドキュメントは、ナレッジという単位でグループ化され、チャットボットやワークフローなど様々なアプリから参照されます。 そして、先程の チャンク化 の項でも説明しましたが、Difyもドキュメントをチャンク化し、ベクトルデータベースに登録しています。 具体例として、架空の会社ホゲホゲ株式会社の社内の就業規約を登録する例を上げてみましょう。ホゲホゲ株式会社は、社内規約を以下のカテゴリごとに単一のPDFファイルに分けているとします。 人事規程 (hr.pdf) 経理規程 (accounting.pdf) 情報システム関連規程 (it.pdf) 個人情報保護規程 (privacy.pdf) 品質保証関連規程 (quality.pdf) 以下の図のようなイメージでDifyにはナレッジとして登録されます。 RAGを作ってみよう それでは、実際にRAGを作成してみましょう。 ■ ナレッジの追加画面を開く まずはナレッジを追加する必要があります。「ナレッジ」(①)→「ナレッジを作成」(②)の順にクリックします。   ■ ドキュメントをアップロードする ナレッジに追加するドキュメントをアップロードします。 以下のURLからダウンロードできる「モデル就業規則」のPDF版をダウンロードしてください。 https://www.mhlw.go.jp/stf/seisakunitsuite/bunya/koyou_roudou/roudoukijun/zigyonushi/model/index.html 「テキストファイルからインポート」をクリックします(①)。そして、ダウンロードしたPDFファイルを選択してアップロードします(②)。最後に「次へ」をクリックします(③)。   ■ チャンクの設定を行う チャンクの設定を行います。チャンクの区切り文字や、チャンクの大きさなど、チャンクをする際に必要な設定を行います。ここではデフォルトの設定として、「保存して処理」をクリックします。チャンクの設定の詳細については「 第15章: チャンキング 」を参照してください。   ■ チャンクを行う チャンクの処理が開始されると、「埋め込み処理中…」と表示されます。しばらく待つと、チャンクの処理が完了し、「埋め込み処理が完了しました」と表示されます。「ドキュメントに移動」をクリックします。   ■ ドキュメントの追加完了を確認する 先ほど追加したドキュメントのステータスが「利用可能」となっていることを確認します。これで、ナレッジの追加は完了です。   ■ アプリの種類選択画面を開く Difyの管理画面トップにアクセスして、「最初から作成」をクリックします。   ■ アプリの種類を選択する どのアプリを作成するかを選択する画面が開きます。ここではチャットボットを作成したいので、「チャットボット」を選択します(①)。 「アプリのアイコンと名前」にはアプリの名前を入力します。ここでは「簡単なチャットボット」と入力します(②)。 「説明」はアプリの説明となります。任意で入力してください(③)。 最後に、「作成する」をクリックします(④)。   ■ コンテキストの追加 RAGに必要なナレッジを追加します。「追加」をクリックします。   ■ ナレッジを選択する 参照するナレッジを選択します。先程追加した「モデル就業規則」を選択します(①)。そして、「追加」をクリックします(②)。   ■ 動作確認を行う これでRAGが完成しました。質問を入力して、回答を確認してみましょう。 質問に対して、適切な回答が返ってくることが確認できました。 ファイル名のところをクリックしてみましょう。この回答を作成するために参照したチャンクが表示されます。 このチャンクを確認することで、チャットボットの回答の根拠を確認することができます。   ■ アプリを更新する 今まで設定を行ってきた画面は、開発者がアプリを作成するための画面です。この画面で行った設定をアプリに更新する必要があります。 「公開する」(①)→「更新」(②)をクリックします。こうすると、修正内容がアプリに反映されます。   ■ アプリを実行する 動作確認するためにアプリを実行します。「アプリを実行」をクリックします。   ■ アプリの動作確認をする アプリが起動し、チャットボットが表示されます。質問を入力して、回答を確認してみましょう。 利用者に使ってもらうためには、このアプリのURLを配布して、アクセスしてもらいます。 以上で、RAGの作成は完了です。とっても簡単にRAGを作成することができましたね。 第10章: ワークフローを作ってみよう ここからは、ワークフローを作成していきます。Difyのワークフローを使うと、本来Pythonなどのプログラミング言語で作成しなければいけなかったLLMアプリケーションをノーコードで作成することができます。 これはまさにDifyの強みであり、Difyによる「AIの民主化」に資するものです。プログラミングができない人でも、Difyを使ってAIを活用することができるのです。今までLLMは一部の専門家のものでしたが、Difyによって一般の人々も使うことができるようになりました。 以降では、Difyのワークフローについて、その基礎概念から詳しく説明します。 ワークフローの基本 Difyに限ったことではないですが、ワークフローは一般的に、入力・処理・出力の3つの要素から構成されます。Difyのワークフローも同様で、入力値を受け取り、処理を行い、出力値を返すという流れになります。図解すると以下のようになります。 入力と出力は一般的なワークフローと同等です。入力では、まずこのワークフローに最初に入力する値を定義し、出力では最終結果を出力します。 処理の部分は、Difyには「ブロック」「ツール」の2つがあります. ブロックは、ワークフローの中で、処理を行うための基本的な要素です。たとえば、条件分岐や繰り返し処理などがあります。他にもOpenAIなどのLLMへの接続を行うブロック、Pythonのコードを実行するブロックなどがあります。 ツールは、インターネット検索やQRコード生成といった、特定の処理を行うためのツールです。他にも株価検索や天気予報取得など、様々なツールが用意されています。 ツールとブロックは順番関係なくつなげていくこともできますし、2つ以上の複数のブロックやツールをつなげることもできます。また、ブロック/ツールを並行に処理させることもできます。 これから作るワークフローの概要 本章で作成するワークフローの概要について説明します。 企業内には様々な契約書があります。契約書にはもちろん様々な契約事項が書いてあり、その中には契約満了期間といった、期日に関わる重要な事項もあります。契約満了期間を把握しておくことは、契約の管理において非常に重要です。ましてや契約期間が切れているなんてもってのほかです。しかし契約書の中には相当大量の契約事項が書かれているものもあり、その中から契約期限切れのおそれがある部分を探すのは大変です。 そこで、DifyのワークフローによるLLMアプリの出番です。そんな課題を解決すべく、以下のワークフローを作成します。イメージが付きやすいようにいきなり完成形をお見せします。   このワークフローは、企業内に散在するPDF形式の契約書から、そのテキストを抜き出し、LLMで解析させて、契約書に内在する契約満了期間のリスクを洗い出します。LLMは現在の時間はわからないので、現在時刻を取得するツールを使って現在時刻を取得し、それをLLMに投げます。LLMは契約書のテキストと現在時刻を入力として、契約満了期間のリスクを洗い出します。最後に契約満了期間のリスクを出力します。 このワークフローは以下のパーツで構成されています。 開始: ワークフローの開始地点です。契約書のPDFファイルが入力されます。 ツール(CURRENT TIME): 現在時刻を取得します。契約書内の契約満了期間と現在時刻を比較するために必要です。 ブロック(テキスト抽出ツール): 契約書のPDFファイルからテキストを抽出します。 ブロック(LLM): 現在時刻と契約書のテキストを入力として、契約満了期間のリスクを洗い出します。 終了: ワークフローの終了地点です。契約満了期間のリスクが出力されます。 処理の流れは以下の通りとなります。 1. 契約書のPDFファイルを入力として受け取ります。 2. ツール(CURRENT TIME)で現在時刻を取得します。LLMは現在の時刻を取得することができないため、このツールを使って現在時刻を取得します。 3. ブロック(テキスト抽出ツール)で契約書のPDFファイルからテキストを抽出します。 4. ブロック(LLM)で契約書のテキストと現在時刻を入力として、契約満了期間のリスクを洗い出します。以下のようなプロンプトを作成して、LLMに投げます。 現在の日付は{CURRENT TIMEで取得した現在時刻}です。以下の契約書について、契約期間の満了が迫っている場合や、期間に関するリスクが存在する場合は、それらを特定し、具体的に列挙してください。 {テキスト抽出ツールで抽出した契約書のテキスト} 5. 終了で契約満了期間のリスクが出力されます。 今回のワークフローで使うPDFは以下のURLからダウンロードできます。 contract 契約書の内容は以下のとおりです。 「第2条(契約期間)」には契約期間が記載されています。この契約期間が満了する日付を洗い出し、リスクを明確化することがこのワークフローの目的です。 ワークフローの構成 今回作成するワークフローの構成を説明します。 まず、「開始」では、契約書のPDFファイルを入力として受け取ります。 「CURRENT TIME(ツール)」「テキスト抽出ツール(ブロック)」が並行して処理されます。まず、「CURRENT TIME(ツール))」で現在時刻を取得します。次に、「テキスト抽出ツール(ブロック)」で契約書のPDFファイルからテキストを抽出します。とてもわかりにくいのですが、「テキスト抽出ツール」は、ツールと言う名前がありますが、実際にはブロックです。 「LLM(ブロック)」では、契約書のテキストと現在時刻を入力として、契約満了期間のリスクを洗い出します。具体的には、「CURRENT TIME(ツール)」「テキスト抽出ツール(ブロック)」の処理結果を以下のようにプロンプトに埋め込んで、LLMに投げます。 現在の日付は{CURRENT TIMEで取得した現在時刻}です。以下の契約書について、契約期間の満了が迫っている場合や、期間に関するリスクが存在する場合は、それらを特定し、具体的に列挙してください。 {テキスト抽出ツールで抽出した契約書のテキスト} {CURRENT TIMEで取得した現在時刻}の部分は、CURRENT TIME(ツール)の処理結果を埋め込んでいます。{テキスト抽出ツールで抽出した契約書のテキスト}の部分は、テキスト抽出ツール(ブロック)の処理結果を埋め込んでいます。 「CURRENT TIME(ツール)」「テキスト抽出ツール(ブロック)」によって取得した値が埋め込まれたプロンプトは以下のようになります。 現在の日付は2022-01-01です。以下の契約書について、契約期間の満了が迫っている場合や、期間に関するリスクが存在する場合は、それらを特定し、具体的に列挙してください。 契約書 第1条(契約の目的) 本契約は、○○株式会社(以下「甲」という)と△△株式会社(以下「乙」という)との間で、XXXXXに関する契約の条件を定めるものである。 ...以下省略... ワークフローの作成 では、実際にワークフローを作成していきましょう。 ■ アプリの種類選択画面を開く 「最初から作成」をクリックします。   ■ アプリの種類を選択する どのアプリを作成するかを選択する画面が開きます。ここではワークフローを作成したいので、「ワークフロー」を選択します(①)。 「アプリのアイコンと名前」にはアプリの名前を入力します。ここでは「契約書チェック」と入力します(②)。 「説明」はアプリの説明となります。任意で入力してください(③)。 最後に、「作成する」をクリックします(④)。   ■ 入力フィールド追加画面を開く ワークフローの入力フィールドを追加するための画面を開きます。「追加」をクリックします。   ■ 入力フィールドを追加する ワークフローの最初の入力となる契約書のファイルを入力するフィールドを追加します。以下のように入力して、最後に「保存」(⑤)をクリックします。 – ①フィールドタイプ: 単一ファイル 入力の形式を選択します。単一のテキストや、ラジオボタンのような選択形式、複数行のテキスト等様々な入力形式があります。ここでは契約書のファイルを一つだけアップロードするので、「単一ファイル」を選択します。 – ②フィールド名: file アップロードした契約書のファイルの内容が格納される変数名になります。後のブロックやツールなどで使います。 – ③ラベル名: ファイル フォームに表示されるラベル名です。ここでは「ファイル」と入力します。 – ④サポートされたファイルタイプ: ドキュメント どの種別のファイルアップロードを許可するかを設定します。画像、音声等様々なファイルがアップロード可能ですが、ここではPDFをアップロードするので「ドキュメント」を選択します。 – ⑤ アップロードされたファイルのタイプ: ローカルアップロード ファイルのアップロード方法を選択します。ローカルアップロードは、自分のPCからファイルをアップロードする方法です。URLからアップロードする方法もあります。ここではローカルアップロードを選択します。 – ⑥ 必須: チェックを入れる ファイルのアップロードを必須にするかどうかを設定します。ここでは契約書のファイルをアップロードしないとワークフローが進められないので、チェックを入れます。   ■ テキスト抽出ツールを追加する テキスト抽出ツールのブロックを追加します。「開始」の右側にある「+」をクリックします(①)。様々なブロックやツールが表示されます。各ブロックやツールの詳細は後ほど説明しますが、ここでは「テキスト抽出ツール」を選択します(②)。   ■ テキスト抽出ツールの設定を行う テキスト抽出ツールの設定を行います。まず入力変数の設定を行います。入力変数はこのブロックに入力される値です。Difyのワークフローではこのブロックに限らず、すべてのブロックやツールはその入力となる値をいれる必要があります。これは当然で、何らかの処理を行う場合、基本的に入力→処理→出力の流れになるためです。 ここでは、「開始」の部分で設定した「file」を入力変数に設定します。こうすると、開始の部分で選択した契約書のファイルがこのブロックに入力されることになります。   ■ テキスト抽出ツールの設定内容を確認する 下図のように表示されればOKです。これは「開始」で設定した「file」という変数がこのブロックに入力されることを示しています。   ■ CURRENT TIME(現在時刻を取得するツール)を追加する CURRENT TIME(現在時刻を取得するツール)を追加するために、「開始」の右側にある「+」をクリックします(①)。「ツール」をクリックすると(②)、ツールの一覧が表示されますので、検索ボックスに「current」と入力して(③)、「CURRENT TIME」を選択します(④)。   ■ CURRENT TIME(現在時刻を取得するツール)の設定を行う ツールを追加したあとは、そのツールの設定を行います。これは各ツールごとに設定内容が異なります。「CURRENT TIME」の場合は以下になります。 ① FORMAT: %Y-%m-%d %H:%M:%S 現在時刻のフォーマットを指定します。ここでは「年-月-日 時:分:秒」のフォーマットを指定します。 ② TIMEZONE: Asia/Tokyo 現在時刻のタイムゾーンを指定します。ここでは日本時間を指定します。このタイムゾーンを正確に指定しないと、正しい現在時刻を取得できません。UTCのままだと日本時間から9時間ズレた値となりますので、ご注意ください。   ■ LLM(ブロック)を追加する 契約書の内容と現在時刻を入力として、契約満了期間のリスクを洗い出すLLMのブロックを追加します。「テキスト抽出ツール」のブロックの右側にある「+」をクリックします(①)。「LLM」のブロックを選択します(②)。   ■ CURRENT TIME(ツール)とLLM(ブロック)を接続する 「CURRENT TIME(ツール)」と「LLM(ブロック)」を接続します。これは「CURRENT TIME(ツール)」の処理結果を「LLM(ブロック)」に入力として渡すための接続です。「CURRENT TIME(ツール)」の右側にある「+」をクリックして(①)、そのままドラッグして、「LLM(ブロック)」に接続します(②)。   以下のようになればOKです。   ■ LLM(ブロック)の設定を行う LLM(ブロック)の設定を行います。まず、LLMのブロックにはそれを処理するモデルの指定が必要です。ここでは、「チャットボット」の章で作成したモデルを指定します(①)。 次にプロンプトを設定します。「+メッセージを追加」をクリックします(②)。   ■ プロンプトの設定を行う LLMに投げるプロンプトを設定します。プロンプトは、LLMに投げる質問のようなものです。ここでは以下のように設定します。 現在の日付はです。以下の契約書について、契約期間の満了が迫っている場合や、期間に関するリスクが存在する場合は、それらを特定し、具体的に列挙してください。 現在時刻や契約書のテキストを埋め込む部分が設定されていませんが、気にしないでください。次のステップで設定します。    ■ 現在時刻をプロンプトに埋め込む 先ほど追加したプロンプトの「現在の日付は」の後に、キーボードで「/」(スラッシュ)を入力します。すると、変数の一覧が表示されます。ここから「CURRENT TIME」の「text」を選択します(①)。これは「CURRENT TIME(ツール)」のtextという変数に現在時刻が格納されており、それをプロンプトに埋め込むための作業です。   以下のようになればOKです。   ■ 契約書のテキストをプロンプトに埋め込む 同様に、テキスト抽出ツールのブロックで抽出した契約書のテキストをプロンプトに埋め込みます。プロンプトの末尾で「/」(スラッシュ)を入力します(①)。すると、変数の一覧が表示されます。ここから「テキスト抽出ツール」の「text」を選択します(②)。   以下のようになればOKです。   ■ 終了を追加する 最後に「終了」を追加します。これを追加しないと、ワークフローの最終結果が出力されません。LLMのブロックの右側にある「+」をクリックして(①)、「終了」を選択します(②)。   ■ 終了の設定を行う 出力変数を設定します。出力変数はワークフローの最終結果となる値です。変数名は「result」とします(①)。そして、このresultに格納される値を設定します。ここでは「LLM(ブロック)」の「text」を設定します(②)。そうすると、LLMのブロックで洗い出した契約満了期間のリスクがresultに格納されます。   以下のようになればOKです。   ■ ワークフローを実行する これでワークフローは完成です。では、ワークフローを実行します。「実行」をクリックすると(①)、ワークフローを開始するために必要な入力フォームが表示されます。「開始」の部分で定義したように、契約書のPDFファイルをアップロードするためのフォームが表示されます。「ローカルアップロード」をクリックして、契約書のPDFファイルをアップロードします(②)。   以下のようになればOKです。「実行を開始」をクリックします。   ■ 結果を確認する ワークフローの実行結果を確認します。契約書の内容を見て、現在日時と比較して、契約満了日が迫っているものを洗い出して、教えてくれています。   ■ トレースを確認する 開始や終了、各ブロックやツールの詳細な処理内容を確認することができます。それぞれのブロックやツールをクリックすると入力内容や出力結果が表示されます。詳細な処理内容を確認したいときや、ワークフローがうまく動かないときに使います。   これでワークフローの作成は完了です。Difyは、複雑なLLMアプリケーションをノーコードで作成することができるため、非常に便利です。これにより、プログラミングができない人でも、AIを活用することができるようになります。 第11章: チャットフローを作ってみよう 本章では、チャットフローを作ります。チャットボットとの違いは、複雑なチャットボットを作れることです。 下図のようにチャットボットは、ナレッジを参照して答えるだけの単純なチャットボットです。一方でチャットフローは、例えば質問の内容を解析して、その内容に沿ったナレッジを参照させる等、高度な処理を行うことができます。その高度な処理は、ワークフローのように複数のブロックやツールを組み合わせて作ることができます。 つまり、チャットフローは、チャットボットとワークフローを組み合わせたものと言えます。   これから作るチャットフローの概要 これから作るチャットフローの概要を説明します。このチャットフローは、質問の内容に応じて、それぞれナレッジの参照先を変えるチャットフローです。自社製品に関するナレッジと自社製品に関するナレッジがあり、ユーザーの質問の内容をLLMが解析して、その質問に応じた適切なナレッジを参照して回答するRAGです。質問に関連したナレッジのみを参照することにより、回答を生成するための情報に余計なノイズが入り込むことを防止し、回答精度を向上させることができます。 まずは、「質問分類器」というブロックによって質問の内容をLLMが解析します。その結果、質問の内容を「自社製品に関する質問」「福利厚生に関する質問」「その他」にカテゴライズします。そのカテゴリに応じて、それぞれのナレッジを参照して回答を生成します。 「自社製品に関する質問」の場合は、「知識取得」というブロックに遷移し、自社製品に関するナレッジを参照します。次にLLMのブロックによって、質問の内容とナレッジを入力として、回答を生成します。最後に「回答」のブロックによって、回答を出力します。この回答のブロックがないと、チャットフローはユーザーに回答を出力できません。 「福利厚生に関する質問」の場合は、参照するナレッジが福利厚生になるだけで、それ以外の処理は「自社製品に関する質問」と同じです。 「その他」の場合は、「回答」のブロックで、「回答ができません」という固定の回答を返すようにします。 ナレッジの作成 まずは、ナレッジを作成します。ナレッジは、チャットフローの中で参照される情報です。自社製品に関するナレッジと福利厚生に関するナレッジを作成します。 ■ ナレッジの作成画面を開く ナレッジを作成するための画面を開きます。「ナレッジ」をクリックし(①)、「ナレッジを作成」をクリックします(②)。   ■ ドキュメントをアップロードする ナレッジに追加するドキュメントをアップロードします。 以下のリンクにて、自社製品に関するナレッジのPDFファイルをダウンロードしてください。こちらは私が作成した架空の自社製品に関するナレッジです。 product 「テキストファイルからインポート」をクリックします(①)。そして、ダウンロードしたPDFファイルを選択してアップロードします(②)。最後に「次へ」をクリックします(③)。   ■ チャンクの設定を行う チャンクの設定を行います。チャンクの区切り文字や、チャンクの大きさなど、チャンクをする際に必要な設定を行います。ここではデフォルトの設定として、「保存して処理」をクリックします。チャンクの設定の詳細については「」を参照してください。   ■ チャンクを行う チャンクの処理が開始されると、「埋め込み処理中…」と表示されます。しばらく待つと、チャンクの処理が完了し、「埋め込み処理が完了しました」と表示されます。「ドキュメントに移動」をクリックします。   ■ ドキュメントの追加完了を確認する 先ほど追加したドキュメントのステータスが「利用可能」となっていることを確認します。これで、ナレッジの追加は完了です。   ■ ナレッジの追加を繰り返す 同様の手順で、福利厚生に関するナレッジのPDFファイルをアップロードして、ナレッジを追加します。 以下のリンクにて、福利厚生に関するナレッジのPDFファイルをダウンロードしてください。こちらは私が作成した架空の福利厚生に関するナレッジです。 benefits チャットフローの作成 それでは、チャットフローを作成していきましょう。 ■ アプリの種類選択画面を開く 「最初から作成」をクリックします。   ■ アプリの種類を選択する どのアプリを作成するかを選択する画面が開きます。ここではチャットフローを作成したいので、「チャットフロー」を選択します(①)。 「アプリのアイコンと名前」にはアプリの名前を入力します。ここでは「賢いチャットフロー」と入力します(②)。 「説明」はアプリの説明となります。任意で入力してください(③)。 最後に、「作成する」をクリックします(④)。   ■ 「開始」以外のブロックを削除する デフォルトでは、LLMや回答などのブロックがありますが、今回はゼロから作成したいため、これらのブロックを削除します。削除するには、各ブロックの右上にある「…」をクリックして(①)、「削除」をクリックします(②)。   ■ 質問分類器を追加する まずは質問分類器を追加します。「開始」の右側にある「+」をクリックして(①)、「質問分類器」をクリックします(②)。   ■ 質問分類器のモデルを選択する 質問分類器のモデルを選択します。「モデル」をクリックすると(①)、別のウィンドウがポップアップします。「モデル」をクリックすると(②)、モデルの一覧が表示されます。ここでは「チャットボット」の章で作成したモデルを選択します(③)。   ■ 質問分類器の入力変数を設定する 質問分類器の入力変数が「sys.query」に設定します。おそらくデフォルトでsys.queryが設定されてると思います。これはユーザーが入力した質問の内容が格納される変数です。チャットフローでは固定で定義されているシステム変数となります。   ■ クラスの設定 質問を分類分けするためのクラスを設定します。このクラスで設定した説明をもとに質問を分類します。ここでは「自社製品に関する質問」「福利厚生に関する質問」「その他」の3つのクラスを設定します。 「+ クラスを追加」をクリックして(①)、3つのクラスを表示させます。それぞれのクラスに対して、「自社製品に関する質問」「福利厚生に関する質問」「その他の質問」を入力します(②)。 そして、設定したクラスごとに、次に遷移するブロックを定義します。   ■ 自社製品に関する知識取得ブロックを追加する 先の質問分類器で自社製品に関する質問がされた場合に、自社製品に関するナレッジを参照するための知識取得ブロックを追加します。 知識取得ブロックとは、ナレッジを参照するためのブロックです。質問を入力して、その質問でナレッジに対して検索を行い、検索結果を出力します。 質問分類器のクラス1の右側にある「+」をクリックして(①)、「知識取得」をクリックします(②)。   ■ 自社製品に関する知識取得ブロックの設定を行う 「クエリ変数」が「sys.query」になっていることを確認します。これはユーザーが入力した質問の内容が格納される変数です。ここで定義した変数によって、次に設定するナレッジに対して検索が行われます。 次にナレッジを追加するために、「+」をクリックします(①)。   ■ 自社製品に関するナレッジを選択する 先ほど追加した自社製品に関するナレッジを選択します(①)。そして、「追加」をクリックします(②)。   ■ 自社製品への質問に対する回答を生成するLLMブロックを追加する 自社製品に関するナレッジを参照して、質問に対する回答を生成するLLMブロックを追加します。先ほど作成した知識取得ブロックの右側にある「+」をクリックして(①)、「LLM」をクリックします(②)。   ■ LLMブロック(自社製品への質問に対する回答生成用)のモデルを設定する LLMブロックのモデルを設定するのモデルを選択します。「モデル」をクリックすると(①)、別のウィンドウがポップアップします。「モデル」をクリックすると(②)、モデルの一覧が表示されます。ここでは「チャットボット」の章で作成したモデルを選択します(③)。   ■ LLMブロック(自社製品への質問に対する回答生成用)のコンテキストを設定する LLMブロックのコンテキストを設定します。コンテキストとは、LLMが回答を生成する際に参照する情報です。ここでは、先ほど追加した知識取得ブロックの出力結果を格納する変数であるresultを設定します。   ■ LLMブロック(自社製品への質問に対する回答生成用)のプロンプトを設定する LLMに投げるプロンプトを設定します。システムメッセージは、「あなたは製品情報に答えるチャットボットです。」と入力します(①)。 ユーザーメッセージは以下のように入力します。以下のようにプロンプトを設定することで、LLMの回答を生成する際に、知識取得ブロックで検索した内容から得られる情報に基づいて回答を生成することができます。いわゆるRAG(Retrieve and Generate)の手法です。ただし、肝心の「#質問」「#取得した情報」の中身の部分は、まだ未設定なのでこれから設定します。 「#質問」に書かれている質問を「#取得した情報」に基づいて回答してください。 #質問 #取得した情報   ■ LLMブロック(自社製品への質問に対する回答生成用)のプロンプトに「#質問」を埋め込む 「#質問」の内容を埋め込みます。「#質問」の後に「/」(スラッシュ)を入力します(①)。すると、変数の一覧が表示されます。ここから「sys.query」を選択します(②)。この変数はユーザーが入力した質問の内容が格納される変数です。チャットフローでは固定で定義されているシステム変数となります。   ■ LLMブロック(自社製品への質問に対する回答生成用)のプロンプトに「#取得した情報」を埋め込む 「#取得した情報」の内容を埋め込みます。「#取得した情報」の後に「/」(スラッシュ)を入力します(①)。すると、変数の一覧が表示されます。ここから「コンテキスト」を選択します(②)。これは先程設定したコンテキストを示すものであり、コンテキストは知識取得ブロックで検索した内容が格納される変数です。   ■ LLMブロック(自社製品への質問に対する回答生成用)のプロンプトの設定を確認する 以下のようになればOKです。   ■ 回答ブロックを追加する LLMブロックで回答を生成した後、その回答を出力するための回答ブロックを追加します。この回答ブロックを作成しないと、チャットで回答が返ってきませんので、ご注意ください。LLMブロックの右側にある「+」をクリックして(①)、「回答」をクリックします(②)。   ■ 回答ブロックの設定を行う 回答ブロックの設定を行います。「回答」というフィールドに、チャットに出力させたい回答を定義します。「/」(スラッシュ)を入力します(①)。すると、変数の一覧が表示されます。ここから「LLM」の「text」を選択します(②)。これはLLMブロックで生成した回答が格納される変数です。   ■ 回答ブロックの設定を確認する 以下のようになればOKです。   ■ 福利厚生に関する知識取得ブロックを追加する 次に、福利厚生に関する知識取得ブロックを追加します。質問分類器のクラス2の右側にある「+」をクリックして(①)、「知識取得」をクリックします(②)。   ■ 福利厚生に関する知譆取得ブロックの設定を行う 「クエリ変数」が「sys.query」になっていることを確認します。これはユーザーが入力した質問の内容が格納される変数です。ここで定義した変数によって、次に設定するナレッジに対して検索が行われます。 次にナレッジを追加するために、「+」をクリックします(①)。   ■ 福利厚生に関するナレッジを選択する 先ほど追加した福利厚生に関するナレッジを選択します(①)。そして、「追加」をクリックします(②)。   ■ 福利厚生への質問に対する回答を生成するLLMブロックを追加する 福利厚生に関するナレッジを参照して、質問に対する回答を生成するLLMブロックを追加します。先ほど作成した知識取得ブロックの右側にある「+」をクリックして(①)、「LLM」をクリックします(②)。   ■ LLMブロック(福利厚生への質問に対する回答生成用)のモデルを設定する LLMブロックのモデルを設定するのモデルを選択します。「モデル」をクリックすると(①)、別のウィンドウがポップアップします。「モデル」をクリックすると(②)、モデルの一覧が表示されます。ここでは「チャットボット」の章で作成したモデルを選択します(③)。   ■ LLMブロック(福利厚生への質問に対する回答生成用)のコンテキストを設定する LLMブロックのコンテキストを設定します。コンテキストとは、LLMが回答を生成する際に参照する情報です。ここでは、先ほど追加した知識取得ブロックの出力結果を格納する変数であるresultを設定します。    ■ LLMブロック(福利厚生への質問に対する回答生成用)のプロンプトを設定する LLMに投げるプロンプトを設定します。システムメッセージは、「あなたは福利厚生に答えるチャットボットです。」と入力します(①)。 ユーザーメッセージは以下のように入力します。以下のようにプロンプトを設定することで、LLMの回答を生成する際に、知識取得ブロックで検索した内容から得られる情報に基づいて回答を生成することができます。いわゆるRAG(Retrieve and Generate)の手法です。ただし、肝心の「#質問」「#取得した情報」の中身の部分は、まだ未設定なのでこれから設定します。 「#質問」に書かれている質問を「#取得した情報」に基づいて回答してください。 #質問 #取得した情報   ■ LLMブロック(福利厚生への質問に対する回答生成用)のプロンプトに「#質問」を埋め込む 「#質問」の内容を埋め込みます。「#質問」の後に「/」(スラッシュ)を入力します(①)。すると、変数の一覧が表示されます。ここから「sys.query」を選択します(②)。これはユーザーが入力した質問の内容が格納される変数です。チャットフローでは固定で定義されているシステム変数となります。   ■ LLMブロック(福利厚生への質問に対する回答生成用)のプロンプトに「#取得した情報」を埋め込む 「#取得した情報」の内容を埋め込みます。「#取得した情報」の後に「/」(スラッシュ)を入力します(①)。すると、変数の一覧が表示されます。ここから「コンテキスト」を選択します(②)。これは先程設定したコンテキストを示すものであり、コンテキストは知識取得ブロックで検索した内容が格納される変数です。   ■ LLMブロック(福利厚生への質問に対する回答生成用)のプロンプトの設定を確認する 以下のようになればOKです。   ■ 回答ブロックを追加する LLMブロックで回答を生成した後、その回答を出力するための回答ブロックを追加します。この回答ブロックを作成しないと、チャットで回答が返ってきませんので、ご注意ください。LLMブロックの右側にある「+」をクリックして(①)、「回答」をクリックします(②)。   ■ 回答ブロックの設定を行う 回答ブロックの設定を行います。「回答」というフィールドに、チャットに出力させたい回答を定義します。「/」(スラッシュ)を入力します(①)。すると、変数の一覧が表示されます。ここから「LLM」の「text」を選択します(②)。これはLLMブロックで生成した回答が格納される変数です。 ■ 回答ブロックの設定を確認する 以下のようになればOKです。   ■ その他の質問に対する回答を生成する 最後に、その他の質問に対しては、「回答ができません」という固定の回答を返すようにします。「その他の質問」のクラスの右側にある「+」をクリックして(①)、「回答」をクリックします(②)。   ■ その他の質問に対する回答を設定する 回答ブロックの設定を行います。「回答」というフィールドに、「回答ができません」と入力します。   ■ チャットフロー全体を確認する これでチャットフローの作成は完了です。全体を確認してみましょう。   ■ 動作確認: 自社製品に関する質問 それでは、チャットフローを動作確認してみましょう。まずは、自社製品に関する質問を入力してみます。   自社製品に関するナレッジに基づいて回答しているのがわかります(①)。また、「ログを表示」をクリックして(②)、トレースをクリックしてみると(③)、質問分類器で「自社製品に関する質問」と判定されていることがわかります(④)。その後の流れを見てみると、確かに自社製品に関するナレッジを参照して回答を生成していることがわかります(⑤)。   ■ 動作確認: 福利厚生に関する質問 次に、福利厚生に関する質問を入力してみます。福利厚生に関するナレッジに基づいて回答しているのがわかります(①)。また、「ログを表示」をクリックして(②)、トレースをクリックしてみると(③)、質問分類器で「福利厚生に関する質問」と判定されていることがわかります(④)。その後の流れを見てみると、確かに福利厚生に関するナレッジを参照して回答を生成していることがわかります(⑤)。   ■ 動作確認: その他の質問 最後に、その他の質問を入力してみます。その他の質問に対しては、「回答ができません」という固定の回答を返しているのがわかります(①)。また、「ログを表示」をクリックして(②)、トレースをクリックしてみると(③)、質問分類器で「その他」と判定されていることがわかります(④)。その後の流れを見てみると、確かにその他の質問に対しては固定の回答を返していることがわかります(⑤)。 第12章: AIエージェントを作ってみよう 本章では、Difyを使ってAIエージェントを作成する方法を解説します。 AIエージェントとは? まずAIエージェントの概要をお伝えします。AIエージェントとは、ユーザーの指示に基づいて適切なツールを選び、処理の流れを自動で組み立てて実行するAI です。従来のアプリケーションでは、開発者があらかじめ「何をどう処理するか」をプログラムする必要がありましたが、AIエージェントはプロンプト(指示文)とツール を与えるだけで、最適な方法を自ら考えて実行します。 例えば「このデータを調査して、要点をまとめて報告して」という指示を出すと、 まずインターネットで検索 次に関連する情報を整理 さらに結果を要約 最後にレポートとして出力 といった一連の流れをAIが自動で判断して実行します。従来のアプリでは開発者がロジックを組まなければならなかった部分をAIが担うため、より柔軟で手軽に高度な処理を行うことが可能になります。 わかりやすく伝えるため、AIにエージェントがない世界(従来型のアプリケーションの場合)と、AIエージェントがある世界を比較してみましょう。 AIエージェントがない世界 先程の図の「従来型のアプリケーション」の部分を見てみます。従来型のアプリケーションでは、UI設計と開発の手間が生じます。従来のアプリでは、ユーザーが操作するためのUI(ユーザーインターフェース)を設計し、実装しなければなりません。例えば、ユーザーが検索ワードを入力し、「QRコードを作成する」オプションを選べるようなUIを作成し、検索結果を表示する画面も用意する必要があります。 また、もし新しい機能(例えば「結果をPDFに変換する」機能)を追加したい場合、UIに新しいチェックボックスやボタンを増やし、それに対応する処理を実装しなければなりません。つまり、新しい機能を追加するたびにUIの変更が必要になり、開発コストが増加します。 処理の流れも明示的に定義しなければなりません。ユーザーが検索ワードを入力し、検索実行ボタンを押した後に、 1. インターネット検索関数を呼び出す 2. 結果を取得 3. QRコードを作成する(オプション) という 明確な処理の流れをプログラムとして定義し、開発者がコードを書かなければなりません。 開発者が関数の呼び出し順やデータの流れをすべて設計し、実装する必要があるため、開発負担が大きくなります。 AIエージェントがある世界 先程の図の「AIエージェントの場合」の部分を見てみます。AIエージェントを使うと、ユーザーは「RAGの情報がほしい」と自然言語で指示するだけ でOK。特定のUIを設計しなくても、AIが適切な処理を組み立てて実行してくれます。 まず、AIエージェントを使うと、ユーザーは 「RAGの情報がほしい」と自然言語で指示するだけでOKです。特定のUIを設計しなくても、AIが適切な処理を組み立てて実行 してくれます。 また、機能追加のたびにUIを変更する必要がありません。新しい機能(例えば「URLのQRコードを作成する」)を追加したい場合、プロンプトを「URLのQRコードを作成してください。」と書くだけで対応可能 です。開発者は新しいUIを作成する必要がなく、AIエージェントに新しいツールを追加するだけで済みます。 また、処理の流れもAIが決定します。AIエージェントは、 1. どの関数を呼び出すか(インターネット検索 → QRコード作成) 2. どの順番で処理を実行するか(検索してからQRコード作成) をLLM(大規模言語モデル)が自動で判断してくれます。開発者は個別の処理の流れを実装する必要がなく、ツールとプロンプトを用意するだけで良いのです。 つまり、AIエージェントを活用すると、従来のような「UI設計」「処理の順番の決定」「機能追加に伴うUI改修」といった開発の手間がなくなり、柔軟で拡張性の高いアプリを簡単に作れるようになります。 AIエージェントの作成 では、早速AIエージェントを作成してみましょう。AIエージェントに依頼された情報をインターネットで検索し、情報検索元のサイトのURLのQRコードを表示します。 動作イメージは以下のとおりです。   ■ アプリの種類選択画面を開く 「最初から作成」をクリックします。   ■ アプリの種類を選択する どのアプリを作成するかを選択する画面が開きます。ここではチャットフローを作成したいので、「エージェント」を選択します(①)。 「アプリのアイコンと名前」にはアプリの名前を入力します。ここでは「情報検索エージェント」と入力します(②)。 「説明」はアプリの説明となります。任意で入力してください(③)。 最後に、「作成する」をクリックします(④)。   ■ プロンプトを設定する AIエージェントに与えるプロンプトを設定します。このプロンプトはAIエージェントの振る舞いを決定する重要な要素です。以下のように入力します。 「〜の情報がほしい」といったような検索依頼があったら、以下のことを実施してください。 - 依頼された内容に関する情報をインターネットで検索する。 - 情報提供元のサイトのURLのQRコードを作成する。   ■ Google Search APIのサインアップ画面を開く Google Search APIを利用してインターネット検索を行います。Google Search APIを利用するためには、サインアップしてAPIキーを取得する必要があります。 以下のURLにアクセスします。 https://www.searchapi.io/ 「Get 100 Free Reqests」をクリックします。   ■ サインアップする サインアップの画面が表示されます。必要な項目を入力してサインアップします。   ■ メールを確認する サインアップ後、登録したメールアドレスに確認メールが届きます。メール内のリンク(Confirm my acount)をクリックして登録を完了します。   ■ APIキーを取得する Google serach APIのURL(https://www.searchapi.io/)にアクセスし、APIキーを取得します。「Dashboard」をクリックして(①)、APIキーをコピーします(②)。   ■ ツールの追加画面を開く インターネットを検索するためのツール「Google Search API」を追加します。ツールの追加画面を開くために、「+ 追加」をクリックします。   ■ Google Search APIを追加する 「Search」をクリックして(①)、「Google Search API」の隣りにある「+追加」をクリックします(②)。   ■ APIキーを設定する APIキーを設定します。先程取得したAPIキーを入力して(①)、「保存」をクリックします(②)。   ■ Generate QR Codeを追加する QRコードを生成するためのツール「Generate QR Code」を追加します。「Utilities」をクリックして(①)、「Generate QR Code」の隣りにある「+追加」をクリックします(②)。   ■ ツールの追加を確認する 以下のようになればOKです。   ■ AIエージェントに指示をする これでAIエージェントは完了したので、AIエージェントに指示をしてみましょう。デバックとプレビューの画面で以下のように入力します。 RAGの情報がほしい   ■ 結果を確認する インターネットを検索した結果と、その検索元のサイトのURLのQRコードが表示されていることがわかります。   どうでしょうか?Difyを使ってAIエージェントを作成することができました。AIエージェントを使うことで、従来のアプリケーション開発よりも柔軟で拡張性の高いアプリを簡単に作成することができます。さらにDifyを使えば、AIエージェントの作成も簡単に行うことができます。ぜひ、Difyを使ってAIエージェントを作成してみてください。 第13章: AIエージェントに独自機能を追加してみよう 先ほど作成したAIエージェントに機能を追加してみましょう。先ほどはDifyに標準で提供されているツールを使って、インターネットを検索して情報を取得し、QRコードを生成するという処理を行いました。本来であれば、AIエージェントに与えるツールの追加はプログミングによってゼロから開発しなければいけませんが、Difyが標準で提供しているツールを使えば、ノーコードでツールを利用でき、AIエージェントがサクッと作れます。 しかしながら、標準で提供されているツールでは対応しきれないことが少なからずあります。 例えば、社内のシステムと連携して特定の処理を行いたいという場合もあるでしょう。社内にあるデータベース内の情報をAIエージェントに与えたいという場合です。「XXXの情報を検索して」とAIエージェントに伝えたら、インターネットへの情報検索に加えて、社内のデータベースを検索して独自ナレッジを取得できたら更に便利です。Difyの標準のツールで提供されていないSaaSサービスにアクセスしたいという場合もあると思います。例えば、ある特定のWordPressのサイトの記事を検索できたら便利です。 Difyにはそのようなニーズを実現するための機能が用意されています。それが「カスタムツール」です。 カスタムツールとは? カスタムツールとは、Difyの標準ツールにはない機能を追加するためのツールです。カスタムツールを使うことで、AIエージェントに独自の機能を追加することができます。カスタムツールを使うことで、AIエージェントに与えるプロンプトに対して、標準ツールでは対応できない処理を追加することができます。 その仕組を図解したのが以下になります。   図中には組み込みツールの他に「カスタムツール」があります。これが今回のキモなのですが、カスタムツールとして追加したいサービスはWeb API(RestではなくてもOK)で公開している必要があり、さらにそのOpenAPIのスキーマをDifyに登録する必要があります。登録したOpenAPIのスキーマを元に、Difyが自動でAPIクライアントを生成し、そのAPIクライアントを使ってカスタムツールを作成します。 SaaSサービスの場合は、大抵の場合はWeb APIが公開されているので、そのAPIを使ってカスタムツールを作成することができます。社内のシステムの場合は、APIが公開されていないこともあるかもしれませんので、その場合はそのAPIを作り込む必要はあります。 つまり、OpenAPIのスキーマを登録すれば、カスタムツールもノーコードで作成できるというわけです。 コラム: OpenAPIとは API(Application Programming Interface)は、異なるシステムやアプリケーション同士がデータをやり取りするためのルールを定めたものです。しかし、APIの仕様が開発者ごとにバラバラだと、使い方を理解するのが大変になってしまいます。そこで登場するのが OpenAPI です。 ■ OpenAPIとは? OpenAPIは、APIの設計を標準化し、分かりやすく定義するための仕様 です。かつて「Swagger」と呼ばれていた技術が発展し、現在は OpenAPI Initiative(Linux Foundation 傘下の団体)が管理しています。 簡単に言えば、APIの取扱説明書を機械が理解できる形式で書くためのルール です。OpenAPIを使うことで、APIのリクエストやレスポンスの仕様を明確にし、開発者が迷わず利用できるようになります。 ■ OpenAPIを使うメリット – APIの設計が統一され、わかりやすくなる – 自動でドキュメントを生成できる(Swagger UI など) – コードの自動生成ができ、開発を効率化 – APIの動作をテストしやすくなる たとえば、次のような OpenAPI 定義を作成すると、自動でAPIのドキュメントを作ったり、プログラムのひな形を生成したりできます。 openapi: 3.0.0 info: title: サンプル API version: 1.0.0 paths: /hello: get: summary: 挨拶を取得 responses: 200: description: 成功時のレスポンス content: application/json: schema: type: object properties: message: type: string example: "こんにちは!" このように、OpenAPIを活用すると、APIの仕様が一目で分かるだけでなく、ドキュメントの自動生成やテストの自動化ができるため、開発効率が大幅に向上 します。 APIを設計・開発する際は、ぜひ OpenAPI を活用してみましょう! カスタムツールを利用したAIエージェントの作成 それでは、カスタムツールを利用してAIエージェントに独自機能を追加してみましょう。「AIエージェントを作ってみよう」で作成したAIエージェントに機能を追加してみます。以下のような構成図となります。     「AIエージェントを作ってみよう」で作成したAIエージェントは、ユーザーに依頼された情報をインターネットで検索し、情報検索元のサイトのURLのQRコードを表示するものでした。これにWordPressの記事を検索する機能を追加してみましょう。 つまり、ユーザーが「RAGに関する情報が知りたい」とAIエージェントに伝えたら、インターネット検索に加えて、WordPressの記事を検索して情報を取得し、その情報提供元のサイトのURLのQRコードを表示するという処理を行います。 WordPressはRest APIを公開しているので、そのAPIを使ってカスタムツールを作成します。いくつかいろんなAPIを公開していますが、今回は記事を検索するAPIを使います。APIの仕様は以下のとおりです。 ■ エンドポイント: GET /wp-json/wp/v2/posts ■ リクエストヘッダー: Content-Type: application/json ■ クエリパラメータ: search: 検索キーワード ■ ステータスコード: 200: 成功 ■ レスポンス: [   { "title": { "rendered": "記事タイトル" }, "link": "記事のURL" }, ... ] ホスト名がhoge.example.com、RAGという単語に関する記事を検索したい場合、APIのリクエストURLとレスポンスは以下のようになります(実際のレスポンスにはもっといろんなフィールドが出力されますが、説明の都合上割愛しています)。 ■ APIのリクエストURL https://hoge.example.com/wp-json/wp/v2/posts?search=検索キーワード ■ レスポンス [ { "title": { "rendered": "RAGについて" }, "link": "https://hoge.example.com/rag" }, ... ] では、早速作ってみましょう。 ■ ツール追加画面を開く AIエージェントに追加するナレッジを作成するために、ナレッジ追加画面を開きます。「ツール」(①)→「カスタム」(②)→「カスタムツール」(③)の順にクリックします。   ■ ツールの名前を入力する ツールを一意に識別する名称を入力します。なんでもいいです。ここでは「WordPress検索」ツールとします。   ■ OpenAPIのスキーマを入力する OpenAPIのスキーマを入力します。 ![]( image_ch12_050.png ) 以下のOpenAPIのスキーマを入力してください。これはWordPressの記事を検索するAPIです。 openapi: 3.0.0 info: title: SIOS Tech-Lab Posts API description: "のりのAzureブログの記事を検索するAPI" version: 1.0.0 servers: - url: https://hoge.example.com/wp-json/wp/v2 paths: /posts: get: summary: "検索ワードに一致するブログ記事を取得" description: "指定した検索ワードに一致するブログ記事の一覧を取得します。" parameters: - name: search in: query required: true description: "検索したいワード" schema: type: string responses: "200": description: "検索結果の一覧" content: application/json: schema: type: array items: type: object properties: link: type: string format: uri description: "記事のURL" title: type: object properties: rendered: type: string description: "記事のタイトル" "400": description: "不正なリクエスト" "500": description: "サーバーエラー" APIの詳細な仕様は以下のURLを参照してください。 https://developer.wordpress.org/rest-api/reference/pages/ コラム: APIの認証 カスタムツールには、呼び出すAPI側に認証が設定されている場合、その認証情報を設定する必要があります。今回ご紹介したWordPressのAPIには認証は必要ありませんが、認証が必要になる場合も多いです。Difyのカスタムツールが送信できる認証情報のタイプは、以下のような方法があります。 ■ ベーシック認証(Basic Authentication) ユーザー名とパスワードを組み合わせて認証します。HTTPヘッダーに Authorization: Basic {base64_encoded_username_password}を設定します。{base64_encoded_username_password} は ユーザー名:パスワードをBase64 エンコードしたものになります。 Difyでの設定方法は以下のとおりです。 (1) 「APIキー」→「ベーシック」を選択。 (2)「キー」に Authorization を入力。 (3)「値」に Base64エンコードされた資格情報を入力。 ■ ベアラー認証(Bearer Authentication) APIトークンを使用する認証方式になります。Authorization: Bearer {access_token} をHTTPヘッダーに設定します。 Difyでの設定方法は以下のとおりです。 (1) 「APIキー」→「ベアラー」を選択。 (2)「キー」に Authorization を入力。 (3)「値」に アクセストークンを入力。 ■ カスタム認証(Custom Authentication) APIによっては、独自の認証方式を持っている場合があります。その場合は、APIの仕様に従って認証情報を設定します。 Difyでの設定方法は以下のとおりです。 (1) 「APIキー」→「カスタム」を選択。 (2)「キー」に APIの仕様に従ったキーを入力。 (3)「値」に APIの仕様に従った値を入力。 ■ プロンプトを修正する AIエージェントに与えるプロンプトを修正します。   以下のように入力します。 「〜の情報がほしい」といったような検索依頼があったら、以下のことを実施してください。 - 依頼された内容に関する情報をインターネットで検索する。 - のりのAzureブログの記事を検索する。 - 検索の結果、抽出されたサイトすべてのURLのQRコードを作成する。 最初に定義したプロンプトに加えて、「のりのAzureブログの記事を検索する」という処理を追加しています。これは、先ほど作成したカスタムツールを使ってWordPressの記事を検索するための指示です。 そして、「検索の結果、抽出されたサイトすべてのURLのQRコードを作成する。」は、インターネットを検索した結果に加えて、WordPressの記事を検索した結果に対しても、そのサイトのURLのQRコードを作成するための指示です。 ■ カスタムツール追加画面を表示する 先ほど作成したカスタムツールを追加するために、カスタムツール追加画面を表示します。「追加」をクリックします。    ■ カスタムツールを追加する 「カスタム」をクリックして(①)、「WordPress検索ツール」の「posts_get」隣りにある「+追加」をクリックします(②)。   ■ 動作確認を行う 「Azureの情報がほしい」と入力して、AIエージェントに指示を出します。すると、QRコードを表示してくれます。   さらに、インターネットを検索した情報に加えて、WordPressのブログを検索した結果も答えてくれています。 第14章: ブロック ここでは、Difyが提供する様々なブロックの説明をします。 LLM LLMに接続してプロンプトを発行し、その結果を得るためのブロックです。   ■ モデル LLMに接続するためのモデルを選択します。モデルごとに詳細なパラメターを設定することができます。   ■ コンテキスト LLMが回答を生成する際に参照する情報(コンテキスト)を設定します。主に後述する「知識取得」のブロックとセットで使われることが多く、知識取得ブロックで取得した情報をコンテキストとしてLLMに渡すことができます。つまり、知識取得で取得した情報を元に、LLMは回答を生成します。   知識取得は、resultという出力変数に結果を格納します。LLMブロックはその出力変数resultをコンテキストとして参照します。 ■ メッセージ LLMに渡すプロンプトを設定します。プロンプトにはシステムメッセージ、ユーザーメッセージ、アシスタントメッセージの3種類があります。 システムメッセージ モデルの振る舞いを決めるメッセージです。例えば「あなたは親切なAIアシスタントです」と設定すると、モデルはその指示に従って返答しようとします。対話のトーンやルールを決める役割があります。 ユーザーメッセージ ユーザーが入力するメッセージです。実際の質問や指示がここに含まれます。例えば「今日の天気は?」と送ると、LLMはその情報をもとに応答を生成します。 アシスタントメッセージ LLMが返答するメッセージです。ユーザーメッセージに対する回答がここに含まれます。例えば「今日の東京の天気は晴れです」といった形で返されます。   ■ ビジョン この機能を有効にすると、LLMに送るプロンプトに画像を含めることが可能です。ただし、この機能はgpt-4oなどのマルチモーダルLLMをモデルとして選択している場合にのみ有効です。 知識取得  ナレッジに対して検索を行いその結果を出力するためのブロックになります。 第11章: チャットフローを作ってみよう でも使いました。 以下の図の左側は知識取得の流れを表しており、右側は知識取得ブロックの設定画面になります。   知識取得は、まずInputとして例えばユーザーの質問があります。ここでは、「有給は何日取得できる?」という質問をInputとして設定しています。そのInputをもとにナレッジを検索して、Outputとして検索結果を出力します。 ■ クエリ変数 先の図のナレッジに対するInputである「有給は何日取得できる?」がクエリ変数になります。クエリ変数は、知識取得ブロックに入力された検索クエリを表します。前のブロックの出力結果である任意の変数を受け取ることができます。 ■ ナレッジ ナレッジを追加する画面です。いわゆる社内規約などあらかじめナレッジとして登録したものを設定します。 ■ 出力変数 このブロックが出力する変数の形式を表します。resultという配列にcontent、title、url、icon、metadataの5つの要素を持つオブジェクトを格納します。 以下がその変数の出力例になります。 results: [ { "metadata": { "_source": "knowledge", "dataset_id": "cdf159c1-5a94-45bb-941d-400614336357", ...以下省略... }, "title": "001018385.pdf", "content": "① 年次有給休暇を取得した期間\r\n② 産前産後の休業期間\r\n③ 育児・介護休業法に基づく育児休業及び介護休業した期間\r\n④ 業務上の負傷又は疾病により療養のために休業した期間\r\n7 付与日から1年以内に取得しなかった年次有給休暇は、付与日から2年以内に限り\r\n繰り越して取得することができる。\r\n8 前項について、繰り越された年次有給休暇とその後付与された年次有給休暇のいず\r\nれも取得できる場合には、繰り越された年次有給休暇から取得させる" }, ...以下省略... ] 質問分類器 質問分類器は、任意のブロックから入力されたデータを受け取り、その質問をLLMが解析をし、特定のブロックにルーティングする機能を持ちます。下図を例に説明します。   例えば、何らかの任意のブロックから「有給は何日取得できる?」というデータが質問分類器に入力されたとします(①)。このブロックは例えばチャットフローであれば、開始のブロックに相当します。つまり、ユーザーが入力した質問などです。 そのデータを受けて、質問分類器に設定したLLMは、クラス(質問をカテゴリ分けするための説明が入力されたもの)の内容と照らし合わせて、質問がどのクラスに該当するかを判断します(②〜③)。この図の例で言えば、「有給は何日取得できる?」という質問は、クラス2の内容(社内規定に関すること)にマッチするので、クラス2にカテゴリ分けされます。 各クラスは事前にルーティング先の任意のブロックとの紐づけを行っておき、LLMが判断したクラスに基づいて、そのルーティング先のブロックに任意のデータを送ります(④)。   以降では各設定項目について説明します。 ■ モデル LLMが度のクラスに分類するかを判断するためのモデルを選択します。 ■ 入力変数 質問分類器に入力されるデータを設定します。前のブロックの出力結果である任意の変数を受け取ることができます。 ■ クラス 質問分類器が分類するクラスを設定します。クラスは、質問をカテゴリ分けするための説明が入力されたものです。例えば、クラス1は「天気に関する質問」、クラス2は「社内規定に関する質問」などです。 ■ ビジョン この機能を有効にすると、質問分類器に画像を含めることが可能です。ただし、この機能はgpt-4oなどのマルチモーダルLLMをモデルとして選択している場合にのみ有効です。 ■ 高度な設定 質問分類器に入力されたデータ(例えばユーザーの質問など)をLLMが解析してどのクラスに分類するかを決定する際、補足の指示をLLMに対してすることができます。 例えば下図では、「「謎の製品X」について聞かれた場合は、商品に関する質問だけど、クラス4に分類してください。」としています。こうすると、「製品Xについて教えて」というと、クラス4に分類されて、「わかりません」と回答します。製品に関する質問には答えるけど、ある特定の製品については回答させたくないといったことを想定しています。   ■ 出力変数 このブロックが出力する変数は、以下のように各クラスに設定した説明を出力します。 { "class_name": "商品に関するもの", "class_id": "1" } IF/ELSE 条件分岐を行うためのブロックです。プログラミングのif文やelse文に相当します。 ここでは以下の図の様に、チャットフローで質問文に「機密」という文字が含まれていたら、「機密情報です!!」と回答し、それ以外だったら「機密ではありません」と回答するという例を説明します。   まず条件文を設定します。①は条件式の左辺の部分です。sys.queryつまりユーザーの質問文が格納されている変数になります。 演算子は「含む」を選択します(②)。図にあるように様々な演算子を設定できます。 右辺の部分には「機密」という文字列を入力します(③)。 ④では、それぞれの条件の場合に次に遷移するブロックを指定します。ここでは最初のIF文にマッチした場合は「回答1」というブロック、それ以外は「回答2」というブロックに遷移するように設定しています。 またもっと複雑な条件式も設定できます。以下の図は、ユーザーの質問が、「機密」もしくは「秘密」もしくは「ひ・み・つ」のいずれかを含む場合は「機密情報です!!」と回答し、「ほげ」だったら「意味不明です」と回答し、それ以外だったら「機密ではありません」と回答する例です。   イテレーション イテレーションは、繰り返し処理を行うためのブロックです。プログラミングのfor文に相当します。   任意のブロックから配列に格納された複数のデータがイテレーションのブロックに送られたとします(①)。 イテレーションの設定画面では、①で送られてきたデータを順次処理する任意のブロックを定義しておき、そのブロックでデータを処理します(②)。 最終結果を任意のブロックに出力します(③)。 例えば以下のケースを考えてみましょう。ユーザーはイチゴと人物の顔写真の2つのファイルをアップロードし、その画像の特徴をLLMブロックで処理して、その結果を結合して、最終的に回答のブロックに出力します。   実例をお見せするのが早いので、設定の手順を説明しながら進めていきます。 ■ 開始ブロックを定義する チャットフローの開始ブロックにて、「ファイルリスト」を設定します(①)。変数名は「files」として(②)、ファイルの種別は「画像」に設定します(③)。これで画像をアップロードすることができるようになります。   ■ イテレーションブロックを定義する 次にイテレーションブロックを定義します。イテレーションブロックには、①で定義した「ファイルリスト」の変数「files」を入力します。これで、イテレーションのブロックに送られるデータが設定されました。 送られてきたデータを順次処理するためのブロックを定義するために、「ブロックを追加」をクリックします(②)。   ■ LLMブロックを定義する イテレーションブロックに送られてきたデータを処理するためのブロックを定義します。ここではLLMブロックを定義します。 USERのプロンプトに「この画像の特徴を一言で表して」と入力します(①)。 「ビジョン」を有効にして、画像を含めることができるようにします(②)。   そして、画像の入力は「現在のイテレーション」の「{x} item」に設定します(③)。こうすることで、イテレーションでぐるぐる回したそれぞれの画像を一つずつLLMブロックの入力にすることができます。   ■ イテレーションブロックの出力変数を定義する LLMブロックの出力結果をイテレーションブロックの出力変数に設定します。この様に設定すると、複数のLLMブロックの出力結果を結合したものを出力することができます。   ■ 回答ブロックを定義する 回答のブロックを定義します。ここでは、LLMブロックで処理した画像の特徴を結合して、最終的な回答を出力します。   ■ 最終的なチャットフローを確認する 以下のようなチャットフローになっていることを確認します。   ■ 動作確認を行う 適当な画像をアップロードして動作確認を行います。プロンプトは何でも構いません。ここでは人物の写真とイチゴの画像をアップロードしています。   確かにアップロードした画像の特徴が出力されています。 コード 任意のPythonコードもしくはJavaScriptが動かせるブロックです。   上図のブロックの詳細な図解は以下の通りとなります。他のブロックと同じように入力変数を定義します(①)。変数の値は、他のブロックからの出力結果を受け取ることができます(②)。入力変数の名前は、コード内で定義したmain関数の引数名として使われます(③)。そして、戻り値の変数名とその値を定義します(④)。戻り値の変数名と出力変数の名前は、同じである必要があります(⑤)。 テンプレート Jinjaテンプレートによるテキストの生成を行うためのブロックです。これは先に説明したコードブロックと非常に似通った機能です。Python/JavaScriptコードブロックがプログラム言語による処理を行うのに対し、テンプレートブロックはJinjaテンプレートエンジンによるテキストの生成を行います。   このテンプレートでは、入力変数にarg1という変数を定義しています(①)。そして、テンプレート内でarg1を使ってテキストを生成します(②)。最後に、生成したテキストを出力変数outputに格納します(③)。 コラム: Jinjaテンプレートとは? テンプレートエンジンとは、あらかじめ決まった形式のテンプレートに、変数やデータを差し込むことで動的なコンテンツを生成するためのツールです。テンプレートエンジンを使うことで、同じテンプレートを使っても、異なるデータを埋め込むことで、異なるコンテンツを生成できます。これにより、同じ構造のコンテンツを繰り返し生成する際に、手作業でコンテンツを作成する手間を省くことができます。 例えば、ウェブアプリケーションで複数のユーザーに「こんにちは、[ 名前 ]さん」というメッセージを表示する場合、テンプレートエンジンを使えば、一つのテンプレートに「[ 名前 ]」の部分だけを差し替えて、何度も異なるメッセージを生成できます。これにより、同じ形式の文書やHTMLページを効率的に作成できるのです。 Difyでも使われるJijnja2の例を見てみましょう。Jinja2は、Pythonのウェブアプリケーションなどでよく使われるテンプレートエンジンです。Jinja2では、テンプレートの中に変数や制御文を埋め込むことができ、動的なページやコンテンツを生成します。 以下のようなテンプレートを考えてみましょう。 <h1>こんにちは、{{ name }}さん!</h1> <p>今日は{{ date }}です。</p> このテンプレートに対して、Pythonコードからデータを渡してレンダリングします。 from jinja2 import Template template = Template('<h1>こんにちは、{{ name }}さん!</h1><p>今日は{{ date }}です。</p>') output = template.render(name='武井', date='2024年9月15日') print(output) このコードを実行すると、以下のようなHTMLが生成されます。 <h1>こんにちは、武井さん!</h1> <p>今日は2024年9月15日です。</p> このように、テンプレートエンジンを使うことで、変数を埋め込んで動的にコンテンツを生成することができます。 変数集約器 複数の変数を集約して一つの変数にまとめるためのブロックです。例えば、 第11章: チャットフローを作ってみよう で作成したチャットフローの例を使って説明します。以下のようになっていました。   質問分類器で、ユーザーのクエリを分類して、適切な知識取得ブロックへルーティングし、その結果をLLMブロックで処理して、最終的に回答ブロックに出力するというチャットフローを作成しました。 この組み方だと、知識取得ブロックが増える、つまり参照したいナレッジが増えれば増えるほど、LLMブロックと回答ブロックが増殖していき、可読性も保守性も悪いフローになります。今回はまだ2つだからいいですが、これが10個20個になると、その煩雑さは想像に難くありません。 変数集約器を使うと、以下のようにスッキリします。つまり、知識取得ブロックの出力結果を変数集約器で集約して、LLMブロックに一つの変数として渡すことができます。   これも実際に作ってみることで理解を進めてみましょう。 一旦、すべてのLLMブロックと回答ブロックを削除して、以下のようにします。   ■ 変数集約器を追加する 知識取得ブロック(どちらでもいいです)の隣の「+」をクリックして(①)、「変数集約器」をクリックします(②)。   ■ 他の知識取得ブロックと変数集約器を接続する もう一つの知識取得ブロックを変数集約器に接続します。知識取得ブロックの+をクリックして(①)、「変数集約器」にドラッグアンドドロップします(②)。   ■ 変数を代入する 知識取得ブロックの出力結果を一箇所にまとめるために、変数集約器に変数を代入します。「+」をクリックして(①)、「知識取得」の出力変数であるresultをクリックします(②)。知識取得2のブロックについても同様に、resultをクリックします(③)。   ■ 変数集約器の設定を確認する 以下の図のようになっていることを確認します。この様になっていれば、2つの知識取得の結果を変数集約器でまとめることができました。   ■ LLMブロックに変数集約器を接続する LLMブロックを作成して、変数集約器に接続します。LLMブロックの設定方法は 第11章: チャットフローを作ってみよう とほぼ同じなので、詳細は割愛します。   違うところは、コンテキストです。ここは知識取得ブロックの出力変数ではなく、変数集約器の出力変数を選択します。これは、知識取得ブロックの出力を変数集約器で集約したからです。 ■ 回答ブロックを作成する 回答ブロックを作成します。回答ブロックの設定方法は 第11章: チャットフローを作ってみよう とほぼ同じなので、詳細は割愛します。   ■ フローを確認する フロー全体を確認します。だいぶスッキリしましたね。 テキスト抽出ツール テキスト抽出ツールは、WordやPDFからテキストを抽出するためのブロックです。例えば、PDF形式のファイルからテキストを抽出して、それをLLMブロックに渡して要約させたり、コンテキスト(LLMが回答を生成するための参照情報)にすることもできます。 以下のフローは、ユーザーがチャットの入力フォームからアップロードしたファイルを、テキスト抽出ツールブロックでテキストに変換し、LLMブロックでそれを要約させて、回答ブロックに渡して出力する簡単なチャットフローです。 テキスト抽出ツールブロックの入力変数に、ファイルを入力してあげるだけで、あとは特別な設定をせず、次のブロックに出力変数を渡せば、テキスト抽出が完了します。 変数代入 変数代入とは会話変数に値を代入できる機能を持つブロックのことです。ここで会話変数という新しい言葉が出てきたので説明します。 会話変数とは、一つの会話が始まってから終わるまで、ずっとその値が消えることなく保持され続ける変数のことです。ここでいう「会話」とは以下のように「New Chat」をクリックして新しい会話を開始するか、ブラウザを閉じたりするなどして、会話が終了するまでの間のことです。下図の様にAIと会話のキャッチボールを続けている限り、会話変数は保持され続けます。   会話変数は、例えばユーザーが最初に選択した言語や、ユーザーが最初に入力した名前など、会話の中で使いたい情報を保持するのに便利です。 ここでちょっと簡単なユースケースに基づいて会話変数を説明します。会話を開始して、AIが行う初めての回答のときには「初めてですね」と回答して、2回目以降のときには「2回目以降ですね」と回答するというフローを作成してみましょう。下図はそのフローのイメージ図です。   まず最初に会話変数is_first_messageを定義して0を代入します。この変数は、会話が始まったときに初期化され、その後の会話の中で保持され続けます。 そして、ユーザーが「こんにちは」と入力します。実際にはここはこんばんはでもこんばんみでも何でもいいです。 次に、IF/ELSEブロックで会話変数is_first_messageが0かどうかを判定します。もし0だったら「初めてですね」と回答する回答ブロックに移動します。そして、会話変数is_first_messageに1を代入します。この会話変数is_first_messageに1を代入するのを担当するのが、変数代入ブロックです。 IF/ELSEブロックで会話変数is_first_messageが1だったら「2回目以降ですね」と回答する回答ブロックに移動します。 そして、またユーザーの入力を待ちます。 これを実現するワークフローを作ってみましょう。 ■ 会話変数を定義する 会話変数を定義します。画面上部の会話変数マークのアイコンをクリックして(①)、「+変数を追加」をクリックします(②)。 「名前」には、変数名is_first_messageを入力します(③)。「タイプ」は数字なのでnumberを選択します(④)。そして、初期値に0を入力します(⑤)。 最後に「保存」をクリックします(⑥)。   ■ IF/ELSEブロックを定義する IF/ELSEブロックを定義します。条件式は、会話変数is_first_messageが0かどうかを判定します。   ■ はじめましての場合の回答ブロックを定義する IF/ELSEブロックの条件が真だった場合の回答ブロックを定義します。ここでは「初めてですね」と回答します。   ■ コードブロックを定義する はじめましての場合の回答ブロックにコードブロックを接続して定義します。引数なしで、単純に1という数値だけを返すコードを入力します(①)。出力変数は、コードの戻り値と同じNumber型の変数を定義します(②)。 ここでコードブロックを定義する理由は、このあと定義する変数代入ブロックでは、固定値を代入できず、他のブロックの出力結果を代入する必要があるためです。   ■ 変数代入ブロックを定義する 変数代入ブロックを定義します。変数代入ブロックは、コードブロックの出力結果を会話変数is_first_messageに代入します。   ■ 2回目以降の場合の回答ブロックを定義する 2回目以降の回答ブロックを定義します。IF/ELSEブロックのELSEの部分に接続します。   ■ フロー全体を確認する フロー全体を確認します。この様になっていればOKです。   ■ 動作確認をする 動作確認をします。たしかに初回は、「初めてですね」と回答し、それ以降は「2回目以降ですね」と回答しています。 HTTPリクエスト HTTPリクエストブロックは、外部のAPIを呼び出すためのブロックです。例えば、外部のAPIを呼び出して、その結果をLLMブロックに渡して回答を生成するといったことができます。 ■ HTTPリクエスト ①は、リクエストの種類を選択します。GET、POST、PUT、DELETEなどのHTTPリクエストメソッドを選択できます。 ②は、リクエスト先のURLを入力します。 ③は、リクエストヘッダを設定します。ヘッダは、リクエストに関する情報を含む部分です。例えば、リクエストの種類やリクエストの送信元などが含まれます。 ④は、クエリパラメーターを設定します。クエリパラメーターは、リクエストに含めるパラメーターを指定します。例えば、検索クエリやページ番号などが含まれます。 ⑤は、リクエストボディを設定します。リクエストボディは、リクエストに含めるデータを指定します。例えば、フォームに入力されたデータやJSON形式のデータが含まれます。 この図の例では、postmanが用意してくれているAPIを呼び出しています。GETメソッドで呼び出したリクエストをオウム返ししてくれるAPIです。api-keyというリクエストヘッダにhoge、searchというクエリパラメーターにパスタと指定しています。   ■ HTTPレスポンス 以下がその実行結果です。確かにHTTPリクエストで指定した通りのレスポンスが返ってきています。   ■ 出力変数 出力変数は以下の通りとなります。 status_code: HTTPステータスコード headers: レスポンスヘッダ body: レスポンスボディ files: ファイル(multipart/form-dataの形式でバイナリデータを送信する場合に使用) パラメーター抽出 パラメーター抽出ブロックは、非構造化データを構造化データに変換してくれます。例えば、任意のテキストを与えるとLLMがそれを解析して、あらかじめ指定した変数の中にデータを入れてくれます。   ユースケースとしては、ユーザーが入力したテキストを解析して、その中から特定の情報を抽出するといったことが考えられます。例えば、ユーザーが「今日はレストラン『タベルナ』でパスタを食べた」と入力したときに、「レストラン名」に「タベルナ」、「料理名」に「パスタ」という情報を抽出して、レストラン名をAPIに渡して場所を取得するといった事ができます。 今回は以下のようなチャットフローを作成してみましょう。   ユーザーは、「今日はレストラン『タベルナ』でパスタを食べた」と入力します。そのテキストをパラメーター抽出ブロックで解析して、restaurantという変数に食べたお店の名前を入力するようにパラメーター抽出ブロックに指示します。そして、それをHTTPリクエストブロックに渡して、レストラン名をAPIに渡して場所を取得します。今回は、 HTTPリクエスト のブロックで紹介したPostman Echo APIを使って、レストランの名前を渡して、オウム返しされてくるレストラン名を確認します。 ■ パラメーター抽出ブロックを定義する パラメーター抽出のブロックを定義します。 「モデル」には、パラメーター抽出対象のテキストや画像などを解析するためのモデルを指定します(①)。 「入力変数」には、パラメーター抽出対象のテキストや画像などを指定します(②)。ここでは、開始のブロックでユーザーが入力したテキストを指定します。 「パラメーターを抽出」の項目にある「+」をクリックして(③)します。   ■ パラメーターを追加する 「restaurant」という変数を追加します(①)。 「タイプ」はパラメーターの型を指定します。ここでは、文字列(String)を指定します(②)。 「説明」は、パラメーターの説明を入力します(③)。ここで入力した説明と、先ほど入力変数に指定したテキストを解析して、その中からパラメーターを抽出します。なので、この説明は、パラメーターを抽出するためのヒントとなります。 最後に「追加」をクリックします(④)。   ■ HTTPリクエストブロックを定義する HTTPリクエストブロックを定義します。HTTPリクエストブロックの設定方法は HTTPリクエスト のブロックとほぼ同じなので、詳細は割愛します。 異なる点は、クエリパラメーターsearchに、パラメーター抽出ブロックで抽出したrestaurantを指定しているところです。   ■ フロー全体を確認する 以下のようになっていればOKです。   ■ 動作確認をする プレビューの画面で、「今日はレストラン『タベルナ』でパスタを食べた」と入力して、そのトレースを確認してみます。   HTTPリクエストのトレースの出力を確認してみたところ、確かにパラメーター抽出ブロックで抽出したrestaurantの値が、HTTPリクエストのレスポンスに含まれていることがわかります。 第15章: チャンキング 本章では、チャンキングについて解説します。チャンキングについては、「 第9章: RAGを作ってみよう 」の節で触れましたが、ここでは更にその設定の詳細について解説します。 Difyでのチャンキングの手法には、大きく分けて「汎用」と「親子」の2つがあります。 ■ 汎用 汎用のチャンキングは、あらかじめ決められた固定長でドキュメントを分割する手法です。例えば、1000文字ごとに分割するといった設定ができます。「 第9章: RAGを作ってみよう 」の節で紹介したチャンキングは、この汎用のチャンキングに該当します。 ■ 親子 親子のチャンキングは、固定長で分割したチャンク(これを親チャンクと呼びます)をさらに分割して、子チャンクを作ります。親チャンクと子チャンクは紐づけを行っており、検索を行う場合は、子チャンクに対してベクトル検索を行い、それに紐づく親チャンクをコンテキストとしてLLMに与えます。 では、以降では、これらのチャンキング手法について詳しく解説します。 汎用 これは非常に馴染み深い方法かと思います。先ほど説明したように、あらかじめ決められた固定長でドキュメントを分割する手法です。例えば、1000文字ごとに分割するといった設定ができます。「 第9章: RAGを作ってみよう 」の節でも紹介しましたが、そこでは説明しきれなかった項目について、ここで詳しく解説します。 設定画面をもとに、どのようにチャンキングを設定するかを見ていきましょう。   一番重要なのは、「チャンク識別子」「最大チャンク長」「チャンクのオーバーラップ」です。以下の図を例に説明します。   元のドキュメントをまず、チャンク識別子で分割します。この例では、チャンク識別子を「。」に設定しているので、文の終わりで分割されます。そして、最大チャンク長で設定した値に収まるまで、チャンクを結合します。よって、一つのチャンクは最大チャンク長に収まります。 前後のチャンクはある程度、重複していることがあります。この重複部分をオーバーラップと呼びます。このオーバーラップを設定することにより、チャンク間の関連性を保つことができます。オーバーラップのサイズは、「チャンクのオーバーラップ」で設定します。 「連続するスペース、改行、タブを置換する」は、チェックするとドキュメント内の連続するスペース、改行、タブを削除します。 「すべてのURLとメールアドレスを削除する」は、チェックするとドキュメント内のURLとメールアドレスを削除します。メールアドレスなどの個人情報を含むテキストをチャンクに含めたくない場合に便利です。 「チャンクをプレビュー」をクリックすると、以下のようにチャンクがどのように分割されるかを確認することができます。 親子 チャンクサイズは、検索や生成AIの精度に大きな影響を与えます。 チャンクサイズが大きすぎる場合は、不要な情報(ノイズ)が含まれ、意図した検索が難しくなります。一方、チャンクサイズが小さすぎる場合は、必要な文脈が失われ、適切な情報を取得できないことがあります。 一方で、チャンクサイズが小さすぎると、必要な文脈が失われ、適切な情報を取得できないことがあります。 実例を見てみましょう。例えば以下のような文章を考えてみます。 生成AIを活用することで、業務の効率化が図れる。 特にRAG(Retriever-Augmented Generation)を用いることで、文書検索がより精度よく行える。 Azure OpenAI Serviceを活用すると、簡単に導入できる。 企業のナレッジ管理に役立つ。   ■ チャンクサイズが大きすぎる場合 極端な例ですが、この文章全体を一つのチャンクとして扱うと、以下のような問題が発生します。 【チャンク1】 生成AIを活用することで、業務の効率化が図れる。 特にRAG(Retriever-Augmented Generation)を用いることで、文書検索がより精度よく行える。 Azure OpenAI Serviceを活用すると、簡単に導入できる。 企業のナレッジ管理に役立つ。   「Azure OpenAI Service とは?」というクエリで検索した場合を考えてみます。 このチャンクには「Azure OpenAI Service」に関する記述はあるが、他の情報も多く含まれるため、検索時に関係のない「RAG」や「業務の効率化」などの情報が混ざってしまい、適切に検索されない可能性があります。 ■ チャンクサイズが小さすぎる場合 こちらも極端な例ですが、以下のように小さく分割してしまうと、以下のような問題が発生します。 【チャンク1】 生成AIを活用することで 【チャンク2】 業務の効率化が図れる。 【チャンク3】 特にRAG(Retriever-Augmented 【チャンク4】 Generation)を用いることで 【チャンク5】 文書検索がより精度よく行える。 【チャンク6】 Azure OpenAI Serviceを 【チャンク7】 活用すると、簡単に導入できる。 【チャンク8】 企業のナレッジ管理に役立つ。 「Azure OpenAI Service の活用方法」というクエリで検索した場合を考えてみます。 「Azure OpenAI Serviceを活用すると、簡単に導入できる。」が チャンク6とチャンク7に分割されているため、検索時に文脈が途切れ、適切な回答が得られない可能性があります。 このようなジレンマを解決するのが親子のチャンキングです。これは以下の図のような流れで行われます。   まず元のドキュメントをチャンク化します(①)。このチャンクを親チャンクと呼びます。 親チャンクをさらに小さいサイズにチャンク化して、子チャンクを作ります(②)。この子チャンクは、親チャンクと紐づいています。 検索を行う場合は、まずクエリをベクトル化します(③)。 そして、子チャンクに対してベクトル検索を行います(④)。 ④の結果、検索された最も関連性の高い子チャンクに紐づく親チャンクをコンテキストとして、LLMに与えます(⑤)。 そのコンテキストをもとに、LLMが回答を生成します(⑥)。 この方法であれば、必要十分な情報を持っている子チャンクに対して検索を行って正しい情報を取得しつつ、LLMに与えるコンテキストは、十分な情報を持っている親チャンクになるため、適切な回答を得ることができます。 親子のチャンキングを設定する方法については、以下の図を参考にしてください。   ①は、親チャンクのチャンク識別子と最大チャンク長を設定します。これは汎用のチャンキングと同じです。もう一つ「全文」というのがあります。これは親チャンクを分割しないで一つの大きいチャンクとして扱う選択肢であります。ドキュメントのサイズが小さいときは有効に働く可能性があります。大きすぎると、10000トークンでぶった切られます。 ②は、子チャンクのチャンク識別子と最大チャンク長を設定します。これは当然ながら親チャンクよりも小さくなります。 ③は、テキストの前処理ルールであり、汎用のチャンキングと同じです。 ④は、チャンクをプレビューすることができます。以下がプレビューの画面になります。この例では、Chunk9が、C1〜C5の5つの子チャンクに分割されていることがわかります。 第16章: ナレッジ ナレッジには様々な機能があります。今まで紹介しきれなかった機能も含めて、ナレッジの機能を紹介します。 インデックス方法 インデックス方法には「高品質」「経済的」の2つのどちらかを選択できます。 経済的 こちらを選択すると、全文検索のみの検索方式になります。もともとDify自身が持っている検索エンジンやデータベースを使うのでコストはかかりません。だから「経済的」なんですね。 「検索設定」は全文検索の結果、スコアの高い順から、何件まで表示するかを設定します。 高品質 こちらは平たく言ってしまうと、お金はかかるけれど、より高精度な検索結果を出すことができる方法です。OpenAIなどが提供する「埋め込みモデル」によるベクトル検索や、Cohereなどが提供する「Rerankモデル」による検索結果の並べ替えを行うことにより、検索結果の精度を向上させます。   高品質のモードには「ベクトル検索」「全文検索」「ハイブリッド検索」の3つのモードがあります。以降では、それぞれを詳細に解説します。 ■ ベクトル検索 ベクトル検索を行う場合には、こちらを選択します。   まず、ベクトル検索に利用する埋め込みモデルを選択します(①)。 Rerankモデルの利用可否、およびRerankモデルを利用する際のモデルを選択します(②)。Rerankモデルとは、取得した検索結果を再評価(Rerank)し、より関連性の高い順に並べ替えるためのモデルになります。 この例では、以下の図のようにベクトル検索した結果をRerankモデルについて並べ替えている例です。これはWikipediaから取得したスター・ウォーズの登場人物の情報に対して、「アナキン・スカイウォーカーによって作成されて、とてもたくさんの言葉が話せる登場人物は?」というプロンプトを投げている例です。正解と思われる結果が、ベクトル検索では10位だったのに、Rerankモデルによって並び替えすることで2位に浮上していることがわかります。   トップK(③)は、ベクトル検索もしくはベクトル検索の結果をRerankモデルによって並べ替えた結果から、スコアの高い順に何件まで表示するかを設定します。 スコア閾値は、ベクトル検索もしくはベクトル検索の結果をRerankモデルによって並べ替えた結果から、スコアがこの値以上のものだけを表示するようにします。 先の図の例では、ベクトル検索の結果をRerankモデルによって並べ替えた結果から上位10件かつスコアが0.8以上のものだけを表示するように設定されています。 ■ 全文検索 全文検索を用いる場合はこちらを選択します。   Rerankモデル(①)はベクトル検索と同様、全文検索によって取得した検索結果を再評価(Rerank)し、より関連性の高い順に並べ替えるためのモデルになります。 トップK(②)は、全文検索もしくは全文検索の結果をRerankモデルによって並べ替えた結果から、スコアの高い順に何件まで表示するかを設定します。 スコア閾値は、全文検索もしくは全文検索の結果をRerankモデルによって並べ替えた結果から、スコアがこの値以上のものだけを表示するようにします。 ■ ハイブリッド検索 (ウェイト設定) ハイブリッド検索は、ベクトル検索と全文検索を組み合わせた検索方式です。ベクトル検索と全文検索の両方の利点を活かし、検索結果の精度を向上させることができます。前にご紹介したベクトル検索と全文検索を実施したうえで、その結果をこれから説明するハイブリッド検索の結果でさらに並べ替えるということを実施します。 なので、ハイブリッド検索をONにすると、(キーワード検索 + ベクトル検索) → ハイブリッド検索の順に実行されます。キーワード検索とハイブリッド検索はマルチスレッドで同時に実行され、そのスレッドの完了を待ったうえで、ハイブリッド検索がおこなれるという動きになっています。 この方式には「ウェイト設定」「Rerankモデル」という2つの機能があり、それぞれでスコアの算出方法が異なります。まずは「ウェイト設定」について説明します。   以下のクエリとドキュメントがあるとします。クエリからドキュメント1〜4を検索するとして、そのスコアをウェイト設定の場合で算出してみます。 クエリ: 革新的なAI技術とは何でしょうか? ドキュメント1: 最新のAI技術により、データベースが大幅に改善されました。 ドキュメント2: 当社はAI技術を活用し、データベースを統合、さらにPythonによる自動化を実現しています。 ドキュメント3: RAGを用いたシステムは、バナナの柔軟性とAIの知見を融合しています。 ドキュメント4: いちごとメロンとおまんじゅうの組み合わせが絶妙です。 まずは全文検索の場合のスコアを算出します。Difyの全文検索では、クエリとドキュメントのTF-IDFを求めて、その値のコサイン類似度を図ることでスコアの算出を行います。 クエリとドキュメント1〜4は、わかち書きを行った結果、以下の単語に分割されたとします。ちなみに、Difyで実際に以下のようになるかはわかりません。説明のための例になります。 クエリ:”AI”, “技術” ドキュメント1: “AI”, “技術”, “データベース” ドキュメント2: “AI”, “技術”, “データベース”, “Python” ドキュメント3: “RAG”, “バナナ”, “AI” ドキュメント4: “いちご”, “メロン”, “おまんじゅう” Difyのコード内で使われているIDFの式は以下のようになっています。   ドキュメントは4件あるので、各キーワードの出現数は下記のとおりです。 キーワード 出現ドキュメント数 AI 3(ドキュメント1,2,3) 技術 2(ドキュメント1,2) データベース 2(ドキュメント1,2) Python 1(ドキュメント2) RAG 1(ドキュメント3) バナナ 1(ドキュメント3) いちご 1(ドキュメント4) メロン 1(ドキュメント4) おまんじゅう 1(ドキュメント4) よってAIというキーワードのIDFは次のようになります。   同様に、技術、データベース、Python、RAG、バナナ、いちご、メロン、おまんじゅうのIDFも計算します。 技術: 1.5108 データベース: 1.5108 Python: 1.9163 RAG: 1.9163 バナナ: 1.9163 いちご: 1.9163 メロン: 1.9163 おまんじゅう: 1.9163 TFはすべて1となります。TF-IDFは、TFとIDFの積になりますので、クエリのTF-IDFは以下のようになります。 {“AI”:1.2231,”技術”:1.5108} これはクエリにおける「AI」という単語のTF-IDFと、「技術」という単語のTF-IDFの値をベクトルで表したものです。 同様に、ドキュメント1〜4のTF-IDFも計算します。 ドキュメント1: {“AI”:1.2231,”技術”:1.5108,”データベース”:1.5108} ドキュメント2: {“AI”:1.2231,”技術”:1.5108,”データベース”:1.5108,”Python”:1.9163} ドキュメント3: {“RAG”:1.9163,”バナナ”:1.9163,”AI”:1.2231} ドキュメント4: {“いちご”:1.9163,”メロン”:1.9163,”おまんじゅう”:1.9163} そして、クエリとドキュメント1〜4のコサイン類似度を計算します。コサイン類似度は、クエリとドキュメントのベクトルの内積を、クエリのベクトルの長さとドキュメントのベクトルの長さの積で割ったものになります。つまり以下の式で表せます。   では、まずクエリとドキュメント1のコサイン類似度を計算してみましょう。クエリとドキュメント1に含まれるキーワードは以下のとおりです。 クエリ: {“AI”, “技術”} ドキュメント1: {“AI”, “技術”, “データベース”} まず内積(分子)を計算します。共通の項は「AI」と「技術」の2つです。 numerator = (1.2231 × 1.2231) + (1.5108 × 1.5108) = 3.779 次に、各ベクトルのノルム(分母)を計算します。 クエリのノルム ドキュメント1のノルム そして、コサイン類似度を計算します。 小数点第3位を四捨五入すると、最終的なコサイン類似度は0.79となります。 同様に、クエリとドキュメント2〜4のコサイン類似度も計算した結果は以下となります。   次にベクトル検索の場合のスコアを算出します。ベクトル検索では、クエリとドキュメントのベクトルを用いてコサイン類似度を計算します。先ほどの例で言えば、クエリとドキュメント1〜4のコサイン類似度を計算し、その値をスコアとします。その結果は以下のようになります。   これでクエリと各ドキュメントのキーワード検索とベクトル検索をした結果のスコアが算出されたことになります。さらにウェイト設定で設定した値により、このスコアに重み付けをしていきます。以下の図では、ベクトル検索:キーワード検索=0.7:0.3という設定になります。セマンティック=ベクトル検索、キーワード=全文検索です。   ウェイト設定の値を係数としてかけたスコアは以下の通りとなります。   この場合ではベクトル検索のほうが検索精度が高いという前提で、ベクトル検索の重みを大きくしていますが、このあたりはドキュメントの内容などによって調整が必要な部分になります。 トップKとスコア閾値は、ウェイト設定を行った結果から、スコアの高い順に何件まで表示するかを設定します。キーワード検索や全文検索で説明した動きと同じです。 このようにして、ウェイト設定により、ベクトル検索と全文検索のスコアを組み合わせて、最終的なスコアを算出することができます。 ■ Rerankモデル こちらは、先に紹介した全文検索とベクトル検索の結果を組み合わせたものを、Rerankモデルによって再評価(Rerank)し、より関連性の高い順に並べ替えます。   流れを以下の通り図解しました。全文検索とベクトル検索をマージして重複ドキュメントを削除し、Rerankモデルによる並べ替えを行います。 まとめ いかがでしたでしょうか?Dify便利ですよね。まさに「AIの民主化」を実現するすごいソフトウェアだと思います。もはやAIは一部のスゴスゴエンジニアだけのものではありません。DifyをつかってもっとAIの利用を加速させましょうどすこい。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 世界一わかりみの深いDify first appeared on SIOS Tech. Lab .
こんにちは、サイオステクノロジーの遠藤です。 今回は、React Route v7のブラウザ開発環境でMSWを利用する方法について自分で導入するときに少しハマったので、備忘録的な形でまとめておこうと思います。 では始めていきましょう。 React Routerアプリを作成 まずは、React Routeのアプリを作成します。v7からviteで作成できるようになったのでviteを利用して作成しました。 ❯ npm create vite@latest Need to install the following packages: create-vite@6.3.0 Ok to proceed? (y) > npx > create-vite │ ◇ Project name: │ msw-blog │ ◇ Select a framework: │ React │ ◇ Select a variant: │ React Router v7 ↗ git Initialize a new git repository? Yes deps Install dependencies with npm? Yes 作成が完了したら、ディレクトリを移動します。 cd msw-blog MSWをインストール MSWのインストールを行いましょう。 npm install msw@latest --save-dev ワーカースクリプトをpublicディレクトリにコピー MSWでは、worker scriptをアプリケーションのpulbicディレクトリに配置することでクライアント上でリクエストをキャッチできるようになります。こちらは以下の形でMSWのCLIを利用することで自分のディレクトリにコピーすることができます。 npx msw init ./public また、以下のようにsaveフラグを付けるとpackage.jsonにpublicディレクトリのパスが記録されるようになり、mswパッケージをインストールするたびにversionに合わせたワーカースクリプトがコピーされるようになるのでおすすめです。 npx msw init ./public --save // package.json { "name": "my-app", "msw": { "workerDirectory": "./public" } } 公式 : https://mswjs.io/docs/cli/init Request handlersでモックを定義 Request handlers  関数を利用することで、モックの定義を行うことができます。まずは、mockを格納するためのディレクトリを作成しましょう。React Route v7では、呼び出し先の関係上app以下で定義しておくのが良さそうなので、app/mocksディレクトリを作成して、配置します。 mkdir app/mocks ディレクトリを作成したら、handlers.tsを作成します。 // app/mocks/handlers.ts import { http, HttpResponse } from 'msw' export const handlers = [ // Intercept "GET <https://example.com/user>" requests... http.get('<https://example.com/user>', () => { // ...and respond to them using this JSON response. return HttpResponse.json({ id: 'c7b3d8e0-5e0b-4b0f-8b3a-3b9f4b3d3b3d', firstName: 'John', lastName: 'Maverick', }) }), ] Integrate する ちょっと良いタイトルが思い浮かばなかったので、公式の言葉をそのまま持ってきました。要は、MSWは環境レベルで適用されるため、あらゆるフレームワーク、リクエスト ライブラリ、その他のツールと統合できるということみたいです。ブラウザで使用するか、Node.js プロセスで使用するか (またはその両方) を決めて、対応する統合モジュールを作成してアプリケーションに統合することで、どんな環境でも作成したモックを使うことができるとチュートリアルには書いてありました。今回は、ブラウザ用の統合モジュールを作成していきます。 app/mocksにbrowser.tsを作成します。 // app/mocks/browser.ts import { setupWorker } from "msw/browser"; import { handlers } from "./handlers"; export const worker = setupWorker(...handlers); app/entry.client.tsxを記述する 本日の本題です。React Router v7にはSpecial Filesというものがありまして、これらのファイルを利用することでアプリケーションの動きを調整することができます。 公式サイト : https://reactrouter.com/explanation/special-files#entryclienttsx entry.client.tsxはブラウザで最初に実行されるコードを制御できるものになっており、これに先程のブラウザ用の統合モジュールを利用することでMSWを動かすことが可能になります。それではapp/entry.client.tsxに以下のコードを追記します。 // app/entry.client.tsx import { startTransition, StrictMode } from "react"; import { hydrateRoot } from "react-dom/client"; import { HydratedRouter } from "react-router/dom"; async function enableApiMocking() { if (process.env.NODE_ENV !== "development") { return; } const { worker } = await import("~/mocks/browser"); console.info("Starting MSW service worker..."); return worker.start(); } enableApiMocking().then(() => { startTransition(() => { hydrateRoot( document, <StrictMode> <HydratedRouter /> </StrictMode> ); }); }); そうしたら、アプリを起動して動作を確認してみましょう。 npm run dev 開発者ツールで [MSW] Mocking enabled. の文字が見えたらモックが動くようになっています。お疲れ様でした。 まとめ 今回は、React Route v7のブラウザ開発環境でMSWを利用する方法についてまとめました。React Route触りたてというのもあり、entry.client.tsxの存在に気づくまで時間がかかりましたが、この記事が誰かの助けになれば幸いです。 ではまた~ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post React Router v7のブラウザ開発環境でMSWを利用する方法 first appeared on SIOS Tech. Lab .
こんにちは、サイオステクノロジーの遠藤です。 今回は、WebフレームワークであるRemixとルーティングライブラリであるReact Routerが統合されたReact Router v7のルーティングについてまとめていきます。 ルーティング方法 React Router v7では、Configuring Routes、Component Routes、File Route Conventionsの3種類のルーティング方法があります。 Configuring Routes React Router v7のデフォルトで設定されるルーティングで、app/routes.tsに定義されたルーティング構成が反映されます。 Component Routes 以下のコードのような形で定義するルーティング方法。React Router v6で使用されていた定義らしく、v6から変更なく使用できるのが利点のよう。 ただ、Remixと統合されたことによって使用可能になった in data loading, actions, code splitting といった機能が使用できないのは注意ポイントです。 import { Routes, Route } from "react-router"; function Wizard() { return ( <div> <h1>Some Wizard with Steps</h1> <Routes> <Route index element={<StepOne />} /> <Route path="step-2" element={<StepTwo />} /> <Route path="step-3" element={<StepThree />} /> </Routes> </div> ); } File Route Conventions Next.jsのPages Router のようなファイルベースでのルーティングをapp/routes以下のファイルに対して行える機能。ただ、「.」を利用してファイルを決定するらしく、ネストが深くなっていった場合に大変そうという印象を受けました。 ファイル構成 app/ ├── routes/ │ ├── _index.tsx │ ├── about.tsx │ ├── concerts.trending.tsx │ ├── concerts.salt-lake-city.tsx │ └── concerts.san-diego.tsx └── root.tsx URLとファイルの対応 URL Matched Route / app/routes/_index.tsx /about app/routes/about.tsx /concerts/trending app/routes/concerts.trending.tsx /concerts/salt-lake-city app/routes/concerts.salt-lake-city.tsx /concerts/san-diego app/routes/concerts.san-diego.tsx 今回はConfiguring Routesの書き方についてご紹介します。 Configuring Routesの書き方 Routesの基本 ルートは app/routes.ts に定義され、 URLパターン と ルートモジュールのファイルパス が必要です。 import { type RouteConfig, route } from "@react-router/dev/routes"; export default [ route("some/path", "./some/file.tsx"), ] satisfies RouteConfig; Routesの設定例 例えば、以下のようにルートを設定できます。 import { type RouteConfig, route, index, layout, prefix } from "@react-router/dev/routes"; export default [ index("./home.tsx"), route("about", "./about.tsx"), layout("./auth/layout.tsx", [ route("login", "./auth/login.tsx"), route("register", "./auth/register.tsx"), ]), ...prefix("concerts", [ index("./concerts/home.tsx"), route(":city", "./concerts/city.tsx"), route("trending", "./concerts/trending.tsx"), ]), ] satisfies RouteConfig; このように、レイアウトルートや動的セグメントを含むルートも定義できます。 ルートモジュールとは? 各ルートに対応するファイル(ルートモジュール)は、実際のページのコンポーネントやデータフェッチのロジックを定義します。 import type { Route } from "./+types/team"; export async function loader({ params }: Route.LoaderArgs) { let team = await fetchTeam(params.teamId); return { name: team.name }; } export default function Component({ loaderData }: Route.ComponentProps) { return <h1>{loaderData.name}</h1>; } ルートモジュールには、アクション、ヘッダー、エラー境界などの機能があり、上の例は loader を使用してページのデータ取得をルートレベルで管理している様子です。 ルートの種類と使いどころ 1. ネストされたルート ルートをネストして階層構造を作ることができます。 export default [ route("dashboard", "./dashboard.tsx", [ index("./home.tsx"), route("settings", "./settings.tsx"), ]), ] satisfies RouteConfig; /dashboard の中に /dashboard/settings という子ルートを作成できます。 2. レイアウトルート layout を使うと、URLを変えずに共通のレイアウトを適用できます。 export default [ layout("./marketing/layout.tsx", [ index("./marketing/home.tsx"), route("contact", "./marketing/contact.tsx"), ]), ] satisfies RouteConfig; 動的セグメントとパラメータ URLの一部を動的に受け取る場合は、 :paramName の形式を使います。 route("teams/:teamId", "./team.tsx"), 取得したパラメータは params から利用できます。 export default function Component({ params }: Route.ComponentProps) { return <h1>Team ID: {params.teamId}</h1>; } File Route Conventionsと組み合わせる 以下のような形で、File Route ConventionsとConfiguring Routesを組み合わせることが可能です。そのため、普段はFile Route Conventionsで管理を行い、複雑なルーティング制御が必要な場合にはConfiguring Routesを利用するといったことも可能です。 import { type RouteConfig, route, } from "@react-router/dev/routes"; import { flatRoutes } from "@react-router/fs-routes"; export default [ route("/", "./home.tsx"), ...(await flatRoutes()), ] satisfies RouteConfig; まとめ 今回は、React Router v7のルーティング方法の一つであるConfiguring Routesについてまとめました。React Router v7を利用したのが今回が初めてだったため、最初はドキュメントを読んでも「?」状態でしたが、ルーティング方法が3つあるということを知ってからはだいぶ理解を進めることが出来ました。 React Router v7は、 Remix の特徴を受け継ぎ Web 標準に沿っているということで、基本を身につけるためにもしばらく触ってみようと思います。 ではまた~ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post React Router v7のルーティング方法 Configuring Routes を整理する first appeared on SIOS Tech. Lab .
挨拶 最近は、「 Dify入門シリーズ 」の検証+執筆にいそしんでいる龍ちゃんです。応用的ステップとして、GASで本格運用する業務アプリの開発などもやっていました。こちらの業務アプリでは、DifyのAPIを叩くわけでなく、Azure OpenAI Service(AOAI)のREST APIで生成AIを実行しています。 今回は、参照用のサンプルコードとして「プロパティサービスを活用してアクセスキーなどを保護しつつ、APIのリクエストを作成するスクリプト」を書いておきます。 検証に使用したモデルのバージョンとAPIのバージョンは以下になります。 項目 バージョン モデル GPT-4o-mini AOAI APIバージョン 2024-10-21 サンプルコード:スクリプトプロパティでアクセスキーを取得 AOAIのモデルのデプロイ等は、終わっていると仮定して進めていきます。REST APIのアクセスには、以下の情報が必要になります。必要な情報は こちらを参考 に取得してください。 コンテンツ 説明 エンドポイント Azure Portal上で確認するエンドポイント キー Azure Portal上で確認するアクセスキー モデル名 モデルをデプロイした際に設定したデプロイ名 APIバージョン MS Learn上で参照して最新のGAリリースの情報を取得 こちらの情報を取得して、スクリプトプロパティとして保存しておきます。 スクリプトプロパティの登録などはこちらを参考 にしてください。以下のパラメータで保存しています。 コンテンツ スクリプトプロパティ エンドポイント AOAI_API_URL キー AOAI_API_KEY モデル名 AOAI_API_MODEL APIバージョン AOAI_API_VERSION 上記のパラメータで保存することで、以下のスクリプトがそのまま使うことができます。 const apiEndpoint = PropertiesService.getScriptProperties().getProperty("AOAI_API_URL"); const modelName = PropertiesService.getScriptProperties().getProperty("AOAI_API_MODEL"); const apiVersion = PropertiesService.getScriptProperties().getProperty("AOAI_API_VERSION"); const apiKey = PropertiesService.getScriptProperties().getProperty("AOAI_API_KEY"); const apiUrl = `${apiEndpoint}/openai/deployments/${modelName}/chat/completions?api-version=${apiVersion}`; サンプルコード:AOAIへの単発リクエスト(fetch) AOAIへの単発のチャットリクエストを送信するサンプルとなります。 fetch:公式リファレンス情報はこちら です。 // AOAIへの単発リクエスト function testRequestToAOAI() { const apiEndpoint = PropertiesService.getScriptProperties().getProperty("AOAI_API_URL"); const modelName = PropertiesService.getScriptProperties().getProperty("AOAI_API_MODEL"); const apiVersion = PropertiesService.getScriptProperties().getProperty("AOAI_API_VERSION"); const apiKey = PropertiesService.getScriptProperties().getProperty("AOAI_API_KEY"); const apiUrl = `${apiEndpoint}/openai/deployments/${modelName}/chat/completions?api-version=${apiVersion}`; const payload = { messages: [ { role: "system", content: `あなたは先生でユーザーは小学生です。優しく教えてください。` //システムプロンプト }, { role: "user", content: "先生!トイレ~" //ユーザープロンプト } ] } const option = { method: 'post', headers: { "Content-Type": "application/json", "api-key": apiKey }, payload: JSON.stringify(payload), muteHttpExceptions: true } const response = UrlFetchApp.fetch(apiUrl, option) const responsJson = JSON.parse(response.getContentText()); try { const res = responsJson.choices[0].message.content.trim(); Logger.log(res) } catch (e) { if(responsJson.error) Logger.log(responsJson.error.message) // リクエストでエラーレスポンスが返答されている可能性 Logger.log(e.message) } } 注目してほしい点としては、 muteHttpExceptions です。こちらは、リクエスト時にエラーが発生した場合は、responseにエラーオブジェクトを詰めて処理を継続させます。もしエラーの場合では、生成AIのレスポンス取得部分が落ちてしまうので、try~catchで捕捉しています。 catchで捕捉されるエラーは、jsonオブジェクトへアクセスできない等のエラーになるため、responseJsonを検証してエラーを取得しています。 龍ちゃん Errorを継承したカスタムエラーを作成する方法もありますが、厳密なエラーハンドリングが求められないのであればこれぐらいの軽量でよいと思います。 サンプルコード:AOAIへの並列リクエスト(fetchAll) fetchAllを活用して並列でAOAIへのリクエストを送信するサンプルとなります。 fetchAll:公式リファレンスの情報はこちら です。 // AOAIへのリクエスト作成 function createRequestToAOAI(text) { const apiEndpoint = PropertiesService.getScriptProperties().getProperty("AOAI_API_URL"); const modelName = PropertiesService.getScriptProperties().getProperty("AOAI_API_MODEL"); const apiVersion = PropertiesService.getScriptProperties().getProperty("AOAI_API_VERSION"); const apiKey = PropertiesService.getScriptProperties().getProperty("AOAI_API_KEY"); const apiUrl = `${apiEndpoint}/openai/deployments/${modelName}/chat/completions?api-version=${apiVersion}`; const payload = { messages: [ { role: "system", content: `あなたは先生でユーザーは小学生です。優しく教えてください。` //システムプロンプト }, { role: "user", content: text } ] } const request = { url: apiUrl, method: 'post', headers: { "Content-Type": "application/json", "api-key": apiKey }, payload: JSON.stringify(payload), muteHttpExceptions: true } return request } function testRequestsToAOAI(){ const children = ["先生トイレ!","`右`の漢字の書き順がわかりません", "1+1は?"] const requests = children.map((value) => createRequestToAOAI(value)) const reponses = UrlFetchApp.fetchAll(requests) reponses.forEach((response, index) => { const responsJson = JSON.parse(response.getContentText()); try { const res = responsJson.choices[0].message.content.trim(); Logger.log(`${children[index]}:${res}`) } catch (e) { Logger.log(responsJson) Logger.log(e) } }) } こちらは、事前にリクエストを作成する関数を使用してリクエスト配列を生成し、そちらの内容を fetchAll で送信しています。単発のリクエストとは異なり、 request オブジェクト内に url が含まれているのが特徴です。 終わり 今回は、GASでAOAIのREST APIを実行するサンプルコードを紹介しました。並列実行を含めたリクエストの作成方法と、エラーハンドリングの実装について解説しています。参考にしていただければ幸いです。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【保存版】GASでAzure OpenAI APIを使いこなす!基本から並列処理まで first appeared on SIOS Tech. Lab .
概要 こちらの記事 では既存のOpenShift上でコンテナとVMを統合管理することが出来るOpenShift Virtualization(以降、OCP-Virt)ついて紹介しました。 本記事では、実際にAWS環境のOpenShift上でOCP-Virtを構築し、簡単なVMの作成と外部公開を行ってみたいと思います。 次回の記事では、同じ仮想化基盤であるSUSE VIrtualizationの紹介をします。 前提条件 構築時の前提条件を以下に記載します。 AWS環境でOpenShift v 4.17クラスターが構築済みであること cluster-admin 権限および Operator インストール権限を持つアカウントを使用して OpenShift クラスターにアクセスできること 作業端末に下記Cliがインストール済みであること oc virtctl ベアメタルインスタンスのノードを追加する必要があるため、従量課金の料金には自己責任で気を付けてください 事前準備 OpenShift Virtualization用ノードの追加 AWS環境のOpenShiftへのノード追加方法を詳しく書いた記事があるので、詳細はこちらの記事を読んでください。 OpenShift(AWS)へのノード追加 OpenShift OperatorHub で Administrator のパースペクティブにいることを確認します。 Web コンソールで[コンピュート] > [MachineSets]に移動します。 [MachineSetの作成] をクリックします。 入力フォームにMachineSet の yaml を記述し、[作成]をクリックします。 MachineSet.yaml apiVersion: machine.openshift.io/v1beta1 kind: MachineSet metadata:   labels:     machine.openshift.io/cluster-api-cluster: <infrastructure_id>   name: <infrastructure_id>-<role>-us-east-2a-baremetal   namespace: openshift-machine-api spec:   replicas: 1   selector:     matchLabels:       machine.openshift.io/cluster-api-cluster: <infrastructure_id>       machine.openshift.io/cluster-api-machineset: <infrastructure_id>-worker-us-east-2a   template:     metadata:       labels:         machine.openshift.io/cluster-api-cluster: <infrastructure_id>         machine.openshift.io/cluster-api-machine-role: <role>         machine.openshift.io/cluster-api-machine-type: <role>         machine.openshift.io/cluster-api-machineset: <infrastructure_id>-worker-us-east-2a     spec:       metadata:         labels:           node-role.kubernetes.io/<role>: ""        providerSpec:         value:           ami:             id: ami-08997afda521c28fa           apiVersion: awsproviderconfig.openshift.io/v1beta1           blockDevices:             - ebs:                 iops: 0                 volumeSize: 120                 volumeType: gp2           credentialsSecret:             name: aws-cloud-credentials           deviceIndex: 0           iamInstanceProfile:             id: <infrastructure_id>-worker-profile            instanceType: c5n.metal           kind: AWSMachineProviderConfig           placement:             availabilityZone: us-east-2a             region: us-east-2           securityGroups:             - filters:                 - name: 'tag:Name'                   values:                     - <infrastructure_id>-node             - filters:                 - name: 'tag:Name'                   values:                     - <infrastructure_id>-lb            subnet:             filters:               - name: tag:Name                 values:                   - <infrastructure_id>-subnet-private-us-east-2a           tags:             - name: kubernetes.io/cluster/<infrastructure_id>               value: owned           userDataSecret:             name: worker-user-data 変更点 <infrastructure_id>:<クラスター名>-<ランダム文字列> infrastructure_idは以下のコマンドで確認するか、既存のマシンセットのyamlを見て確認してください。 $ oc get infrastructure cluster -o jsonpath='{.status.infrastructureName}' ocp-test-sp9lv <role>:worker metadata.name:マシンセットの名前(既存のマシンセットの名前と被るとエラーが出るので注意) instanceType:c5n.metal(ベアメタルインスタンスが要件となるため、ベアメタルインスタンスタイプを選択) ami.id:ami-08997afda521c28fa(インスタンスタイプに対応したRHCOSのAMI) MachineSet が作成されていることを確認します。しばらくした後(少なくとも10分以上はかかる印象があります)、利用可能数がspec.replicasで設定したマシン1台になっていることを確認します。 OpenShift Data Foundation インストール VMに永続・分散ストレージを提供するためにはOpenShift Data Foundationを導入する必要があります。これは、VMのマイグレーションを行うためには必須となります。ただし、ノードを3台追加する必要があるため、お試しでOCP-Virtを使ってみたいといった際はOpenShift Data Foundationを導入しなくても問題ありません。 OpenShift Data Foundation 用ノードの追加 上記のOpenShift Virtualization用ノードの追加と同じ手順でOpenShift Data Foundation 用ノードの追加を行います。MachineSetのyamlと変更点は下記になります。 MachineSet.yaml apiVersion: machine.openshift.io/v1beta1 kind: MachineSet metadata:   labels:     machine.openshift.io/cluster-api-cluster: <infrastructure_id>   name: <infrastructure_id>-<role>-us-east-2a-odf   namespace: openshift-machine-api spec:   replicas: 3   selector:     matchLabels:       machine.openshift.io/cluster-api-cluster: <infrastructure_id>       machine.openshift.io/cluster-api-machineset: <infrastructure_id>-worker-us-east-2a   template:     metadata:       labels:         machine.openshift.io/cluster-api-cluster: <infrastructure_id>         machine.openshift.io/cluster-api-machine-role: <role>         machine.openshift.io/cluster-api-machine-type: <role>         machine.openshift.io/cluster-api-machineset: <infrastructure_id>-worker-us-east-2a     spec:       metadata:         labels:           node-role.kubernetes.io/<role>: ""       providerSpec:         value:           ami:             id: ami-08997afda521c28fa           apiVersion: awsproviderconfig.openshift.io/v1beta1           blockDevices:             - ebs:                 iops: 0                 volumeSize: 120                 volumeType: gp2           credentialsSecret:             name: aws-cloud-credentials           deviceIndex: 0           iamInstanceProfile:             id: <infrastructure_id>-worker-profile           instanceType: m6a.2xlarge           kind: AWSMachineProviderConfig           placement:             availabilityZone: us-east-2a             region: us-east-2           securityGroups:             - filters:                 - name: 'tag:Name'                   values:                     - <infrastructure_id>-node             - filters:                 - name: 'tag:Name'                   values:                     - <infrastructure_id>-lb           subnet:             filters:               - name: tag:Name                 values:                   - <infrastructure_id>-subnet-private-us-east-2a           tags:             - name: kubernetes.io/cluster/<infrastructure_id>               value: owned           userDataSecret:             name: worker-user-data 変更点 <infrastructure_id>:<クラスター名>-<ランダム文字列> infrastructure_idは以下のコマンドで確認するか、既存のマシンセットのyamlを見て確認してください。 $ oc get infrastructure cluster -o jsonpath='{.status.infrastructureName}' ocp-test-sp9lv <role>:worker metadata.name:マシンセットの名前(既存のマシンセットの名前と被るとエラーが出るので注意) instanceType:m6a.2xlarge(3台の合計リソースが要件を満たすインスタンスタイプを選択) ami.id:ami-08997afda521c28fa(インスタンスタイプに対応したRHCOSのAMI) OpenShift Data Foundation Operatorインストール OpenShift OperatorHub で Administrator のパースペクティブにいることを確認します。 Web コンソールで[Operators] > [OperatorHub]に移動します。 検索バーに「OpenShift Data Foundation」と入力して、 OpenShift Data Foundation をクリックして Install をクリックします。 デフォルトの設定で Install をクリックします。 Operatorのインストール完了確認 [インストール済みの Operator]に OpenShift Data Foundation、OpenShift Data Foundation Clientが存在しており、ステータスがSucceededになっていることを確認します。 OpenShift Data Foundation をクリックして Storage System を作成します。 パラメータを入力できる画面に遷移しない場合はWeb コンソールを再読み込みする デフォルトの設定で Install をクリックします。 下記コマンドを実行して StorageClass:ocs-storagecluster-ceph-rbd、ocs-storagecluster-cephfs、openshift-storage.noobaa.io が作成されていることを確認します。 $ oc get storageclass NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE gp2-csi ebs.csi.aws.com Delete WaitForFirstConsumer true 5h45m gp3-csi (default) ebs.csi.aws.com Delete WaitForFirstConsumer true 5h45m ocs-storagecluster-ceph-rbd openshift-storage.rbd.csi.ceph.com Delete Immediate true 4m17s ocs-storagecluster-cephfs openshift-storage.cephfs.csi.ceph.com Delete Immediate true 4m9s openshift-storage.noobaa.io openshift-storage.noobaa.io/obc Delete Immediate false 78s   OCP-Virt 構築 OpenShift Virtualization Operatorインストール OpenShift OperatorHub で Administrator のパースペクティブにいることを確認します。 Web コンソールで[Operators] > [OperatorHub]に移動します。 検索バーに「OpenShift Virtualization」と入力して、 OpenShift Virtualization をクリックして Install をクリックします。 デフォルトの設定でInstall をクリックします。 Operatorのインストール完了確認 [インストール済みの Operator]に OpenShift Virtualizationが存在しており、ステータスがSucceededになっていることを確認します。 OpenShift Virtualization をクリックして HyperConverged を作成します。 デフォルトの設定で Install をクリックします。 ステータスに Available が表示されていることを確認します。 下記コマンドを実行して StorageClass:ocs-storagecluster-ceph-rbd-virtualization が作成されていることを確認します。 $ oc get storageclass NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE gp2-csi ebs.csi.aws.com Delete WaitForFirstConsumer true 3h23m gp3-csi (default) ebs.csi.aws.com Delete WaitForFirstConsumer true 3h23m ocs-storagecluster-ceph-rbd openshift-storage.rbd.csi.ceph.com Delete Immediate true 4m12s ocs-storagecluster-cephfs openshift-storage.cephfs.csi.ceph.com Delete Immediate true 4m4s openshift-storage.noobaa.io openshift-storage.noobaa.io/obc Delete Immediate false 3m16s ocs-storagecluster-ceph-rbd-virtualization openshift-storage.rbd.csi.ceph.com Delete Immediate true 0s 簡単なVMの作成 OpenShift OperatorHub で Administrator のパースペクティブにいることを確認します。 Web コンソールで[Virtualization] > [VirtualMachines]に移動します。 [Create VirtualMachines] > [From template] をクリックします。 プロジェクトの選択、作成する必要があれば作成して作成対象のOSをクリックします。右上にSource availableと書いてあるOSが簡単に作成可能です。今回はCentOS Stream 9 VMを選択してみます。 デフォルトの設定で[Quick Create VirtualMachines] をクリックします。 数分後、ステータスがRunningであることを確認します。 [Console]タブへ移動し、[Guest login credentials]をクリックしてログイン情報を取得後、コンソールからVMにログインします。「i」キーを押すとinsert modeに入ります。insert modeを抜けるには、「ESC」キーを押します。 コンソールから下記コマンドを実行してパッケージのインストールをします。 $ sudo dnf install nginx ... Complete! コンソールから下記コマンドを実行してNginxを起動します。 $ sudo systemctl start nginx コンソールから下記コマンドを実行してnginxが起動していることを確認します。 $ sudo systemctl status nginx ... Active: active (running) ... VMの外部公開設定 下記コマンドを実行してVMが作成されているプロジェクトに移動します。 $ oc project <プロジェクト名> Now using project "<プロジェクト名>" on server "https://api.<ドメイン>:6443". 下記コマンドを実行し、パラメータシートを参照してServiceのマニフェストファイルを作成します。 $ vi service.yaml apiVersion: v1 kind: Service metadata:   name: vm-service   namespace: vm-tutorial spec:   selector:     kubevirt.io/domain: centos-stream9-moccasin-bandicoot-85 # VM名を指定   ports:     - protocol: TCP       port: 80       targetPort: 80  # Nginxのリッスンポート 下記コマンドを実行してserviceを作成します。 $ oc apply -f service.yaml service/nginx-service created 下記コマンドを実行して作成したserviceを公開します。 $ oc expose service vm-service route.route.openshift.io/vm-service exposed 下記コマンドを実行してrouteが作成されていることを確認します。 $ oc get route NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD vm-service vm-service-vm-tutorial.apps.<ドメイン> vm-service 80 None VMの疎通確認 表示されたホストにアクセスしてnginxの画面が表示されることを確認します。 まとめ 実際にAWS環境のOpenShift上でOCP-Virtを構築し、簡単なVMの作成と外部公開を行ってみました。OCP-VirtはOperaterのインストールを行うことで簡単に仮想化基盤を構築することが出来ました。 次回の記事では同じ仮想化基盤であるSUSE VIrtualizationの紹介をします。 参考文献 https://docs.redhat.com/ja/documentation/openshift_container_platform/4.17/html/virtualization/index https://docs.redhat.com/ja/documentation/red_hat_openshift_data_foundation/4.17/html/deploying_openshift_data_foundation_using_amazon_web_services/index ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post OpenShift Virtualization 構築してみた first appeared on SIOS Tech. Lab .
こんにちは、 サイオステクノロジー の織田です。 最近CMでよく見る話題の生成AI、 Gemini を使ってみました!今回は、実際に使ってみた感想をブログ形式でまとめてみます。 Geminiとは? Geminiは、Google Workspaceに組み込まれた生成AI機能です。Gmail、Docs、SpreadSheetなど、普段使い慣れたアプリでAIの力を活用できるのが特徴です。同じ生成AIツールですとChatGPTがありますが、あちらはチャットボットとしてしか活用ができません。一方でGeminiは、Gmail、Docs、SpreadSheetといったGoogle Workspaceのツールと組み合わせて作業を行うことができます。 たとえば、Gmailでメールを作成する場合、Geminiはメールの下書きを作成したり特定のメールを抽出したりできます。ドキュメントでは、Geminiはドキュメントの要約を作成したり、ドキュメント内のテキストの表現や誤字を変更したりすることができます。スライドでは、Geminiはプレゼンテーション用の画像を作成することができます。 GeminiとGmailの連携:領収書管理の効率化 テスト用のタクシー代とホテル代の領収書付きメールで試した結果、両方の合計金額が算出された。 ここからは、Gmail、Docs、SpreadSheetのそれぞれについてGeminiがどのように活躍するかを詳しく説明します。(Slidesは日本語未対応のためここでは割愛します。)Geminiは、Gmailと連携することで、メール作成や管理を大幅に強化します。実際に触って特に便利だと感じたのが、領収書付きメールの処理です。受信トレイから領収書付きのメールのみ表示させるのはもちろん、添付された領収書の合計金額を計算してくれます。これにより、手作業で領収書を探したり、電卓で計算したりする手間が省け、大幅な時間短縮が実現できます。 GeminiとDocsの連携:長文中の誤字を一瞬で検知 Geminiは、Docsと連携することで、文書作成を強力にサポートしてくれます。例えば、簡単なプロンプトを与えるだけで、説明資料、企画書、小説など、さまざまな種類の文書を生成することができます。また、既存の文書を要約したり、テキストの表現を変更したりすることも可能です。これにより、文書作成にかかる時間を大幅に短縮することが可能になります。また、特に便利だと感じたのは誤字検出です。Docs上の文書についてGeminiに「誤字を検出して」と指示をだすと、一瞬ですべての誤字を指摘してくれました。長い文章を書くとどうしても誤字が数ヵ所混じってしまいますが、人手で確認するのはとても億劫です。Geminiを使えばこの面倒な作業を一瞬で終わらせることができます。 ダミーの長文を用意し誤字を検出してみたところ、事前に入力しておいた「ウェフサイト」が検出された GeminiとSpreadSheetの連携:目的に適した関数を提案 Geminiは、SpreadSheetと連携することで、データ分析を簡単に実施できるようにしてくれます。例えば、やりたい分析内容を指示するだけで、Geminiが適切な関数や数式を提案してくれます。「こういう処理をしてほしいんだけど、どの関数使えば良いんだっけ?」ということがよくあると思います。そんなとき、Geminiはユーザーのやりたいことを理解し、最適な関数や数式を提案してくれるため、作業効率が大幅に向上します。また、複雑なスプレッドシートの内容を分かりやすく要約することも可能です。これにより、データ分析の効率が向上し、より深い洞察を得ることができます。 例えば、特定の条件に合致するデータの出現回数を求めたい場合、Geminiに「このファイル中のhogehogeの出現回数を求める関数を教えてください」と指示するだけで、適切な関数(COUNTIF関数など)を提案してくれます。また、大量のデータを含むスプレッドシートの内容を要約したい場合、Geminiに「このシートの内容を要約して」と指示するだけで、主要なポイントをまとめた要約文を生成してくれます。 SpreadSheet用の関数は非常に多く実装されており、目的にあった関数がぱっと思い出せない時もあると思います。そんなときにGeminiを頼ってみてはいかがでしょうか。 特定の単語の出現回数を求める関数をGeminiに尋ねた結果、COUNTIF関数が正しく提案された Geminiを使ってみた感想 Geminiを実際に使用する前は、「AIチャットボットが仕事で役に立つだろうか?」と考えていました。実際に使用すると、領収書の整理や関数の提案など、「これ欲しかったんだよなあ」という機能が多くありとても便利だと感じています。 特に、Gmailでの領収書の整理、Docsでの長文中の誤字検出、SpreadSheetでの関数提案は、日々の業務に大きな変化をもたらしてくれると思います。 回答の精度についてはまだ改善の余地があると感じますが、将来的にGeminiのような生成AIツールが私たちの働き方を大きく変えていくのではないかと期待しています。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 1人がこの投稿は役に立ったと言っています。 The post Geminiを使ってみた感想:Gemini × Google Workspace first appeared on SIOS Tech. Lab .
こんにちは、サイオステクノロジーの遠藤です。 今回は、 JavaScript Rising Stars で2023年、2024年と二年連続で1位に輝いたUIコンポーネントコレクションであるshadcn/uiについてまとめていきます。 では始めていきましょう! shadcn/uiとは? shadcn/uiはRadix UIとTailwindCSSを用いて作成されたUIコンポーネントコレクションです。 shadcn/uiの一番の特徴は公式ドキュメントにも This is not a component library. と書かれているように既存のコンポーネントライブラリではない点にあります。 一般的なコンポーネントライブラリの場合、NPMからパッケージをインストールしてコンポーネントをインポートすることで対象のコンポーネントを使用します。この方法の場合、ザイン システムに合わせてコンポーネントをカスタマイズしたり、ライブラリに含まれていないコンポーネントが必要になったりするまではうまく機能しますが、自分でコンポーネントに対して修正を加えたくなった場合、コンポーネントをラップしたり、スタイルをオーバーライドするための回避策を記述したり、互換性のない API を持つ異なるライブラリのコンポーネントを混在させたりすることになります。 一方shadcn/uiでは、実際のコンポーネント コードを自分のプロジェクトに落としてきて扱います。そのため、コンポーネントをカスタマイズおよび拡張する完全な制御権を持つことができ、コンポーネントを自分の要件に合わせて修正することが容易になります。 また生成AIに対応している点を特徴として謳っており、生成AIがshadcn/uiのコンポーネントを組み合わせて自然言語にあったwebデザインを作成するのはもちろん、shadcn/uiをベースに新しいコンポーネントの生成を行えるようなことを想定しています。 実際に先日投稿した 【React】プロンプトからNext.jsアプリを自動生成!v0を試してみた! で使用したv0ではshadcn/uiを利用してuiを作成しているようです。 公式ドキュメント : https://ui.shadcn.com/docs インストール 今回はNext.jsで導入する方法を確認していきます。 公式ガイド : https://ui.shadcn.com/docs/installation/next npm,pnpm,yarn,bunのいずれかを使用してinitコマンドを走らせます。今回私はbunを利用しました。 ❯ bunx --bun shadcn@latest init 各種設定を聞かれるので答えていきます。 ✔ Which style would you like to use? › New York ✔ Which color would you like to use as the base color? › Neutral ✔ Would you like to use CSS variables for theming? … no / yes これで完了です! 注意点 2025/02現在、Next.js or React19を利用していてnpmを利用している場合、  --force  or   --legacy-peer-deps  のフラグが必要になるようです。npmを利用している方はご注意を! npm i <package> --force npm i <package> --legacy-peer-deps 公式ガイド : Using shadcn/ui with Next.js 15 and React 19. Buttonコンポーネントを使ってみる では実際に利用してみましょう。まずはチュートリアルにもあるButtonコンポーネント利用する方法を確認してみます。 shadcn/uiはコンポーネントコレクションのため、一般的なコンポーネントライブラリでよくあるライブラリからButtonコンポーネントを呼び出してimportするといったことができません。使用するには使用したいコンポーネントを追加して上げる必要があります。追加するためにまずは以下のコマンドを実行してみましょう。 bunx --bun shadcn@latest add button そうすると、デフォルトだとcomponents/ui直下にbutton.tsxが作成されます。そしてこのbutton.tsxを呼び出すことでButtonコンポーネントを使用することが可能です。page.tsxに以下のように記述してnext.jsを動かし、表示を確認してみましょう。 import { Button } from "@/components/ui/button"; export default function Home() { return ( <div> <Button>Click me</Button> </div> ); } 無事表示することが出来ました!これがshadcn/uiの基本的な使用方法です。 Buttonコンポーネントのスタイルを変更してみる ここでButton.tsのコードの一部を確認してみます。 ・・・ const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", { variants: { variant: { default: "bg-primary text-primary-foreground shadow hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-9 px-4 py-2", sm: "h-8 rounded-md px-3 text-xs", lg: "h-10 rounded-md px-8", icon: "h-9 w-9", }, }, defaultVariants: { variant: "default", size: "default", }, } ) ・・・ 中を確認するとvariantsというものがあり、variantと、sizeというプロパティがあります。これらを変更することで、コンポーネントに当てるスタイルを変更することが出来ます。今回はvariantに”outline”, sizeに”lg”を当ててみました。 <Button variant="outline" size="lg"> Click me </Button> 無事スタイルが変更されました!ここまでは一般的なコンポーネントライブラリでも利用できることの多い機能ですね。 Buttonコンポーネントをカスタマイズする 次は実際に自分なりのカスタマイズをコンポーネントに加えてみましょう。全体的にボタンが赤くなる”red”variantを加えてみます。 variant: { default: "bg-primary text-primary-foreground shadow hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", red: "bg-red-500 text-white shadow-sm hover:bg-red-600", #追加 }, そうすると無事ボタンが赤くなりました!このコンポーネントの直接編集ができるのがコンポーネントコレクションであるメリットですね! aschild shadcn/uiではradix-uiのSlotを利用してasChild パターンというものが利用されています。asChildを利用すると、 asChild   false  のときデフォルトとして指定したコンポーネントをレンダリングする asChild   true  のとき渡した子要素をレンダリングする といったことが可能になります。 adChildパターンの理解に関してはこちらのブログを参考にさせていただきました。 asChild Pattern 今回は以下のコードを利用して Buttonコンポーネントをそのまま利用した場合 asChildを利用せずにButtonコンポーネントの子要素としてaタグを渡した場合 asChildを利用してButtonコンポーネントの子要素としてaタグを渡した場合 の挙動を確認します。 import { Button } from "@/components/ui/button"; export default function Home() { return ( <div className="flex flex-col items-end justify-center h-screen space-y-4"> <Button>ボタンそのまま</Button> <Button> <a href="<https://google.com>">そのままaタグを渡す</a> </Button> <Button asChild> <a href="<https://google.com>">asChildでaタグを渡す</a> </Button> </div> ); } 1つ目のButtonコンポーネントをそのまま利用した場合、buttonタグとしてそのまま出力されます。 2つ目のasChildを利用せずにButtonコンポーネントの子要素としてaタグを渡した場合では、buttonタグの子要素として <a href=” https://google.com “>そのままaタグを渡す</a> が扱われる形になります。 3つ目のasChildを利用してButtonコンポーネントの子要素としてaタグを渡した場合では、直接aタグに対してスタイルやhref要素がマージされて扱うことができるようになります。 終わりに 今回はshadcn/uiについてご紹介しました。実際に使用してみている感想としてはコードを直接変更できることによる取り回しの良さが最高です。またデフォルトのデザインでもおしゃれかつ、複数のvariantが用意されているので、そのままでも使っていけると感じています。また、新しいコンポーネントも追加されているみたいですので、これからにも期待大ですね! ではまた~ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post コンポーネント「ライブラリ」ではなく「コレクション」?shadcn/uiに入門してみた first appeared on SIOS Tech. Lab .
こんにちは! 今月も「OSSのサポートエンジニアが気になった!OSSの最新ニュース」をお届けします。 2025/2/6、The Linux Foundation Japan が SBOM の利用について解説した資料の日本語版である「SPDX 3.0を用いたAI部品表(AI BOM)の実装」を公開しました。 The Linux Foundation、AI部品表「AI BOM」の日本語資料を公開 https://japan.zdnet.com/article/35229116/ 生成 AI 技術「DeekSeek」のセキュリティ面に関する危険性について、情報が出ています。 個人情報ダダ漏れ。まだまだ出てくるDeepSeekの脆弱性 https://www.gizmodo.jp/2025/02/deepseek-data-protection-vulnerabilities.html Appleは、Xcode のビルドエンジン「Swift Build」をオープンソース化したと発表しました。 Apple、Xcodeで使われているビルドエンジン「Swift Build」をオープンソースで公開。WindowsとLinuxもサポートへ https://www.publickey1.jp/blog/25/applexcodeswift_buildwindowslinux.html ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【2025年2月】OSSサポートエンジニアが気になった!OSS最新ニュース first appeared on SIOS Tech. Lab .
今号では、前回紹介しきれなかったログローテーションの設定について、もう少し深堀りして見ていきます! 設定ファイルに設定可能な情報 前回、デフォルトの設定からどのような設定項目があるかを見ていきました。 今回は、それ以外にどのような設定があるのか、よく使用されると考えられる設定項目からピックアップして解説します。 ローテートの頻度 – daily:毎日 – weekly:毎週 – mounthly:毎月 – yearly:毎年 圧縮の有無 – compress:圧縮する – nocompress:圧縮しない (デフォルト) 空のログファイルの扱い – notifempty:空のログはローテートしない – ifempty:空のログもローテートする (デフォルト) さらに、下記設定項目を使用することで、より詳細に動作を設定することができます。 delaycompress ローテート直後のログをすぐに圧縮せず、次回ローテート時に圧縮します。 【例】ローテート1回目 log → log.1、2回目 log.1 → log.2.gz (ここで log.1 を圧縮する) …と続く olddir ローテート後のログの保存先を指定します。 ディレクトリは事前に作成しておく必要があります。 copytruncate ログをコピーしてから元のログを空にします。 特定のアプリが、 ファイルを参照し続けている (開きっぱなし) の場合などに有用 な設定です。 ただし ローテート中にログが書き込まれた場合、当該ログが消えるリスクがあるので要注意 です。ログの書き込みが少ない時間帯に実行するなど、運用でカバーする工夫が必要です。 運用における補足事項 設定ファイルは /etc/logrotate.d 配下で運用 /etc/logrotate.conf は基本的に編集せず、 個別のログローテーション設定を /etc/logrotate.d 配下に配置することを推奨 します。 設定変更後は、まずは動作テストを実行 logrotate -d コマンドでテスト実行 (実際にはローテートしない) ができます。 実際にローテートを実行する前に動作に問題が発生しないか確認しましょう。 反対に、logrotate -f コマンドを実行すると設定変更後ただちにローテートされます。 ディスク容量の定期的なチェック 長期間ファイルをローテートしていくと、古いログが溜まりすぎてシステムの容量を圧迫する可能性があります。 定期的にディスク容量をチェックするようにし、必要に応じてログの退避などの対応をしましょう。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 知っておくとちょっと便利!logrotate の仕組みについて2 first appeared on SIOS Tech. Lab .
今回は、以前Difyで作成したプロトタイプ「 Dify入門ガイド:X投稿を自動生成!10分で作るブログPR効率化ワークフロー 」をGASと Azure OpenAI Service で業務アプリとして作成しました。Difyで作成したプロトタイプから、普段使いしやすいように業務アプリ化する具体的な流れについて解説していきます。 Dify版プロトタイプ プロトタイプについては、「 Dify入門ガイド:X投稿を自動生成!10分で作るブログPR効率化ワークフロー 」で紹介しています。 こちらのプロトタイプでは、「URLと文字数を入力として、HTMLから情報を取得しXの投稿文を生成」します。プロトタイプでは、2ステップで処理を作成しています。 HTMLから記事情報を取得 取得したテキストからPR文を生成する 業務アプリ検討 業務アプリケーションのモチベーションとしては、「ブログの投稿と連動してXの投稿を自動生成し、業務効率を改善」となります。GASを選択した背景を含めて解説していきます。 要件 業務アプリは、以下の要件を満たす必要があると考えました。 投稿の管理:投稿ステータス管理(投稿済み・未投稿) ブログの更新と連動して投稿を取得する(定期実行) Xの自動投稿はせずに人の手で確認をする 複数の担当者が一覧にアクセスすることができる(アクセス管理) 定期実行・アクセス管理・一覧表示などの要件から、GASとスプレッドシートで業務アプリの作成を決定しました。GASとDifyの連携で処理を行うことも考えましたが、Difyの環境をAzure上で構成するよりもREST APIでAOAIにリクエストを投げるほうがお手軽なので、生成AIにはAOAIを使用します。 GASの制限 GASを採用するにあたって業務アプリに関係のある制限事項についてまとめていきます。 公式の情報としてはこちら になります。 制限 閾値 スクリプトの実行時間 6 min URL Fetch数 20,000 件/日 スクリプトあたりの同時実行数 1,000 トリガー 20 ユーザー/スクリプト トリガー実行に関しては、日付ベースの場合では実行時間の範囲を指定することができます。この実行時間は、必ずその範囲で実行するわけではないので、スクリプト間で前処理が必要な場合の処理に関しては工夫が必要です。こちらの制限を回避する方法としては、スクリプトを独立させておくなどが挙げられます。 上記の条件に抵触しないように業務アプリとして作成する必要があります。 GAS:XのPR文生成 from RSS それでは、GASコードの解説に入っていきます。構成図としては、以下のような構成になっています。データソースとしては、Google Spred Sheetを使用しています。 スクリプトとしては、3つになります。 トリガー実行:RSSから記事情報を取得しURL・タイトル・投稿日をシートに書き込む トリガー実行:シートからURLを取得し情報を圧縮してシートに書き込む 通常実行:圧縮したテキストをAOAIに投げて投稿本文を生成しシートに書き込む トリガー実行をする部分に関しては、それぞれ単独で運用しても問題なく機能するように作成しています。 投稿管理を行うシートの構成としては、 RSS というシート名で以下のヘッダーを持っています。 ヘッダー名 説明 タイトル RSSから情報を取得した記事タイトル リンク RSSから情報を取得した記事リンク 日付 RSSから情報を取得した記事投稿日 圧縮生データ HTMLタグなどを除外し圧縮したテキストデータ PR AOAIが生成したXの投稿文 実際の出力画面としては、以下のようになります。手動でフラグ管理としてステータス(投稿済み・未投稿)の列を追加して管理しています。 もし使用される方がいれば、上記のヘッダーを持った RSS というシートを用意してください。 共通スクリプト シートへのアクセスはどのスクリプトでも実行するため、共通として書き出しています。 // SpreadSheetのアクセスSheet取得 const accessSheet = (sheetName = "RSS") => { const file = SpreadsheetApp.getActiveSpreadsheet() const sheet = file.getSheetByName(sheetName) return sheet } 引数として sheetName を受け取れるようにしていますが、デフォルトとして RSS を指定しています。 RSSから情報を取得しシートに記載する このスクリプトでは「RSSから情報を取得( getRSSFeed )しシート内に情報が記載されていない場合、行の最後にタイトル・リンク・投稿日の情報を追記」を処理しています。工夫として、重複を避けるために記載済みの情報を削除する仕組みが搭載されています。 // スケジュール用:RSSから取得してスプレッドシートに書き込む const createRowData = () => { const sheet = accessSheet() const lastRow = sheet.getLastRow() // 2-2(B2) からスタート const linkList = lastRow == 1 ? [] : sheet.getRange(2, 2, lastRow - 1, 1).getValues().map((row) => row[0]) const dataFromRSS = getRSSFeed() const data = [] dataFromRSS.forEach((value) => { const [, link] = value if (!linkList.includes(link)) data.push(value) }) if (data.length == 0) return sheet.getRange(lastRow + 1, 1, data.length, data[0].length).setValues(data) } RSSの取得部分は処理として分割しています。こちらでは、RSSフィードからXML情報を取得しています。XMLをパースし、配列化して、最後に投稿日順にソートをして返答しています。 // RSSから情報を取得する const getRSSFeed = () => { // RSSフィードのURLを指定 const rssUrl = 'https://ch-lab.sios.jp/feed'; // ここにRSSフィードのURLを入力してください // RSSフィードを取得 const response = UrlFetchApp.fetch(rssUrl); const xml = response.getContentText(); const document = XmlService.parse(xml); const root = document.getRootElement(); // RSSフィードのエントリを解析 const items = root.getChild('channel').getChildren('item'); const data = []; items.forEach(function (item) { const title = item.getChild('title').getText(); const link = item.getChild('link').getText(); const pubDate = new Date(item.getChild('pubDate').getText()); data.push([title, link, pubDate]); }); // 日付sort data.sort((a, b) => a[1] > b[1] ? 1 : -1) return data } URLから圧縮済みテキストを生成しシートに記載 このスクリプトでは「シートから圧縮済みテキストを生成していない場合、URLから情報を取得し圧縮済みテキストを生成( compressHTML )しシートに追記」を処理しています。工夫として、重複を防ぐために圧縮済みテキストが ”” の場合のみURLを取得しています。また、HTML情報を取得する部分に関しては fetchAll でリクエストを同時実行しています。 // スケジュール用:URLから圧縮のテキストデータ未作成の場合は作成 function getCompressTextFromURL() { const sheet = accessSheet() const lastRow = sheet.getLastRow() // 情報がなければスルー if (lastRow == 1) return const itemList = sheet.getRange(2, 1, lastRow - 1, 5).getValues() const linkList = itemList.map((row) => row[1]) const nonComporessTextlinkList = [] itemList.forEach((row) => { const [, , , comporessText] = row if (comporessText == "") nonComporessTextlinkList.push(row[1]) }) const response = UrlFetchApp.fetchAll(nonComporessTextlinkList) response.forEach((value, index) => { const html = value.getContentText() const compressText = compressHTML(html) const url = nonComporessTextlinkList[index] const listIndex = linkList.indexOf(url) if (listIndex == -1) return itemList[listIndex][3] = compressText }) sheet.getRange(2, 1, lastRow - 1, 5).setValues(itemList) } URLから情報を取得して、情報を圧縮するスクリプトは以下になります。こちらは「 GASコード付き:URLからHTML取得&圧縮する方法|AI入力の最適化に 」で詳細に解説しています。 // HTMLから圧縮したTextを作成 const compressHTML = (html) => { // HTMLを解析 const document = HtmlService.createHtmlOutput(html).getContent(); // 必要な部分を抽出(この記事の内容が <section class="entry-content"> タグ内にあると仮定します var content = ''; const regex = /<section class="entry-content"[^>]*>([\s\S]*?)<\/section>/; const match = regex.exec(document); if (match && match[1]) { const temp = match[1]; // プロンプト圧縮用 content = temp.replace(/<img([^>]*?)alt="([^"]+)"([^>]*)>/g, '<img$1$3>$2') .replace(/<("[^"]*"|'[^']*'|[^'">])*>/g, '') //タグ削除 .replace(/[\r\n]+/g, "") //改行削除 .replace(/[\s\t\n]/g, "") //空白削除 .trim(); //ダメ押しのTrim(文字列の前後の空白削除) } return content } 圧縮の処理は以下になります。 imgタグのalt情報の分離 HTMLタグの削除 空白の削除 生成AIから投稿文を生成しシートに記載 このスクリプトでは「圧縮済みテキストからPR文を生成していない場合、AOAIのリクエストを生成( createRequestToAOAI )し、Xの投稿文を同時に5件まで生成しシートに追記」を処理しています。工夫として、重複を防ぐために圧縮済みテキストが ”” の場合のみ圧縮済みテキストを取得しています。また、APIの負荷を考慮してリクエストを最大5件まで制限し、 fetchAll でリクエストを同時実行しています // 通常実行:シートからPR文未作成のものを抽出して生成、最大同時生成5個 function getCreateContent() { const sheet = accessSheet() const lastRow = sheet.getLastRow() // 情報がなければスルー if (lastRow == 1) return const itemList = sheet.getRange(2, 1, lastRow - 1, 5).getValues() const comporessTextList = itemList.map((row) => row[3]) let nonContentRequestList = [] let nonConentComporessText = [] itemList.forEach((row) => { const [, , , comporessText, prText] = row if (prText == "") { nonContentRequestList.push(createRequestToAOAI(comporessText)) nonConentComporessText.push(comporessText) } }) nonContentRequestList = nonContentRequestList.slice(-5) nonConentComporessText = nonConentComporessText.slice(-5) const responses = UrlFetchApp.fetchAll(nonContentRequestList) responses.forEach((response, index) => { try { const responsJson = JSON.parse(response.getContentText()); const res = responsJson.choices[0].message.content.trim(); Logger.log(responsJson) const listIndex = comporessTextList.indexOf(nonConentComporessText[index]) if (listIndex == -1) return itemList[listIndex][4] = res } catch (e) { Logger.log(e) } }) sheet.getRange(2, 1, lastRow - 1, 5).setValues(itemList) } リクエスト作成の処理に関しては以下になります。 GASからのREST APIへの書き込みに関しては公式の情報を参考 に構築しています。 // AOAIへのリクエスト作成 function createRequestToAOAI(text) { const apiEndpoint = PropertiesService.getScriptProperties().getProperty("AOAI_API_URL"); const modelName = PropertiesService.getScriptProperties().getProperty("AOAI_API_MODEL"); const apiVersion = PropertiesService.getScriptProperties().getProperty("AOAI_API_VERSION"); const apiKey = PropertiesService.getScriptProperties().getProperty("AOAI_API_KEY"); const apiUrl = `${apiEndpoint}/openai/deployments/${modelName}/chat/completions?api-version=${apiVersion}`; const payload = { messages: [ { role: "system", content: `## 出力要件: 1投稿140文字以内で作成する 記事の要点を簡潔にまとめる 魅力的なフレーズや問いかけを活用する ## 出力例: - AIを活用した最新のマーケティング戦略とは?成功事例を交えて解説.詳しくはこちら - 生成AIが変えるデザインの未来。クリエイター必見のトレンドをチェック ## 注意事項:企業アカウント向けの場合、ブランドトーンを意識する ユーザーの関心を引く表現を心がける クリックを促すアクション(例: 詳しくはこちら)を含める 上記の条件に従い、適切なX向けの投稿文を生成してください。` }, { role: "user", content: text } ] } const request = { url: apiUrl, method: 'post', headers: { "Content-Type": "application/json", "api-key": apiKey }, payload: JSON.stringify(payload), muteHttpExceptions: true } return request } 使用しているモデルとAPIバージョンは以下になります。 項目 バージョン モデル GPT-4o-mini AOAI APIバージョン 2024-10-21 システムプロンプトとしては、以下のプロンプトを与えています。 ## 出力要件: 1投稿140文字以内で作成する 記事の要点を簡潔にまとめる 魅力的なフレーズや問いかけを活用する ## 出力例: - AIを活用した最新のマーケティング戦略とは?成功事例を交えて解説.詳しくはこちら - 生成AIが変えるデザインの未来。クリエイター必見のトレンドをチェック ## 注意事項: 企業アカウント向けの場合、ブランドトーンを意識する ユーザーの関心を引く表現を心がける クリックを促すアクション(例: 詳しくはこちら)を含める 上記の条件に従い、適切なX向けの投稿文を生成してください。 コラム こちらのスクリプトは通常実行しています。理由としては、主に二つあります。 一つ目は金銭的な側面です。現状、リクエスト単位でお金が発生しています。自動実行で再リクエストなどを実装した場合、コストが予期せず発生する可能性があります(だいぶ恐怖です)。 二つ目は生成した結果を人間の目でチェックしたいからです。SNS投稿の効率化がこちらのアプリのモチベーションとなっています。SNS投稿は社外の目があるとのことで、生成した結果を自動で投稿するのは、ハルシネーションなどのリスクを含めて断念しました。再生成なども行うことを視野に入れて実装しました。 上記の二点から、手動での実行にしています。 おわり 今回は、Difyで作成したプロトタイプをGASとAzure OpenAI Serviceを利用して業務アプリ化する方法について解説しました。投稿内容の自動生成と管理を実現しつつ、リスク管理の観点から手動での確認プロセスを組み込むことで、効率的かつ安全なワークフローを構築することができました。今後も改善を重ねながら、より使いやすいシステムを目指していきたいと思います。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【GAS】Difyプロトタイプを本格業務アプリに!実践的な自動化 first appeared on SIOS Tech. Lab .
こんにちは、サイオステクノロジーの遠藤です。 最近はブログ執筆に精力的に取り組んでおり、キーボードでたくさんの日本語を打ち込んでいます。日本語を入力する機会が多い方なら入力効率をあげる方法はないかな?と考えたこともあると思います。 今回はそんな方におすすめな、一般的な配列を乱さず打鍵効率を上げてくれる拡張ローマ字入力AZIKをご紹介します。 AZIKとは? AZIKは、一般のローマ字入力のキー配列をそのままに、日本語によく出てくる文字列(読み)を2〜3ストロークで打てるようにし、さらに打ちにくいパターンの互換キーを提供する拡張ローマ字入力です。 例えば、「入場券」というキーワードを入力する場合を考えてみます。 通常のローマ字で入力する場合、「NYUUJOUKEN」と11回のストロークが必要となります。 一方AZIKを利用した場合「NGHJPKD」の7ストロークで入力することが可能で、通常のローマ字入力と比べて4文字もストローク数を減らすことができます。 AZIKを利用されたことがない方だと、どういう仕組みでこのような入力になるのかさっぱりわからないと考えると思われますし、このようなルールを覚えるのは大変そうといった印象を抱かれると思います。が、実はこのルールの覚えやすさに配慮されている点がAZIKの特徴です。 AZIKでは1ストローク目は通常のローマ字と同様に子音の決定に使用されます。そして2ストローク目は文字の意味からではなく、実際の打鍵の指の動きによって決定されます。そのため、通常のローマ字入力のタッチタイピングができている人であればAZIKの基本については1時間程度で習得することが可能です。 また、ひとつのルールを除いて通常のローマ字と共存が可能のため、AZIK特有の入力方法を忘れてしまった場合、通常のローマ字入力を使用することも可能です。そのため導入のため一気にすべてのルールを覚える必要がなく、段階的に通常のローマ字入力からAZIKに移行することが可能です。  注意点としては、通常のローマ字の指の動きをベースにしているため、通常のローマ字のタッチタイプがある程度できる方じゃないと効果を発揮できません。AZIKに挑戦したい方はまずはタッチタイピングを身に付けてから挑戦しましょう。 作者様の解説書 : 拡張ローマ字入力  AZIK AZIKの入力方法 まずはこれだけは覚えよう 「っ」の入力方法 まず通常のローマ字入力と大きく違う点として、「っ」の入力を行うときには「;」キーを使用します。この変更によるメリットとしては、「あっ」といった「母音 + 子音」といった組み合わせの場合の入力が大幅に早くなります。このルールが先程あげていた通常のローマ字入力と共存出来ないルールです。「ssa」で「っさ」と入力することができなくなるのでこのルールだけは最初に習得する必要があります。 拡張キー 日本語には出現頻度が高い「読み」があります。たとえば、「かん」、「さい」などは非常に出現頻度が高い文字列で、これらのパターンには、「2文字めに《ん》がくるパターン」と「二重母音」のパターンが多いです。そこで、ローマ字テーブルを拡張する基本方針としてこれら2つのパターンの文字列の入力を2ストロークキーで打てるようにされています。 撥音拡張 「2文字めに《ん》がくるパターン」の打ち方です。これを撥音拡張といいます。 撥音拡張ではローマ字の2ストローク目の母音キーの代わりに、その母音キーの下のキーを使います。 たとえば「かん」を打つには、1ストロークめの子音は「K」そのままですが、2ストロークめは「A」の下のキーの「Z」を使います。つまり「KZ」と打ちます。「か」を打つのと同じ指使いで、2ストローク目の左の小指を一段下にずらすと「かん」になります。 機能 キー 覚え方 ann Z Aの下 inn K Iの下 unn J Uの下 enn D Eの下 onn L Oの下 単独の「ん」はQを使います。これは以下のように、「母音+ん」を打つときに使います。 あん(AQ)、いん(IQ)、うん(UQ)、えん(EQ)、おん(OQ) ※単独の「ん」は通常のように「NN」で打っても構いません。 また、 AZIKの原則ではAの下のZを「あ段」の撥音拡張に使いますが、この原則では「さん、だん、わん」など、1ストローク目の子音キーが左手に位置する文字は非常に打ちにくくなります。そこで、1ストロークめの子音が左手になる行の「あ段撥音拡張キー」は「N」で代用できるようにしてあります。(撥音互換キー) 読み 入力 だん DN がん GN ざん ZN さん SN たん TN わん WN らん RN 二重母音拡張 [ai][uu][ei][ou] などの二重母音が含まれる場合の打ち方です。原則は対応する母音キーの(下以外の)すぐ近くのキーを使います。 たとえば「さい」を打つには、さ行の子音キーの「S」と、「A」の上のキー「Q」、つまりSQと打ちます。「さ」を打つ指使いで、2ストローク目の左の小指を一段上隣りにずらすと「さい」になります。 機能 キー 覚え方 ai Q Aの上隣り uu H Uの斜め下隣り ei W Eの左隣り ou P Oの右隣り あ行の二重母音「あい」「うう」「えい」「おう」は、普通のローマ字のように、それぞれ、AI、UU、EI、OUと打ちます。 特殊拡張 頻出文字列の中には、「もの」「こと」などのように、撥音でもなければ、二重母音でもないパターンがあります。これらの文字列も次のように2ストロークで打てるようになっています。 これらの拡張を《特殊拡張》と呼びます。特殊拡張は、前述の撥音拡張や二重母音拡張とちがって文字のイメージから連想できるようになっています。 KT こと ST した TT たち HT ひと WT わた MN もの MS ます DS です KM かも TM ため DM でも KR から SR する TR たら NR なる YR よる RR られ ZR ざる MT また TB たび NB ねば BT びと GR がら GT ごと NT にち DT だち WR われ その他 外来語に多い特殊な拗音のうち、ウォ、ティ、ディなどは以下のように、1ストロークめの子音キーに続けてその下のキーを打ち、その後母音キーや拡張キーを打ちます。 ウォ WSO ティ TGI ディ DCI 特に外来語にしか見られない読みは直接長音記号が入力されるようにしてあります。 ウォー WP フォー FP それでもまだ打ちにくいパターンでは個別に次のような形で打てるようにしてあります。 わい WF(WQ) さい SF(SQ) せい SS(SW) ざ ZC(ZA) ざい ZV(ZQ) ぜ ZF(ZE) ぜい ZX(ZW) AZIKの導入の仕方 AZIKを利用するにはIMEに対してAZIK用のローマ字テーブルを設定して上げる必要があります。私の場合は日本語入力としてGoogle日本語入力を使用しているので、Google日本語入力での導入方法を紹介します。 まず設定用のtxtファイルをダウンロードしてください。 azik ダウンロード 続いて右下の「あ」を右クリックして、「プロパティ」を開きます。 「ローマ字テーブル」の「編集」をクリックします。  ポップアップが表示されたら、「編集」をクリック後、「インポート」を選択します。ポップアップで「現在のローマ字テーブルを上書きしますか?」と出てくるので、「OK」を選択後、先ほどダウンロードしたファイルを選択します。その後、「適応」をクリックすることでAZIKが使用可能になります。 まとめ 今回は、標準的なローマ字入力よりも効率的に日本語を入力できる拡張方式 AZIKについてご紹介しました。慣れるまでは少し時間がかかるかもしれませんが、AZIKを導入することで大幅に入力速度を向上させることが可能ですので是非1度試してみてください。 また、AZIKをさらにカスタマイズしてより多くの単語を2語で入力できる拡張AZIKなるものを考えている方もいらっしゃるみたいなので今回の記事で興味を持った方はそちらも調べてみると、さらなる入力速度向上を目指せるかもしれません。 ではまた~ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 日本語の打鍵効率を上げる! 拡張ローマ字入力AZIKのご紹介 first appeared on SIOS Tech. Lab .
今回は、「 実体験:Notion AIでブログ執筆が楽になる!3つの使い方をふんわり解説 」で紹介したタイトルとメタディスクリプション作成プロンプトを改善しました。プロンプトの紹介をしつつ、プロンプト作成の方法について紹介していきます。 プロンプト紹介 前回のプロンプトでは、タイトルとメタディスクリプションを同時に作成していました。プロンプトをより特化させるために分割しています。検証するためにサンプルとして、「 GASコード付き:URLからHTML取得&圧縮する方法|AI入力の最適化に 」を対象として実行しています。 Notion AIでは、記事の内容を入力として使用することができます。タイトル用とメタディスクリプション用では、記事の内容から抽出したキーワードをプロンプトに組み込んでいます。これによって汎用的なプロンプトとして利用することができます。 タイトル用プロンプト 主要なキーワードを抽出し、それをもとにクリックされやすいブログ記事タイトルを作成してください。 抽出した内容をもとにクリックされやすいブログ記事タイトルを作成してください。以下のルールを守ってください。 - 文字数は32〜40文字(検索結果で適切に表示される長さ) - 重要なキーワードを前半に配置(SEO効果を最大化) - 具体的な数字、疑問形、ベネフィットを含める(例:「最新」「初心者向け」「〇〇の方法」) - クリックを促す表現を使用(例:「今すぐ知るべき」「〇〇とは?」) - ブログタイトルを複数提案してください。 実行例 ## クリックされやすいブログ記事タイトル案 主要キーワード:Google Apps Script (GAS)、HTML取得、テキスト圧縮、AI入力最適化、正規表現 - 【GAS完全ガイド】HTMLスクレイピング&圧縮の実践手順|AI入力を最適化する3つの方法 - 【2025年版】GASでURL取得からHTML圧縮まで!初心者でもできるAI入力の効率化テクニック - 【GAS×AI開発】HTMLデータを最適化!実務で使える自動テキスト圧縮の実装方法とは? - 【保存版】GASプログラミング入門:HTMLスクレイピングから圧縮まで完全解説 - 【実践ガイド】GASでAI入力を最適化!HTMLデータ取得&圧縮の基本から応用まで メタディスクリプション用プロンプト 主要なキーワードを抽出し、それをもとにSEOに強いメタディスクリプションを作成してください。 抽出した内容をもとにブログ記事のメタディスクリプションを作成してください。以下のルールを守ってください。 - 文字数は70〜100文字(検索結果で適切に表示される長さ) - 記事の概要を簡潔に伝える(読者が知りたい情報を明確に) - 行動を促すフレーズを含める(例:「今すぐチェック」「詳しくはこちら」) - 自然な文章にする(単なるキーワードの羅列ではなく、読者が読みたくなる文章に) - 複数のメタディスクリプションを作成してください。 実行例 ## SEOメタディスクリプション案 主要キーワード:Google Apps Script (GAS)、HTML取得、テキスト圧縮、AI入力最適化、正規表現 - 案1:「GASでURLからHTMLを取得し、AIモデル入力用に最適化する方法を詳しく解説。imgタグのalt抽出から空白削除まで、実践的なコード例で学べます。効率的なテキスト処理を今すぐマスター!」 - 案2:「Google Apps ScriptでWebページのテキストを効率的に圧縮する方法を完全解説。AI入力の最適化に役立つHTMLタグ処理テクニックを、サンプルコード付きで詳しく紹介します。」 - 案3:「URLからHTMLを取得し、AI入力に最適な形式に圧縮するGASの実装方法を解説。正規表現を使った実践的なコード例で、すぐに使える技術が身につきます。」 お気に入りのプロンプトはお気に入り登録しましょう。 Notion AIではプロンプトをお気に入り登録することができます。登録したプロンプトは入力せずに実行することができます。お気に入りの登録手順を解説しておきます。 まずは、プロンプトを一度実行しておく必要があります。保存したいプロンプトを「最近」から選択してお気に入り登録します。 お気に入り登録したプロンプトは、「お気に入り」欄に表示され編集が可能になります。 編集では、プロンプト内容とアクション名を登録することができます。「すぐに実行」を True にすることで、「お気に入り」から選択したタイミングで実行されます。 これとは別にプロンプト改修を行えるように、Notionのページやブログでプロンプトを保存しておくと便利です。 プロンプト改修の手順紹介 Notionの公式ではプロンプトのテクニックが紹介 されています。プロンプト作成では、明確で具体的に記載することが求められます。指示が明確であるほど、求めている情報を取得することができます。 この具体化がとても難しいです。そこで、複数の生成AIを活用してプロンプトを作成しました。今回の手順は、以下の手順です。 Felo を用いて初期調査・プロンプトのひな形を作成 ChatGPTやGeminiを用いて、初期プロンプトをしばいて中間プロンプトを作成 中間プロンプトを別のAIに評価させる まずは、検索型のAIを用いてプロンプト作成に必要な情報を収集して、初期プロンプトを作成させます。初期プロンプトを複数のAIの力を借りて改修していきます。最終的に複数の生成AIから合格ができれば、採用して使用感をチェックすることでよいプロンプトを作成できます。 龍ちゃん この辺りも自分の中でまとまれば執筆しますね! 終わり 今回は、Notion AIのプロンプト改善について、タイトルとメタディスクリプションの分割方法や具体的なルール、プロンプトのお気に入り登録機能、そして複数の生成AIを活用したプロンプト作成手法を紹介しました。これらの手法を活用することで、より効果的なプロンプトを作成することができます。皆さんもぜひ試してみてください! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【2025年版】Notion AIプロンプト改善|タイトル&メタディスクリプション first appeared on SIOS Tech. Lab .
はじめに 近年、コンテナ技術は企業のITインフラにおいて不可欠な存在となりました。 アプリケーションの開発・運用をスムーズにする一方で、適切に管理しなければスケールやセキュリティの課題が発生します。 前回の記事でコンテナプラットフォームの概要として紹介した代表的なものとして Red Hat OpenShift や SUSE Rancher などがあり、Kubernetesを基盤としながら、運用を簡素化する機能を提供しています。 本記事では、コンテナ管理プラットフォームに求められる主な機能について解説し、どのようなポイントが選定時に重要になるかを考えていきます。 コンテナプラットフォームに求められる主な機能 コンテナプラットフォームを適切に運用するためには、以下のような機能が求められます。 ① クラスタ管理 コンテナ管理プラットフォームの最も基本的な役割は、 Kubernetesクラスタの管理 です。 Kubernetesクラスタ管理についての主な機能は下記となります。 マルチクラスタ対応 :複数のクラスタを統合的に管理し、異なる環境(オンプレミス、クラウド)にデプロイ可能 オートスケーリング :トラフィックの増減に応じて、Podやノードのスケールを自動化 セルフヒーリング :障害が発生した際に、正常な状態へ自動復旧 Kubernetesクラスタ自体の機能を管理するのはもちろんのこと、1つの組織が1つのクラスタを管理することは少なく、 異なるクラウド環境にある多数クラスタを一元管理できる点などが重要となってきています。 ② デプロイと運用の自動化 開発者がスムーズにアプリケーションをデプロイできるよう、 CI/CDパイプラインとの統合 が求められます。 GitOpsサポート :Gitによるコード管理と運用自動化を組み合わせることで Blue-Greenデプロイ :Canaryリリース等を実装することで サービスの切り替えを容易にし運用負荷を軽減 ローリングアップデート  :ダウンタイムなしの更新 OpenShiftやRancherなどでは CIツールとCDツールを組み合わせて CI/CDパイプラインに組み込むことで、開発とデプロイの自動化などを実現できます。 ③ ネットワークとセキュリティ コンテナ環境では、ネットワークの設定とセキュリティ対策が不可欠です。 ネットワークポリシーの適用 (Calico, Cilium, OVN-Kubernetes など) サービスメッシュの統合 (Istio, Linkerd など)でマイクロサービス間の通信管理 コンテナイメージのスキャン (Trivy, Clair など)で脆弱性の事前検知 RBAC(Role-Based Access Control)による権限管理 特にエンタープライズ環境では、 ゼロトラストセキュリティの導入 が求められるため、認証・認可の強化が重要です。 ④ 監視・ログ管理 大規模なコンテナ環境では、可観測性(Observability)が必要になります。 メトリクス監視(Prometheus + Grafana) でリソース使用率を可視化 ログ管理(Fluentd, ELK, Loki) でコンテナの挙動を記録 分散トレーシング(Jaeger, OpenTelemetry) でマイクロサービスのパフォーマンスを解析 これらのツールをコンテナプラットフォームと併せて活用するすることで、障害発生時の迅速な対応が可能となります。 ⑤ マルチクラウド・ハイブリッドクラウド対応 企業では、単一のクラウドプロバイダーに依存せず、 マルチクラウドやハイブリッドクラウド環境 を採用するケースが増えています。 オンプレミス + クラウドの統合管理 異なるクラウド(AWS, Azure, GCP)間のワークロード移動 コスト最適化のための柔軟なリソース管理 まとめ コンテナ管理プラットフォームを選定する際には、以下のポイントが重要です。 ・ クラスタ管理の自動化と拡張性 ・ デプロイの効率化(GitOps、CI/CD) ・ ネットワーク & セキュリティの強化 ・ 監視・可観測性の確保 ・ マルチクラウド・ハイブリッドクラウド対応 OpenShift は エンタープライズ環境 に適し、 Rancher は マルチクラウド運用  などに向いています。 前回の記事でも触れましたがコンテナ技術の進化とともにこれらのプラットフォームも継続的に進化しており、導入前には 自社のユースケースに合ったを重点とすべき機能を適切に把握することが重要 となってきます 。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 企業のDXを支えるコンテナプラットフォームに求められる重要なポイント解説 first appeared on SIOS Tech. Lab .
概要 こんにちは、サイオステクノロジーの安藤 浩です。 今回はOpenseaで提供されているAPIを利用して、アプリ開発をしていますので、Opensea のAPIについて紹介していきます。 OpenSeaは NFT マーケットプレイスで、そのサービスでAPIが提供されているので、APIで提供されている機能をAPIへリクエストしながらご紹介します。 提供されている Endpoints について 大きく分けて3分類のEndpoint があります。ERC721, ERC1155 に対応しているようです。 詳細は以下に記載があります。 https://docs.opensea.io/reference/api-overview Analytics Endpoints コレクションやイベントの分析用のAPIが提供されています。 NFT Endpoints NFTに関連するコレクション、NFT のメタデータ、所有権情報などを取得するためのAPIが提供されています。 OpenSea Marketplace Endpoints OpenSea のNFT マーケットプレイス上での取引に関するAPIなどが提供されています。 ※Stream API が提供されていますが、ここでは触れません。 OpenAPI について OpenAPI Spec は以下で取得できるのでこちらを利用するとリクエストするのに便利です。 https://docs.opensea.io/reference/openapi-definition API Key について 本番用のOpenSea API キーを取得するには、申請書に記入する必要があります。 テストネットではAPI Keyは不要です。 API Keyの詳細に関しては以下に記載があります。 https://docs.opensea.io/reference/api-keys NFT Endpoints 今回は、主にNFTに関する情報を取得したいと思うので、NFT Endpoints のテストネットのAPIを試したいと思います。(一部抜粋) テストネットのAPIのベースになるURLは以下です。 APIには v1 と v2 がありますが、今回はv2で試します。 https://testnets-api.opensea.io/api/v2   テストネットに対応するOpensea のURLは  こちら  です。 VSCode の拡張機能: Rest Client  を利用して、HTTP リクエストしてみたいので以下のように書いてリクエストしてみます。 以下で利用したRest Client のコードは以下のリポジトリに配置してあります。 https://github.com/Hiroshi-Ando-sti/introduction-openseapi/tree/main @BASE_URL=https://testnets-api.opensea.io @BASE_PATH=/api/v2 ### GET /accounts/{address_or_username} @address=0xCebC36de334CE12dFD08f4C39E833016263Ba5B0 GET {{BASE_URL}}{{BASE_PATH}}/accounts/{{address}} HTTP/1.1 まずは、アカウントの情報を取得するAPIにリクエストしてみます。 ※以降、 @BASE_URL ,  @BASE_PATH  は省略します。 上記を実行した結果はこちらです。 { "address": "0xcebc36de334ce12dfd08f4c39e833016263ba5b0", "username": "hir-ando", "profile_image_url": "https://i.seadn.io/s/raw/files/a02cdbb10a9305a3148d4197f0b0de20.png?w=500&auto=format", "banner_image_url": "https://i.seadn.io/s/raw/files/635aa07c9d1248c744b9c5dd2a709fc0.jpg?w=500&auto=format", "website": "", "social_media_accounts": [], "bio": "STI", "joined_date": "2024-12-24" } Opensea には Collection という概念があり、NFTをまとめるグループのようなものです。 Collection の情報を取れるAPIは以下です。 ### GET /collections GET {{BASE_URL}}{{BASE_PATH}}/collections HTTP/1.1 hiroshinft という Collection があるのでこの情報を取得してみます。 テストネットのOpensea では以下から確認できます。 https://testnets.opensea.io/ja/collection/hiroshinft ### GET /collections/{collection_slug} @collection_slug=hiroshinft GET {{BASE_URL}}{{BASE_PATH}}/collections/{{collection_slug}} HTTP/1.1 実行結果は以下の通りです。(一部省略) Collection の情報がとれていることが分かります。 { "collection": "hiroshinft", "name": "HiroshiNFT", "description": "", "image_url": "", "banner_image_url": "", "owner": "0x36da942099c028275321130b5e503f37da446487", "safelist_status": "not_requested", "category": "", "is_disabled": false, "is_nsfw": false, "trait_offers_enabled": false, "collection_offers_enabled": true, "opensea_url": "https://testnets.opensea.io/collection/hiroshinft", "project_url": "", "contracts": [ { "address": "0xe88df35e01e3e33df38fb0b5e324282feceb20c2", "chain": "sepolia" } ], "editors": [ "0x36da942099c028275321130b5e503f37da446487" ], "fees": [ { "fee": 0.5, "recipient": "0x0000a26b00c1f0df003000390027140000faa719", "required": true } ], "total_supply": 2, "created_date": "2025-02-20" } 今度は Opensea で取得できるChainとContract Address を指定してContract を指定したいと思います。 ### GET /chain/{chain}/contract/{address} @chain=sepolia @contract_address=0xE88Df35e01e3e33Df38FB0B5e324282feCeb20c2 GET {{BASE_URL}}{{BASE_PATH}}/chain/{{chain}}/contract/{{contract_address}} HTTP/1.1 NFTの名前、供給量などが確認できます。 { "address": "0xe88df35e01e3e33df38fb0b5e324282feceb20c2", "chain": "sepolia", "collection": "hiroshinft", "contract_standard": "erc721", "name": "HiroshiNFT", "total_supply": 0 } 個別のNFT の情報を取得したいと思います。ここでは  @identifier  を2とします。 ### GET /chain/{chain}/contract/{address}/nfts/{identifier} @chain=sepolia @contract_address=0xE88Df35e01e3e33Df38FB0B5e324282feCeb20c2 @identifier=2 GET {{BASE_URL}}{{BASE_PATH}}/chain/{{chain}}/contract/{{contract_address}}/nfts/{{identifier}} HTTP/1.1 NFTの作成者や所有者情報、Opensea のURLなどが確認できます。 { "nft": { "identifier": "2", "collection": "hiroshinft", "contract": "0xe88df35e01e3e33df38fb0b5e324282feceb20c2", "token_standard": "erc721", "name": null, "description": null, "image_url": null, "display_image_url": "", "display_animation_url": null, "metadata_url": null, "opensea_url": "https://testnets.opensea.io/assets/sepolia/0xe88df35e01e3e33df38fb0b5e324282feceb20c2/2", "updated_at": "2025-02-20T13:42:02.649572", "is_disabled": false, "is_nsfw": false, "animation_url": null, "is_suspicious": false, "creator": "0x36da942099c028275321130b5e503f37da446487", "traits": null, "owners": [ { "address": "0xcebc36de334ce12dfd08f4c39e833016263ba5b0", "quantity": 1 } ], "rarity": null } } 上記で取得できた所有者のAddress を指定して、その Address が所有するNFTを情報を取得します。 ### Get NFTs (by account) ### GET /chain/{chain}/account/{address}/nfts @chain=sepolia @address=0xCebC36de334CE12dFD08f4C39E833016263Ba5B0 GET {{BASE_URL}}{{BASE_PATH}}/chain/{{chain}}/account/{{address}}/nfts HTTP/1.1 1件しかないですが、nfts は配列であり複数件あれば複数件取得できます。 { "nfts": [ { "identifier": "2", "collection": "hiroshinft", "contract": "0xe88df35e01e3e33df38fb0b5e324282feceb20c2", "token_standard": "erc721", "name": null, "description": null, "image_url": null, "display_image_url": "", "display_animation_url": null, "metadata_url": null, "opensea_url": "https://testnets.opensea.io/assets/sepolia/0xe88df35e01e3e33df38fb0b5e324282feceb20c2/2", "updated_at": "2025-02-20T13:42:02.649572", "is_disabled": false, "is_nsfw": false } ] } また、Collection を指定して、その Collection 内のNFTの情報を取得できます。 ### Get NFTs (by collection) ### GET /collection/{collection_slug}/nfts @collection_slug=hiroshinft GET {{BASE_URL}}{{BASE_PATH}}/collection/{{collection_slug}}/nfts HTTP/1.1 こちらも先ほど同様で複数件取得が出来るようになっています。 { "nfts": [ { "identifier": "2", "collection": "hiroshinft", "contract": "0xe88df35e01e3e33df38fb0b5e324282feceb20c2", "token_standard": "erc721", "name": null, "description": null, "image_url": null, "display_image_url": "", "display_animation_url": null, "metadata_url": null, "opensea_url": "https://testnets.opensea.io/assets/sepolia/0xe88df35e01e3e33df38fb0b5e324282feceb20c2/2", "updated_at": "2025-02-20T13:42:02.649572", "is_disabled": false, "is_nsfw": false }, { "identifier": "1", "collection": "hiroshinft", "contract": "0xe88df35e01e3e33df38fb0b5e324282feceb20c2", "token_standard": "erc721", "name": null, "description": null, "image_url": null, "display_image_url": "", "display_animation_url": null, "metadata_url": null, "opensea_url": "https://testnets.opensea.io/assets/sepolia/0xe88df35e01e3e33df38fb0b5e324282feceb20c2/1", "updated_at": "2025-02-20T13:39:02.836098", "is_disabled": false, "is_nsfw": false } ] } GET /chain/{chain}/contract/{address}  と似ていますが、Contract Address のNFTの情報を取得します。 ### Get NFTs (by contract) ### GET /chain/{chain}/contract/{address}/nfts @chain=sepolia @contract_address=0xE88Df35e01e3e33Df38FB0B5e324282feCeb20c2 GET {{BASE_URL}}{{BASE_PATH}}/chain/{{chain}}/contract/{{contract_address}}/nfts HTTP/1.1 上記で @identifier=2  を指定した情報と同じ情報が含まれています。  @identifier=1  もMintしてあるのでその情報も含まれていることが確認できます。 { "nfts": [ { "identifier": "2", "collection": "hiroshinft", "contract": "0xe88df35e01e3e33df38fb0b5e324282feceb20c2", "token_standard": "erc721", "name": null, "description": null, "image_url": null, "display_image_url": "", "display_animation_url": null, "metadata_url": null, "opensea_url": "https://testnets.opensea.io/assets/sepolia/0xe88df35e01e3e33df38fb0b5e324282feceb20c2/2", "updated_at": "2025-02-20T13:42:02.649572", "is_disabled": false, "is_nsfw": false }, { "identifier": "1", "collection": "hiroshinft", "contract": "0xe88df35e01e3e33df38fb0b5e324282feceb20c2", "token_standard": "erc721", "name": null, "description": null, "image_url": null, "display_image_url": "", "display_animation_url": null, "metadata_url": null, "opensea_url": "https://testnets.opensea.io/assets/sepolia/0xe88df35e01e3e33df38fb0b5e324282feceb20c2/1", "updated_at": "2025-02-20T13:39:02.836098", "is_disabled": false, "is_nsfw": false } ] } 上記で指定していた Contract Address では traits がないので先ほどの情報に含まれていませんでしたが、Traits を取得することができます。 ### Get Traits ### GET /traits/{collection_slug} @traits_collection_slug=usernft-14 GET {{BASE_URL}}{{BASE_PATH}}/traits/{{traits_collection_slug}}/nfts HTTP/1.1 例えば、Collection が  usernft-14  というものだとTraits に  stage  というパラメータがあります。 { "categories": { "stage": "number" }, "counts": { "stage": { "min": 1, "max": 4 } } } まとめ Opensea のAPI の特に NFT Endpoints について取り上げました。 NFT Endpoints のAPIを利用するとCollection 、NFT のメタデータ、所有権情報などを取得することができます。 Opensea のAPIを利用して、NFT の所有者や作成者、Contract の情報が取得できるので活用してみてはいかがでしょうか。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post [Web3] Opensea API を利用した NFT の情報取得 first appeared on SIOS Tech. Lab .
Google Apps Script(GAS)でURLから不要なタグや改行を削除した圧縮したテキスト情報を取得する方法について解説します。 圧縮の処理は以下になります。 imgタグのalt情報の分離 HTMLタグの削除 空白の削除 URLからHTMLを取得して情報を圧縮する まずは、ソースコード全体を貼ります。こちらは「URLを引数として受け取り、HTML情報を取得し、特定のタグ情報内に含まれる情報を圧縮し、テキストで返答する:返答するサンプルとなります。 function testFunction(url) { // HTMLを取得 const html = UrlFetchApp.fetch(url) // HTMLを解析 const document = HtmlService.createHtmlOutput(html).getContent(); var content = ''; // 必要な部分を抽出(この記事の内容が <section class="entry-content"> タグ内にあると仮定します // 当ブログのコンテンツ部分になります const regex = /<section class="entry-content"[^>]*>([\\s\\S]*?)<\\/section>/; const match = regex.exec(document); if (match && match[1]) { const temp = match[1]; // プロンプト圧縮用置換プログラム content = temp.replace(/<img([^>]*?)alt="([^"]+)"([^>]*)>/g, '<img$1$3>$2') //imgのalt属性の抜き出し .replace(/<("[^"]*"|'[^']*'|[^'">])*>/g, '') //タグ削除 .replace(/[\\r\\n]+/g, "") //改行削除 .replace(/[\\s\\t\\n]/g, "") //空白削除 .trim(); //文字列の前後の空白削除 } Logger.log(content) return content } HTML情報の取得には、GASの UrlFetchApp と HtmlService を使用して、テキスト情報を取得しています。 UrlFetchApp : GASでfetchリクエストを行うサービス 。コードでは、GETリクエストを投げてHTML情報を取得しています。 HtmlService : GASでHTMLを扱うサービス 。コードでは、 UrlFetchApp で取得したHTML情報からテキスト情報としてコンテンツを取得しています。 取得したテキスト情報から特定の領域を取得するために、正規表現を使用しています。今回は、当ブログのコンテンツ領域を取得するタグを指定して、タグ間を抜き出しています。 imgタグのalt情報の分離 正規表現を使用して、imgタグに含まれるalt情報をタグの外側に抽出します。 function testFunctionImg() { const htmlText = ` <p>imgタグからalt情報を抽出する</p> <img src="example.jpg" alt="サンプル画像"> <img src="test.png" alt="テスト画像"> `; const updatedText = htmlText.replace(/<img([^>]*?)alt="([^"]+)"([^>]*)>/g, '<img$1$3>$2'); console.log(updatedText); } 実行した結果は、以下になります。 <p>imgタグからalt情報を抽出する</p> <img src="example.jpg" >サンプル画像 <img src="test.png" >テスト画像 HTMLタグの削除 正規表現を使用して、HTMLタグ <> を空文字に置換しています。 function testFunctionRemoveTag(){ const htmlText = ` <p>htmlタグを置換して削除する</p> <img src="example.jpg" >サンプル画像 <img src="test.png" >テスト画像 ` const updatedText = htmlText.replace(/<("[^"]*"|'[^']*'|[^'">])*>/g, ''); console.log(updatedText); } 実行した結果は、以下になります。 htmlタグを置換して削除する サンプル画像 テスト画像 空白の削除 正規表現と JavaScriptのtrim を使用して空白を削除しています。 function testFunctionRemoveBlank(){ const htmlText =` htmlタグを置換して削除する サンプル画像 テスト画像 ` const updatedText = htmlText.replace(/[\\s\\t\\n]/g, "").trim(); console.log(updatedText) } 実行した結果は、以下になります。 htmlタグを置換して削除するサンプル画像テスト画像 情報を圧縮するモチベーション 処理自体は正規表現と既存関数の組み合わせで実現しています。ここでは、なぜ情報を圧縮するのかについて解説をしていきます。 「 Dify入門ガイド:X投稿を自動生成!10分で作るブログPR効率化ワークフロー 」では、URLからPR文の生成を行っていました。内部の処理は、HTMLから情報を取得してLLMに入力しPR文を生成しています。HTML取得の部分はDify製のツールを使用して実装しています。 こちらで作成したプロトタイプを、GASとAOAIを使用したアプリで実現するために本記事を執筆しました。Dify製ツールを使用していた部分を自作しています。HTMLを取得しただけの生の状態では、タグ情報や空白が含まれています。「 生成AIの入力時のトークン量を節約 」のために不要情報を落として情報を圧縮しています。 アプリ化の検討に関しては、「 GASでブログの投稿と連動してAIにPR文を考えてもらう:プロトタイプ 」で検討しています。 終わり 本記事では、GASを使用してURLからHTMLを取得し、必要な情報を抽出・圧縮する方法について解説しました。特にAIモデルへの入力を最適化するために、imgタグのalt情報の分離、HTMLタグの削除、空白の削除という3つの処理を実装しました。このアプローチにより、効率的なテキスト処理が可能になります。 今週は狂ったようにブログを書いてた龍ちゃんです。DifyとGASの検証をしていますが、フロントエンドの検証もやりたいなとふつふつと湧き上がるものを感じています。3月はそっちにも取り組みたいですな!!! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post GASコード付き:URLからHTML取得&圧縮する方法|AI入力の最適化に first appeared on SIOS Tech. Lab .
1. 初めに PS/SLの佐々木です。 最近ContractAddressがどのように導出されているのかが気になり調べていたところ、ContactAddressの導出方法は二つがあることを知りました。その中で事前にコントラクトアドレスを計算して、デプロイ前のコントラクトに対して送金できたり、署名を作成できたりと結構面白そうだったのでまとめてみました。 Ethereumのスマートコントラクトをデプロイするにオペコードというものが存在します。オペコードには * CREATE ** および ** CREATE2 * の二つの種類があります。 この記事では、 CREATE と CREATE2 の違いを比較し、 CREATE2 の理解や使いどころの理解の手助けになればと思います。 2. opcodeとは EthereumのスマートコントラクトはEVM上で実行され、EVMは低レベルの命令セット(opcode)を用いて処理を行います。 CREATE や CREATE2 は、新しいスマートコントラクトをデプロイするためのopcodeです。 *CREATE :一般的なスマートコントラクトのデプロイ手段。 *CREATE2 :特定の計算方法を用いてコントラクトアドレスを決定できる。 これらの仕組みを理解することで、スマートコントラクトの事前アドレス計算や資金送付の最適化が可能になります。 3. CREATEの仕組み *CREATE* * opcodeは、新しいスマートコントラクトをデプロイする際に利用されます。デプロイされたコントラクトのアドレスは、以下の要素から決定されます。* keccak256(RLP_ENCODED(msg.sender+ nonce)) ここで、 nonce はコントラクトを作成するアカウント(EOAまたはコントラクトアカウント)のトランザクション回数です。このため、任意のタイミングでコントラクトをでデプロイしたときのコントラクトアドレスを予測することができません。 4. CREATE2の仕組み *CREATE2* * opcode は EIP-1014 で導入され、より予測可能な方法でコントラクトのアドレスを決定できるようになりました。 CREATE2 を使用した場合、コントラクトのアドレスは以下の式で決定されます。* keccak256(0xFF + 創設者のアドレス + salt + keccak256(バイトコード)) ここで salt は任意の32バイトの値です。 CREATE2 を使用することで、生成されるコントラクトアドレスが nonce に依存しなくなり任意のタイミングでデプロイする場合でもコントラクトアドレスを事前に計算することができます。 CREATE2の制約 デプロイ時のバイトコードが一致している必要がある 事前に計算したアドレスと実際にデプロイされるアドレスが一致するためには、同じ bytecode を使用する必要があります。 異なる salt を用いると異なるアドレスが生成される *salt* を変更すると、同じコントラクトコードでも異なるアドレスが生成されます。* 事前にアドレスを計算できるが、バイトコードの変更は不可 事前に計算したアドレスに対してバイトコードを変更すると、意図したアドレスでデプロイできなくなります。 デプロイにはコントラクトのみから実行可能で外部アカウントであるEOAからは呼び出し不可 CREATE2のユースケース 異なるチェーン間で同じコントラクトアドレスを使用したい場合 デプロイ前にトランザクション署名が可能 NFTををMintするコントラクトをデプロイする前に署名を事前に作成可能 デプロイのタイミングを最適化&コントラクトアドレスを事前告知可能 デプロイ前に送金したETHの扱いについて CREATE2 を使用すると、デプロイ前のアドレスが確定するため、実際にコントラクトがデプロイされる前に、そのアドレスへETHを送ることが可能になります。しかし、この時点ではそのアドレスにコードは存在しません。 EVMの仕様上、コードが存在しないアドレスに送金されたETHはそのアドレスにロックされ、誰もアクセスできなくなります。そのため、コントラクトが正しくデプロイされる前にETHを送る場合は、確実にデプロイできることを確認する必要があります 5. CREATE と CREATE2 の違い 項目 CREATE CREATE2 アドレス計算 keccak256(address + nonce) keccak256(0xFF + address + salt + keccak256(bytecode)) 事前計算 不可能 可能 デプロイ方法 通常のトランザクション saltを利用してアドレスを固定化 送金 デプロイ前に送金できない デプロイ前に送金可能 8. まとめ *CREATE2* を活用すると、特定のアドレスでのスマートコントラクトの動作を保証できる。* *computeAddress* 関数を活用することで、デプロイ前にコントラクトアドレスを予測できる。* ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post デプロイ前のコントラクトアドレスを知りたい – CREATE2 Opecodeについて – first appeared on SIOS Tech. Lab .
こんにちは、サイオステクノロジーの遠藤です。 弊社ではこの度、Azure OpenAI サービスを基盤として最短一日でRAG環境を提供するAzure OpenAI Service RAGスターターパックのサービス提供を始めました。 本ブログではAzure OpenAI Service RAGスターターパックを利用して検索機能の評価を行う方法を確認します。 また、より詳細なサービス概要は 弊社ソリューションサイト SIOS NEXT TECH SOLUTIONS からもご確認いただけます。ご興味のある方は是非アクセスしてみてください。 Azure OpenAI Service RAGスターターパックとは? Azure OpenAI Service RAGスターターパック(以下「RAGSP」という)は社内データといった独自情報を外部知識として活用する生成AI技術であるRAG(Retrieval-Augmented Generation)を利用したアプリケーションを提供可能なパッケージとなります。 本パッケージではクラウドインフラからアプリケーションまでを最短一営業日で構築し、チャットUI+回答精度の評価改善までのオールインワン基盤をご提供します。 評価の利用手順の概要 RAGSPでは以下の手順で評価を行うことが出来ます。 プロンプトフローのインポート 評価の実行 利用するプロンプトフローの選択 評価用のデータセットの追加 評価項目の設定 評価の実行 結果の確認 注目 プロンプトフローとは? プロンプトフロー(Prompt Flow)は、Azureが提供している生成AIのプロンプト開発・評価・管理を効率化するためのツールです。RAGSPでは概要図のバックエンドAPIに当たる部分がプロンプトフローで構築されています。 プロンプトフローの最大の特徴は「ノード」と言われる処理の単位の流れをグラフィカルに表示し、フローの流れを直感的に追える点にあります。例えば以下の画像の例では、inputノードでアプリの入力を受け取り、LLMノードで入力された質問を処理し、それをpythonできれいな形に整形して最終的にアプリケーションの出力として吐き出している様子が表現されています。 RAGSPでの具体的なノードの構成と処理の流れは以下のような形になっていて、これをAzure Functions上で動作させてBackend APIとして扱っています。 プロンプトフロー単体に関するブログも執筆しておりますので、よりプロンプトフローについての知識を深めたい方は以下の記事も合わせてご拝読ください。 2025-02-19 【Azure】プロンプトフローで生成AIを評価してみよう プロンプトフローのインポート では、実際に評価を行う方法について確認していきましょう。プロンプトフローの評価はAzure AI FoundryまたはAzure Machine Learning Studioを利用して行うことが可能ですが、今回はAzure AI Foundryを利用してプロンプトフローを利用する方法について確認していきます。 また今回インポートを行うプロンプトフローの接続先として、 RAGテスト用就業規則 をデータソースとして学習させたものを利用しております。 RAGSPで環境を作成すると、「hub-<環境名>」というリソースが作成されます。そのリソースに移動し、「Launch Azure AI Foundry」をクリックしましょう。 クリックすると、Azure AI Foundry 管理センターが表示されます。「新しいプロジェクト」をクリックします。そうするとポップアップでプロジェクト名が求められるので入力します。 完了後、プロジェクト画面に遷移します。遷移後の左のタブの「プロンプトフロー」を選択することでプロンプトフローを利用することが出来ます。 画面が遷移したら、「作成」をクリックします。 「ローカルからアップロード」を選択します。 そうするとポップアップが表示されで、「参照」クリックしてRAGSPのソースコードからプロンプトフローをアップロードします。その後、「フローの種類の選択」から「Chat Flow」を選択し、アップロードを行います。 そうするとRAGSPを構成しているプロンプトフローをAzure AI Foundryにアップロードが完了します。 評価の実行 インポートが完了したら、ここからは実際に評価を行っていきましょう。 利用するプロンプトフローの選択 AI Foundry左側のメニューから「評価」をクリックします。その後、「新しい評価」をクリックしましょう。 そうするとポップアップが表示されるので、一番下のプロンプトフローを選択します。 画面遷移後、「どのフローを評価しますか?」で先ほど作成したフローを選択します。選択できたら、「次へ」をクリックします。 コピーしました! 評価用のデータセットの追加 画面が遷移したら、「データセットの追加」をクリックし、「ファイルをアップロードする」を選んで評価に使用するファイルをアップロードします。 評価にはcsv形式またはJSONL形式のファイルを利用することが出来ます。わかりやすいように一部省略している部分もありますが内容は質問文にあたるquestionと質問に対する正しい答えであるground_truthから構成されています。今回は評価がきちんとされているか判断するために「定年は何歳ですか?」のground_truthとして70歳という誤った内容を入れてみました(真に正しいの答えは65歳)。また、学習に利用した RAGテスト用就業規則 には記載されていない内容である「ChatGPTはどの会社が提供するサービスですか?」と「Cosmos DBとはなんですか?」というquestionを入れてみました。これで評価がどうなるのかを確認していきましょう。 question ground_truth 試用期間は何ヶ月ですか 3ヶ月 1週間の所定労働時間は何時間ですか? 40時間 採用されたときに出す必要のある書類はなんですか 住民票記載事項証明書、自動車運転免許証の写し、資格証明書の写し、その他会社が指定するもの 結婚したときに慶弔休暇はありますか? 1日あります 試用期間は最初の何日以内であれば即時解雇できますか? 14日以内です 定年は何歳ですか? 70歳 ChatGPTはどの会社が提供するサービスですか? OpenAI Cosmos DBとはなんですか? Cosmos DB は、最新のアプリ開発に対応するフル マネージドの NoSQL およびリレーショナル データベースです ファイルのアップロードが完了すると、 ①にアップロードしたファイルのプレビューが表示されます。 ②ではプロンプトフローのデータセットマッピングを行うことが出来ます。 設定が完了したら③の「次へ」を押しましょう。 評価項目の設定 画面遷移後、どのようなメトリックで評価を行うかを設定します。評価できる項目としてはプレビューなものも含めて16種類の項目から評価することが出来ます。今回はその中でもRAGの評価に使用されるものに絞ってご紹介します。 項目 評価内容 根拠性 生成 AI アプリケーションで生成された回答が、入力ソースからの情報とどの程度一致しているかを計測します。 関連性 生成 AI アプリケーションで生成された回答がどの程度適切で、提示された質問に直接関連するかを計測します。 類似性 ソース データ (グラウンド トゥルース) 文と生成 AI アプリケーションで生成された応答の間の類似性を計測します。 F1スコア F1 スコアは、生成 AI アプリケーションの予測とソース データ (グラウンド トゥルース) の間で共有される単語の数の比率を測定します。 ROUGEスコア ROUGE は、モデルの出力を参照サマリと比較してテキスト生成の品質を測定します。 今回の評価対象は比較的取り回しのよい、「根拠性」「関連性」「類似性」について評価を行ってみます。画面から「根拠性」「関連性」「類似性」にチェックをつけたら、その下の「接続」と「デプロイ名/モデル」を選択し、評価に利用するモデルを設定します。 画面を下にスクロールすると評価に必要な項目とデータセットのマッピング画面があるのでマッピングを行います。今回はcontextとしてアプリのcontext、responseとしてアプリのoutput、queryとしてデータセットのquestion、ground_truthとしてデータセットのground_truthを設定します。 評価の実行 評価の確認画面が表示されるので画面下部の「送信」をクリックすると評価が始まります。 結果の確認 評価が完了したら、評価レポートを見てみましょう。メトリックダッシュボードから評価項目として設定したグラフを見ることが出来ます。今回のデータでは「根拠性」と「関連度」に関しては二極化傾向があり、「類似性」に関しては偏りの少ない分布が見られますね。 個別の評価結果を見る方法についても確認しましょう。各データに対する評価内容を詳しく見るには画面上部の「データ」タブをクリックすることで見ることが出来ます。 ①では質問文に対して作成したアプリがどのような結果を返したのかを確認することが出来ます。 ②では「生成 AI アプリケーションで生成された回答がどの程度適切で、提示された質問に直接関連するかを計測」してくれる関連度の点数と、関連度の理由が説明されています。 ③では「生成 AI アプリケーションで生成された回答が、入力ソースからの情報とどの程度一致しているかを計測」してくれる根拠性の点数と、根拠性の理由が説明されています。 ④では回答の根拠として使用される文であるコンテキストが表示されています。RAGSPではコンテキストはAzure AI Searchから受け取っています。 ⑤では「ソース データ (グラウンド トゥルース) 文と生成 AI アプリケーションで生成された応答の間の類似性を計測」してくれる類似性の点数が表示されています。 では、いくつかの評価をピックアップして個別に確認してみましょう。まずは「採用されたときに出す必要のある書類はなんですか」という質問の評価を見てみます。 この場合には、Azure AI Searchからしっかりコンテキストが渡され、それをもとに正しく応答が生成されていることにより、ground_truthの内容とも一致しているため根拠性、類似性ともに最高評価である5となっています。また、生成された回答が適切で、提示された質問に関連する回答を生成出来ているため、関連度も5になっています。 続いて、あえてground_truthとして「70歳」と誤った値を入れてみた「定年は何歳ですか」という質問の回答を見てみます。(正しい答えは65歳) この場合ではクエリと応答の関連度は高いため関連度は高くなっています。また、応答がコンテキストに基づいて生成出来ているため、根拠性は高くなっています。ただ、類似性については応答とあえて間違った内容を設定したground_truthに違いがあるため低い値となっています。 最後にRAGのデータソースとして利用した RAGテスト用就業規則 にない答えを設定した「ChatGPTはどの会社が提供するサービスですか?」と「Cosmos DBとはなんですか?」という質問に対する回答を見てみます。 この場合は応答が特徴的で、RAGSPではハルシネーションを避けるためにコンテキストを参照した答えを作成できない場合には、「ご質問に一致するデータが無く回答出来ませんでした。」と出力するように設定しています。このような出力がなされた場合には、回答の生成に必要なデータソースが登録されていなかったり、プロンプトの設定が悪いなどが考えられますので、評価結果に応じてデータソースの追加やチューニングを行っていくことが必要になります。評価に戻りますが、「ご質問に一致するデータが無く回答出来ませんでした。」応答を設定しているため、「関連度」「根拠性」「類似度」については最低値の1が出るようになっています。 ハルシネーションとは? AIが事実と異なる情報をもっともらしく生成してしまう現象のこと。特に、大規模言語モデル(LLM)では、学習データにない内容を補完しようとして誤った回答をすることがある。 例: 「日本の首都は大阪です」と誤った情報を回答する。 「田中太郎は2020年にノーベル賞を受賞しました」と、実在しない事実を作り出す。 まとめ 今回は弊社からリリースしたAzure Open AI Service RAGスターターパック(RAGSP)を利用し、アプリケーションが正しく動作しているかの評価を行いました。Azureが提供するプロンプトフローを利用した評価を提供しているため、安心してアプリケーションの品質をチェックすることができます。 改めての紹介になりますが、以下のページからより詳細なサービス概要をご確認いただけますので是非一度ご覧いただければと思います。お問い合わせをお待ちしております。 「Azure OpenAI Service RAGスターターパック」 サービス概要ページ https://nextech-solutions.sios.jp/genai/azure-openai-service-ragsp.html RAGSP関連記事 2025-01-20 Azure OpenAI Service RAGスターターパック サービス提供開始しました! 2025-01-20 Azure OpenAI Service RAGスターターパック 利用してみた! ~データ登録・チャットUI編~ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Azure OpenAI Service RAGスターターパック 利用してみた! ~評価編~ first appeared on SIOS Tech. Lab .
挨拶 ども!ノリでブログを書いていると、今週は月曜日から毎日ブログを書いている龍ちゃんです。最近は、 Dify入門ガイドシリーズ に力を入れています。 今回は、Azure OpenAI Serviceのお話になります。生成AIを活用したアプリを作成する際に、レスポンスがJSONで返答されるかどうかは重要な要素になります。DifyでもAOAIでも、方法を模索して実装していました。 Difyでの構造化:「 Dify入門ガイド:LLM出力を構造化する!JSONデータ作成の具体的手順 」 AOAIでの構造化:「 AOAI:Gpt-4oでJSON出力に失敗する対症療法 」 AOAIのバージョンアップに併せて「構造化出力」という機能がリリースされていました。こちらを用いてTypescript環境で構造化出力を行うサンプルの実装を行います。 公式のサンプル では、PythonとREST APIの記載があります。 今回は、動くかの確認を目標に進めていきます。 前提条件 公式リファレンス:構造化出力の情報 はこちらにあります。モデルによっては使えない機能なので、サポートされているモデルやバージョンなどを確認して使用してください。アップデートによっては使えなくなる可能性もあるので、公式の情報を確認して実装するのが一番です。 私の環境では、以下のモデルを使用してAPI Key認証で検証しています。 モデル名 モデルバージョン APIバージョン gpt-4o-mini 2024-07-18 2024-08-01-preview Typescriptのライブラリに関する情報ですが、発見できた情報としては以下の二つです。 TypeScript 用 Azure OpenAI ライブラリ – 2.0.0 Azure OpenAI library for TypeScript 実装は、OpenAIの「 Text Generation:Generate JSON data 」を参考に進めていきます。 サンプルでは、「 LINE×生成AI:チャットバトルゲームを作る! 」で作成したAPIのレスポンスオブジェクトを作成します。こちらのサンプルでは、「キャラクター同士を戦わせ戦いの勝者と描写を出力するゲーム」になっています。出力したいJSONは以下になります。 { "winner":"user"|"system", "combatLogs": { "round":number, "combatLog":string }[] } 構造化出力:Typescript json schemaサンプル JSON返答を実現するサンプルの全文を貼ります。 const client = new AzureOpenAI({ endpoint: "******************", apiKey: "*******************", apiVersion: "***************", deployment: "***************", }); const systemPrompt = ` あなたは決闘の審判です。二つのキャラクターの戦闘を見守り、勝敗までの流れを判定してください。 AI側がチャンピオン、ユーザー側が挑戦者です。 次の内容は必ず守ってください。 「チャンピオンのキャラクターが勝利した場合はsystem、挑戦者が勝利した場合はuserと明記してください。」 --- ボクシングチャンピオン主にこぶしで戦う --- 以下のType出力を守った内容を最後に付録として記載してください。 --- { "combatLogs": { "round":number, "combatLog":string }[] } --- 例は以下のようになります。combatLogは小説家のように過大に脚色して演出してください。決闘の勝者を明確にしてください。 --- { "combatLogs": [ { "round": 1, "combatLog": "訓練場の教官が鉄の剣で攻撃しました" }, { "round": 2, "combatLog": "訓練場の教官が鉄の盾で防御しました" } ] } --- `; const result = await client.chat.completions.create({ model: '', messages: [ { role: 'system', content: systemPrompt, }, { role: 'user', content: "剣士 主に剣で戦う" }, ], response_format:{ type:"json_schema", json_schema:{ name:"combat_schema", schema:{ type:"object", properties:{ winner:{ description:"戦いの勝者を記述する。ユーザー側が勝利した場合は「user」、システム側が勝利した場合は「system」を代入", type:"string" }, combatLogs:{ description:"戦いの記録を記述する。roundには記録の順序を記述する。combatLogには記録の内容を記述する。", type:"array", items:{ type:"object", required:["round","combatLog"], properties:{ round:{ type:"number" }, combatLog:{ type:"string" } } } } } } } } }); // text形式で返答される console.log(result.choices[0].message.content) // JSONパースで丸め込む const choice: PromptResultType = JSON.parse(result.choices[0].message.content); 手順としては、AOAIとの通信部分にresponse_formatとしてjson_schemaの定義を渡すことで、構造化出力で返答が返されます。 出力サンプル { "winner": "system", "combatLogs": [ { "round": 1, "combatLog": "リングの中央、チャンピオンであるボクシングチャンピオンが、俊敏な身のこなしで挑戦者の剣士に挑みかかります。鋭い目つきで、彼の拳はまるで猛獣の爪のように速く、恐れを知らない剣士を襲います。" }, { "round": 2, "combatLog": "剣士がその長い剣を振るい、防御の構えを取ります。しかし、チャンピオンは緩急自在に動き、右フックが剣士の面に直撃!衝撃で剣士は後ろに仰け反り、観衆の息を呑む音が響きます。" }, { "round": 3, "combatLog": "剣士が立ち直り、再び心を整えます。彼は素早く踏み込み、切りつけるチャンスを狙うも、ボクシングチャンピオンはその動きを見逃さず、すかさずカウンターのジャブを放つ!剣士は打撃を受けて体勢を崩します。" }, { "round": 4, "combatLog": "次第にチャンピオンの優位が明白になる中、剣士は最後の力を振り絞り、一閃の剣撃を放つ。しかし、予測されたその攻撃をかわしたチャンピオンは、一瞬の隙をついて剣士の腹部に強烈なアッパーカットを叩き込みます!剣士はその場に崩れ落ち、観客は歓声と共にその瞬間を見守ります。" }, { "round": 5, "combatLog": "ダウンした剣士は力なく立ち上がることができず、レフェリーが試合終了を告げる。チャンピオンの圧倒的な強さに、剣士は無情にも敗北を認めざるを得ませんでした。リングの中、勝利の拳を掲げるボクシングチャンピオンの姿が、まるで神々しい光に包まれているかのようです。" } ] } response_format定義方法 今回のサンプルでは、json_schemaを自力で作成しました。Node.jsでresponse_formatを記述する方法は、 「自力」と「Zod」を使う方法の二つ があります。 自力でjson_schemaを指定した場合は、出力がテキスト形式で返答されます。そのため、最終的にJSONをパースして構造を取得しています。 基本はjson schemaに準拠されています が、場合によっては使用できないものもあるようです。 Zodを使う場合は、検証済みのデータが返答されます。 二つの方法に共通して、「安全上の理由」で有効なJSON構造の情報を吐き出さない可能性もあるためエラーハンドリングは必要です。 今回は、検証目的だったのでjson schemaを自力で試しました。 アプリケーションに組み込む場合はzodで組んだ方が良いかと思います。こちらは検証してまとめます。 構造化出力の精度を高める方法 JSON構造とjson schemaを貼ります。 { "winner":"user"|"system", "combatLogs": { "round":number, "combatLog":string }[] } { type:"json_schema", json_schema:{ name:"combat_schema", schema:{ type:"object", properties:{ winner:{ description:"戦いの勝者を記述する。ユーザー側が勝利した場合は「user」、システム側が勝利した場合は「system」を代入", type:"string" }, combatLogs:{ description:"戦いの記録を記述する。roundには記録の順序を記述する。combatLogには記録の内容を記述する。", type:"array", items:{ type:"object", required:["round","combatLog"], properties:{ round:{ type:"number" }, combatLog:{ type:"string" } } } } } } } } システムプロンプトで出力形式の詳細な指示を事前に設定する json schemaの名前をシステムプロンプトの内容に合わせて適切に命名する 複雑な出力が必要な場合は、処理を小さな単位に分割する これらの方法を組み合わせることで、より高精度な構造化出力を実現できます。 システムプロンプトでの詳細な指示は、AIモデルが期待される出力形式を正確に理解するために重要です。出力の例(few-shot)を含めることで、より具体的な指示となります。 json schemaの適切な命名は、システムプロンプトの例と一致させることで特に効果を発揮します。また、英語として不自然でないことも重要です。定義している情報が出力したい情報と一致していることで高い精度での情報抽出が行えます。 複雑な出力を小さな単位に分割することは、各部分の精度を個別に向上させることができます。複数の戦闘を行わせる場合では、戦闘ごとの描写の抽出が難しくなる可能性があります。この場合は、戦闘ごとにプロンプトを分割するべきです。 これらの方法を実装することで、より信頼性の高い構造化出力を実現し、アプリケーションの品質向上につながります。 終わり 今回は、Azure OpenAI Serviceを使用してJSON形式の構造化出力を実装する方法について解説しました。システムプロンプトの工夫やjson schemaの適切な設計により、より精度の高い出力を得ることができます。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Azure OpenAI Serviceの構造化出力(JSON)Node検証 first appeared on SIOS Tech. Lab .
こんにちは、サイオステクノロジーの佐藤 陽です。 引き続きCosmosDBを勉強しているので、どんどんアウトプットしていきたいと思います。 今回のテーマはインデックスポリシーです。 インデックスの概念については 前回の記事 にてご紹介しているので、是非ご参照ください! はじめに 今回はCosmosDBのインデックスの中でもインデックスポリシーについてご紹介します。 インデックスポリシーとは、 インデックスを作成するための指示内容 になります。 CosmosDBにおいてはデフォルトで、すべてのコンテナのすべての項目にインデックスが作成されます。 つまりデフォルトでは「全ての項目にインデックスを作成する」といったポリシーが存在しています。 ただし、本当に全ての項目にインデックスを作成するべきかどうかは要件に依ります。 インデックスの作成を必要な項目に絞ることで、無駄なRUの発生が抑えられ、コストの削減やスループットの向上が見込まれます。 つまり今回の記事の内容は、 既にあるインデックスを削減する方向の話 となります。 インデックスポリシー 繰り返しになりますが、インデックスポリシーとはインデックスを作成するための指示内容です。 インデックスポリシーの設定に基づき、CosmosDBはインデックスを作成します。 インデックスポリシーの設定としてはAzurePortalの以下の箇所から確認できます。 こちらの内容がデフォルトの内容になっています。 CosmosDB/データエクスプローラー/Database(Restaurant)/Container(Food)/Settings/IndexPolicy   includedPaths がインデックスを作成する対象となるパスを指定しています。 現在 "includedPaths": [     {         "path": "/*"     } ], となっていることから、全ての項目に対してインデックスが作成されることが分かります。 これは全ての項目に対する検索が高速化されることを意味しており、速度面だけ考えれば喜ばしい事です。 一方で、インデックス作成時には RU(Request Unit) が発生するため、インデックス作成時にコストが発生したり、スループットが落ちたりします。 そう考えると「決して検索対象とならないようなデータ」にもインデックスを作成するのはもったいないですね。 そこで、インデックスポリシーを適切に設定し、最適化していく流れをご紹介したいと思います。 インデックスポリシーの設定値 まずインデックスポリシーの設定値について解説します。 先程示したjsonのプロパティとして以下のものが存在します。 これらを順を追って説明します。 indexMode automatic includedPaths excludedPaths indexMode indexModeはその名通りインデックスを作成するモードになります。 このプロパティが取る値としては consistent と none となります。 consistent ファイルが追加・更新・更新されたタイミングでインデックスの内容が更新されます。 つまり常に最新のデータの状態でインデックスを活用できる一方で、更新されるたびにRUが発生することも考慮する必要があります。 none consistentとは異なり、ファイルなどが追加された場合でもインデックスの内容が更新されません。 noneの使いどころとしては以下2点です そもそもインデックスが必要ない データの大量投入(Bulk Insert)時 2.に関しては、consistentモードで短時間で大量にデータを投入するとインデックスの更新が頻発し、RUも大量に発生します。 そこで、大量のデータを投入する場合は一時的にnoneに設定し、データ投入後に再度consistentに戻すことによりRUを最小限に抑えることができます。 automatic 次に automatic のプロパティですが、実はこれ使うことが非推奨とされているようです。 こちらの stackoverflow にてautomaticに関する質問されている方がいて、それに対してCosmosDBの開発チームの方が以下のように回答されていました。 「自動」プロパティは、ほとんどのコンテナで非推奨とされています。 自動プロパティをfalseに設定すると、インデックスを使用するかどうかに基づいてクエリの結果に不整合が生じることがありました(例えばスキャンとは対照的に)。 (翻訳&要約済み) また、CosmosDBの 公式ドキュメント の日本語のページにはautomaticに関する言及がありましたが、英語のページではその部分が削除されていました。 こういった事からも現状はこのプロパティは無視して良さそうです。 includedPaths 次にincludedPathsについてご紹介します。 こちらはその名の通り、インデックスを作成するアイテムを指定するためのパスになります。 このパスに関しては前回の ブログ記事 で示したような、ツリーを考えると分かりやすいです。 前回と同じ図表を改めて記載します。 パス 値 id /name 寿司 1 /name うどん 2 /name パスタ 3 /price 500 2 /price 1000 3 /price 2000 1 /ingredients/0/name 米 1 /ingredients/0/name 小麦 2 /ingredients/0/name デュラム小麦 3 /ingredients/1/name 魚 1 /ingredients/1/name 塩 2,3 /ingredients/2/name 水 2,3 先程も示しましたが、デフォルトとしては以下のようになっています。 この時、 *(ワイルドカード) はそのパスの配下の全てのアイテムをインデックス作成の対象とすることを示しており、今回はrootの以下全てのアイテムが対象となります。 "includedPaths": [     {         "path": "/*"     } ], この時、例えば 料理名(name) のアイテムに対するインデックスのみを作成したい場合は以下のように設定します。 ここでPathの最後に ? が付与されていますが、スカラー値(文字列または数値)へのパスは/?で終わるようにすることが仕様として決められています。 "includedPaths": [     {         "path": "name/?"     } ], この ? の目的ですが、先程挙げた例だとうまく解説できないので、仮で name の下に以下のようなデータを追加するものとします。 {     "name": {         "japanese":"寿司",         "english":"sushi"     } } この場合、 name のプロパティに対してはインデックスが作成されますが その配下の japanese , english のプロパティに対してはインデックスが作成されないといった形でポリシーが設定されます。 次に 原材料名(/ingredients/0/name) のアイテムに対するインデックスのみを作成したい場合は以下のように設定します。 配列を持つ場合は [] といった表記を用います。 "includedPaths": [     {         "path": "ingredients/[]/name?"     } ], excludedPaths 次はexcludedPathsになります。 こちらもその名の通り、インデックス作成の対象外とするパスを指定します。 パスの設定方法としてはincludedPathsと同様です。 パスの競合 includedPathsとexcludedPathsが競合している場合も想定されます。 例えば以下のようなケースです。 ingredients のパスに関して、includedPathsにもexcludedPathsにも記載があります。 "includedPaths": [     {         "path": "/*"     }     {         "path": "ingredients/[]/name?"     } ], "excludedPaths": [     {         "path": "ingredients/*"     },     {         "path": "/\"_etag\"/?"     }, ], 結論から述べると、今回は includedPaths の設定が優先されます。 理由としては、パスの指定がより具体的(深いパスまで指定されている)であるためです。 Include-Exclude戦略 ここでIncludeとExcludeに関して紹介してきましたが、この設定において 含まれるパスまたは除外されるパスのいずれかとしてルート パス /* を指定する必要がある といった記載がドキュメントにはあります。 つまり以下の2択です。 includedPathsとして全てのパス(/*)を選択し、除外するものをいくつかピックアップしてexcludedPathsに示す excludePathsとして全てのパス(/*)を選択し、対象するものをいくつかピックアップしてincludedPathsに示す どちらが良いかは格納されるアイテムの性質に依るかと思います。 ただ、新規に追加されるプロパティのインデックス作成漏れを防ぐためにも1.の方が良いかなと思います。(MSとしても1.を推奨しています。) まとめ 今回はCosmosDBのインデックスポリシーの基本的な部分についてご紹介しました。 デフォルトでは全てのプロパティに対してインデックスが作成されますが、インデックスポリシーを適切に設定することで無駄なRU発生を防ぎ、より高いパフォーマンス得ることができます。 今回紹介した設定方法などを活用し、是非CosmosDBを最適化していきましょう! ではまた! 参考 https://learn.microsoft.com/ja-jp/azure/cosmos-db/index-policy https://blog.shibayan.jp/entry/20201202/1606905917 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【Azure】CosmosDBにおけるインデックスポリシー入門ガイド【初心者向け】 first appeared on SIOS Tech. Lab .