電通総研 テックブログ

電通総研が運営する技術ブログ

基本概念から理解するAzure AI Search - Azure OpenAI Serviceとの連携まで

こんにちは。XI 本部AIトランスフォーメーションセンター所属の山田です。
先日、部内の勉強会でAzure AI Searchについて紹介したので、テックブログでもその内容を紹介したいと思います。

Azure AI Searchとは?

Azure AI SearchはAzureのフルマネージドの検索サービスで、以下のような機能をサポートしています。

  • 全文検索、ベクトル検索、ハイブリッド検索、あいまい検索、自動補完、geo検索など豊富な検索ソリューションに対応
  • 他のAzureサービスとの強力な統合機能を提供
    • データソースから自動で検索インデックスへの保存処理(インデクサー)
    • OCRやテキストの翻訳などデータソースのコンテンツをAIで解析(スキル、エンリッチメント、ナレッジストア)

最近は、Azure Open AI Serviceと組み合わせたRAGソリューションを実現するためのコンポーネントとして利用されることが多く、Microsoftもこのパターンを強く推しています。

本記事では、Azure AI Searchの基本的な用語・概念の紹介からAzure OpenAI Serviceとの連携についてまで紹介します。

Azure AI Searchに保存されるデータに関する用語と概念

Azure AI Searchで扱うデータ構造について理解をするために「検索インデックス」「スキーマ」「ドキュメント」「フィールド」という用語・概念を整理しておきます。

用語 概要
検索インデックス 検索のためのデータのコレクション。1つの検索インデックス内に複数のドキュメントが格納される。※単に「インデックス」と表記をする場合もあるが本資料内では「検索インデックス」と表記する。
スキーマ 検索インデックスの構造を定義するもの。スキーマは、検索インデックスに格納されるドキュメントにどのようなフィールドが含まれ、それぞれのデータ型など情報を含む。
ドキュメント 検索インデックス内に格納される個々のデータ項目。Azure AI SearchではJSON形式で表現される。
フィールド ドキュメント内の個々の属性を示す。RDBの列に似たもので型が存在する。フィールドによって検索結果の並び替えやフィルタリングが行える。

Azure AI Searchでは、検索インデックスというデータのコレクションを作成し、検索インデックス内に複数のドキュメントを格納する形式でデータを保存します。

Azure AI Searchのスケーラビリティに関する用語と概念

Azure AI Searchのスケーラビリティやコストについて理解するために「SU(検索ユニット、スケールユニット)」「レプリカ」「パーティション」「シャード」という用語・概念について整理しておきます。

用語 概要
SU(検索ユニット、スケールユニット) Azure AI Searchサービスの課金単位。SUの数はレプリカとパーティションの数によって決定される。
レプリカ Azure AI Searchでホストされるインスタンスの数。各レプリカは検索インデックスの完全なコピーを持っており、独立でクエリの処理が可能。レプリカ数を増やすことで負荷分散、高可用性を実現できる。
パーティション Azure AI Searchでのストレージの単位。
シャード 検索インデックスを分割した単位。Azure AI Searchでは各検索インデックスは事前にシャードの単位で分割され、パーティションごとに均等に分散されて保存される。なおシャードは実装の詳細であるためサービス利用者が意識する必要はない。

検索ユニット(SU)はレプリカとパーティションの数によって決まります。
下図はレプリカ数 1、パーティション数 2でのイメージです。この場合、課金単位であるSUは2となります。

ポイントとして可用性に影響を与えるのは「レプリカ数」です。
読み取り/書き取りで99.9%の可用性が必要な場合は3つ以上のレプリカが必要になります。
パーティションは全てのレプリカに適用されるため、レプリカ数の多いリソースでパーティションを増加させるとコストインパクトが大きいため注意が必要です。

実際、レプリカ数3のリソースでパーティション数を2に増加した場合にSAの数が6となりコストが大きく跳ねることのイメージがつくかと思います。

また作成可能な検索インデックスの数、ストレージの容量、スケール上限はプランによって決まっているので注意が必要です。

azure.microsoft.com

Azure AI Searchでサポートされる検索の仕組み

Azure AI Search は「全文検索」と「ベクトル検索」のどちらにも対応しています。

  • 全文検索
    フルテキストインデックスによって作成された転置インデックスを利用してドキュメントを検索します。

  • ベクトル検索
    ベクトルの近傍検索によってドキュメントを検索します。これにより検索クエリを直接含んでいない場合にも意味的な類似したドキュメントを抽出できます。

全文検索について

全文検索機能を理解するために「転置インデックス」と「アナライザー」について整理しておきます。

用語 概要
転置インデックス 文書に含まれる各単語(トークン)とその単語が出現する文書IDの組み合わせによって構成される索引(インデックス)。これにより単語が含まれる文書を高速に探し出すことができる。
アナライザー 転置インデックスを構成する際に、文章を単語(トークン)の単位に分割する処理機能やコンポーネントを指す。アナライザーでは単語分割(形態素解析)やノイズとなる単語(ストップワード)の除去、小文字や原型への変換処理などが行われる。

Azure AI Searchではドキュメントを保存する際に、転置インデックスの作成・更新処理が行われます。

転置インデックスに格納される単語はアナライザーの種類によって単語分割などの処理が異なるため、利用するアナライザーによって転置インデックスに格納される単語が変化します。

ユーザーから入力される検索クエリもアナライザーによる単語の分割が行われ、分割した単語を転置インデックスから探すことでドキュメントの抽出が行われます。

Azure AI Searchのアナライザー

全文検索機能を利用する上でアナライザーの選択は重要になります。
Azure AI Searchで利用可能なアナライザーは大きく分けると2種類あります。

  • 組み込みアナライザー
    Azure AI Searchに標準で組み込まれているアナライザーです。組み込みアナライザーには「標準Luceneのアナライザー」、「言語固有アナライザー」、「特殊アナライザー」があります。言語固有アナライザーはLucene言語アナライザーとMicrosoft言語アナライザーがあります。
  • カスタムアナライザー
    ユーザー定義のアナライザーです。組み込みアナライザーを拡張するようなこともできます。

アナライザーを特に指定をしない場合「標準Luceneのアナライザー」が利用され、転置インデックスが作成されます。
選択するアナライザーによって処理結果がどのように変化するかを少し紹介します。
ここでは組み込みアナライザーの「標準Luceneのアナライザー(standard)」「Luceneの日本語アナライザー(ja.lucene)」「Microsoftの日本語アナライザー(ja.microsoft)」「キーワードアナライザー(keyword)」を使って処理して結果の違いを紹介します。

入力文は以下で試しています。

私は機械学習エンジニアです。

アナライザー 分割結果
standard

 [{'token': '私', 'startOffset': 0, 'endOffset': 1, 'position': 0},
   {'token': 'は', 'startOffset': 1, 'endOffset': 2, 'position': 1},
   {'token': '機', 'startOffset': 2, 'endOffset': 3, 'position': 2},
   {'token': '械', 'startOffset': 3, 'endOffset': 4, 'position': 3},
   {'token': '学', 'startOffset': 4, 'endOffset': 5, 'position': 4},
   {'token': '習', 'startOffset': 5, 'endOffset': 6, 'position': 5},
   {'token': 'エンジニア', 'startOffset': 6, 'endOffset': 11, 'position': 6},
   {'token': 'で', 'startOffset': 11, 'endOffset': 12, 'position': 7},
   {'token': 'す', 'startOffset': 12, 'endOffset': 13, 'position': 8}]
        
ja.lucene

[{'token': '私', 'startOffset': 0, 'endOffset': 1, 'position': 0},
   {'token': '機械', 'startOffset': 2, 'endOffset': 4, 'position': 2},
   {'token': '学習', 'startOffset': 4, 'endOffset': 6, 'position': 3},
   {'token': 'エンジニア', 'startOffset': 6, 'endOffset': 11, 'position': 4}]
        
ja.microsoft

[{'token': '私', 'startOffset': 0, 'endOffset': 1, 'position': 0},
   {'token': '機械', 'startOffset': 2, 'endOffset': 4, 'position': 2},
   {'token': '学習', 'startOffset': 4, 'endOffset': 6, 'position': 3},
   {'token': 'エンジニア', 'startOffset': 6, 'endOffset': 11, 'position': 4},
   {'token': 'です', 'startOffset': 11, 'endOffset': 13, 'position': 5}]
        
keyword

 [{'token': '私は機械学習エンジニアです。',
    'startOffset': 0,
    'endOffset': 14,
    'position': 0}]
        

同一の文に対する処理結果を比較することで、それぞれのアナライザーの特徴を大まかにですが把握できます。
標準Luceneのアナライザー(standard)は多言語にも対応するため、日本語を扱う際には区切られる単語長が短い傾向にありそうです。
言語アナライザーであるLuceneの日本語アナライザー(ja.lucene)の場合は日本語の検索で使いやすいように名詞の抽出が行われています。
同様に言語アナライザーであるMicrosoftの日本語アナライザー(ja.microsoft)の場合はLuceneの日本語アナライザー(ja.lucene)との違いとしては助動詞「です」が含まれていました。
特殊アナライザーのキーワードアナライザー(keyword)は入力文を区切らずに全体を1つの単語として扱います。

これらの違いは入力する文章によっても異なりますが、選択したアナライザーがどのような特徴を持っているかを把握しておくことは重要です。

ベクトル検索について

ベクトル検索機能について理解するには「Embedding(埋め込みベクトル、分散表現)」「vectorizer(ベクタライザー)」「最近傍検索」「近似最近傍検索」について整理しておきます。

用語 概要
Embedding(埋め込みベクトル、分散表現) テキストなどのデータを多次元のベクトルとして表現したもの。意味的に類似性の高いコンテンツはベクトル空間内で互いに近くに位置する。
vectorizer(ベクタライザー) テキストなどembeddingに変換するコンポーネント
最近傍検索 与えられたクエリポイント(ベクトル値)に最も近いデータポイントをベクトル空間から探索する方法。クエリポイントと各データポイントのベクトル間の距離を計算し、最も距離が小さいポイントを特定する必要があるため大規模なデータほど計算コストが必要になる。
近似最近傍検索 与えられたクエリポイント(ベクトル値)に近似的に最も近いデータポイントをベクトル空間から探索する方法。正確さを犠牲にしてパフォーマンスを向上させる方法であり、大規模なデータにも対応できる。

ベクトル検索では事前に文書群をベクトル化し、検索インデックスのドキュメント内のフィールドとして保存しておきます。
ユーザーから入力される検索クエリもベクトル化し、ベクトルの近傍検索によってドキュメントの抽出を行います。
このとき、ユーザーの検索クエリと保存されているベクトルの次元がそろっている必要があります。

なおAzure AI Searchのvectorizer(ベクタライザー)は2024年2月現在パブリックプレビュー版機能であるため注意が必要です。

ベクトルの近傍検索アルゴリズム

Azure AI Searchではベクトルの近傍検索のアルゴリズムとして最近傍検索のk近傍法(kNN)と近似最近傍検索のHierarchical Navigable Small Worlds(HNSW)をサポートしています。
デフォルトではHNSWが利用されますが、これはAzure AI Searchを利用する際に保存するデータ量の上限が事前にわからないシナリオが多いためです。
なおパラメータ指定などについては、若本さんの記事が参考になるのでこの部分に興味がある方はぜひご覧ください。

tech.dentsusoken.com

ハイブリッド検索

Azure AI Searchでは、全文検索とベクトル検索を組み合わせたハイブリッド検索をサポートしています。
ハイブリッド検索は全文検索とベクトル検索の結果をReciprocal Rank Fusion (RRF) というアルゴリズムで再ランク付けするもので内部的には全文検索とベクトル検索の両方が行われます。
詳細についてはMicrosoft Learn側で紹介されているため興味がある方はご覧ください。

learn.microsoft.com

検索インデックスのスキーマ設計

実際にAzure AI Searchを使って検索サービスを実現するには、検索インデックスを作成する必要があります。
検索インデックスを作成する際は、検索対象のデータ特性とユースケースに合わせてスキーマ設計を行います。

フィールドにはデータ型に加えて、検索時にフィールドがどのように使用されるかを表す属性を定義します。
Azure AI Searchで定義可能な属性は以下になります。

属性 概要
searchable フルテキストインデックスを作成するかを制御する。trueのフィールドには指定されたアナライザーで転置インデックスが作成される。
filterable $filterクエリで参照できるかを制御する。
sortable ソート対象に利用できるかを制御する。
facetable 検索結果の集約化に利用するかを制御する。
key ドキュメントの一意識別子となるフィールド。このフィールドは文字列(Edm.string)で定義される。
retrievable 検索結果に含めるかを制御する。falseにしたフィールドはスコアリングの内部ロジックなどに応用できる。

スキーマ設計時はユースケースに応じてフィールドの属性を設定することが重要です。
リッチなスキーマを定義することで強力な検索機能を実現できますが、より多くのストレージを利用することになります。
特に検索可能(searchable)なフィールドは必要なものだけを定義し、無駄な転置インデックスが作成されるのを防ぎましょう。
フィルタ可能なフィールドやソート可能なフィールドもインデックスサイズに影響を及ぼします。

インデックスのサイズと検索パフォーマンスにはトレードオフがあることを理解しておくことが重要です。

検索インデックスにドキュメントを追加する方法

Azure AI Searchでは、検索インデックスに対してドキュメントを追加する方法としてPushモデルとPullモデルの二種類があります。

Pushモデル

Pushモデルは、Azure AI SearchのAzure SDKREST APIを利用して、検索インデックスに対してJSON形式のドキュメントを追加する方法です。

PushモデルはAzure SDK/HTTPリクエストをサポートする任意のプログラミング言語やモジュールで検索インデックスへのドキュメントの追加処理を記述可能です。

Pushモデルを利用する場合のアーキテクチャの例

Pushモデルでは任意のプログラミング言語で処理の記述が行えるため、Azure AI Searchへのドキュメント追加方法について様々なアプローチを取ることができます。

以下のアーキテクチャイメージは、ファイルからテキストデータの抽出、Azuree AI Searchへのドキュメント追加をAzure App Serviceのアプリケーションで行う場合になります。

Azure OpenAI または Azure Cognitive Search を使用してエンタープライズ ナレッジ ベースを検索してクエリを実行する - Azure Architecture Center | Microsoft Learn

その他にも、Azure Functionsを用いてイベント駆動型のアーキテクチャでAzure AI Searchにドキュメント追加を行う場合のアーキテクチャイメージもあります。

Azure でドキュメントの分類を自動化する - Azure Architecture Center | Microsoft Learn

Pullモデル

Pullモデルは、Azure AI Searchがサポートしているデータソースをクロールし、自動で検索インデックスにドキュメントを追加する方法です。
Pullモデルの仕組みや機能を理解するために「データソース」「インデクサー」「スキル」「スキルセット」「エンリッチメント」「ナレッジストア」という用語・概念を整理しておきます。

用語 概要
データソース インデクサーのデータ抽出対象となるクラウド上のデータソース。Azure Blob StorageやAzure Cosmos DBなどがサポートされる。
インデクサー インデクサーはデータソースのデータを検索インデックスのスキーマ構造にマッピングする処理を行うコンポーネント。一般的な検索システムの「クローラー」のような処理を担う。
スキル インデクサーでコンテンツを検索インデックスに投入する際に、コンテンツを変換する単一の操作を提供するもの。
スキルセット スキルセットは特定のインデクサーで利用するスキルの集合。少なくとも1つのスキルから構成され、最大で30のスキルを含む。
エンリッチメント インデクサーの拡張機能で、画像などのテキスト情報を持たないデータを検索可能な構造に変換するもの。
ナレッジストア エンリッチメントされたコンテンツを保存するストレージ。Azure Blob StorageやAzure Table Storageを利用できる。

Pullモデルを使って検索インデックスにドキュメントを追加する場合は、インデクサーを使って検索インデックスに対してドキュメントを追加します。
インデクサーの作成手順としてはデータソース、スキルセットを作成し、それらを利用するインデクサーを作成するという流れになります。

Pullモデルを利用する場合のアーキテクチャの例

Pullモデルでは、Azure AI Searchの検索インデックスへのドキュメントの追加処理はAzure AI Searchに閉じた形になります。

以下のアーキテクチャイメージは、ストレージアカウントをデータソースに登録して自動で検索インデックスにドキュメントを追加する場合になります。
スキルセットを利用することで、抽出したテキストデータをTranslatorで他言語に翻訳することやAzure AI Document Intelligenceと組み合わせたドキュメント分析なども可能です。

Azure OpenAI または Azure Cognitive Search を使用してエンタープライズ ナレッジ ベースを検索してクエリを実行する - Azure Architecture Center | Microsoft Learn

Pullモデルのインデクサーではマッピング対象の検索インデックスを1つだけ指定する必要がありますが、複数のデータソースを1つの検索インデックスにマッピングできます。
以下のアーキテクチャイメージは別のデータソースとして登録されたBlobストレージとTableストレージに対し、個別のインデクサーを定義し、1つの検索インデックスにマッピングをしている例です。

Azure Cognitive Search を使用してファイル コンテンツとメタデータのインデックスを作成する - Azure Architecture Center | Microsoft Learn

インデクサーの計算リソース

Pullモデルでのインデクサーを使ってのドキュメントの追加処理はAzure AI Search側で行われます。
この実行環境には「プライベート実行環境」と「マルチテナント実行環境」の2つが存在します。

環境 概要
プライベート実行環境 リソース固有の環境。ここで実行されるインデクサージョブは最大24時間実行可能。プライベート環境で実行可能なインデクサージョブの数は検索ユニットで1つ。プライベートエンドポイント経由で他のリソースにアクセスする必要があるインデクサージョブはこの環境で実行する必要がある。
マルチテナント実行環境 マネージドな環境。ここで実行されるインデクサージョブは最大2時間実行可能。実行できるインデクサージョブの数は不確定。

インデクサーがネットワークで保護されたリソースにアクセスする場合は、この実行環境の違いについて把握しておく必要があります。特にプライベートエンドポイントでリソースにアクセスする必要がある場合はプライベート実行環境でインデクサーが実行されるように追加で構成をする必要があります。

Azure AI Search側からのAzure OpenAI Serviceとの連携

Azure AI SearchからAzure OpenAI Serviceリソースを連携させ、Blobストレージのコンテンツをベクトル化し、検索インデックスにドキュメントに追加するインデクサーを構成できます。

この操作はAzureポータルの「データのインポートとベクター化」から実施できます。

「データへの接続」ではBlobストレージの情報を入力します。

「データのベクター化と強化」ではAzure OpenAI Serviceリソースの情報を入力します。

画面キャプチャ上では「毎時間」となっていますが、インデックス作成のスケジュールを「即時」にすると操作終了後にインデクサーが実行できます。

この操作の裏では、Pullモデルの節で紹介したインデクサーの作成と同じことが行われておりデータソース、スキルセット、インデクサーのリソースがAzure AI Search内に作成されています。

利用されるスキル

作成されるインデクサーのスキルセットには「テキスト分割スキル」と「Azure OpenAI Embedding スキル(プレビュー)」の2つのスキルが含まれています。

テキスト分割スキルはBlobストレージのコンテンツから抽出したテキストデータをいわゆる「チャンク」という単位に分割するスキルになります。

learn.microsoft.com

テキスト分割スキルの定義のJSON情報(一部抜粋)を確認すると、チャンクに含まれる最大文字列長は2,000、オーバラップサイズは100で構成されているようです。

 {
      "@odata.type": "#Microsoft.Skills.Text.SplitSkill",
      "name": "#1",
      "description": null,
      "context": "/document",
      "defaultLanguageCode": "en",
      "textSplitMode": "pages",
      "maximumPageLength": 2000,
      "pageOverlapLength": 100,
      "maximumPagesToTake": 1
}

Azure OpenAI Embeddingスキルはチャンクに区切られた各テキストをEmbeddingモデルの「text-embedding-ada-002」を使ってベクトル化するスキルになります。

learn.microsoft.com

Azure OpenAI Embeddingスキルの定義のJSON情報(一部抜粋)を確認すると「emebedding-ada-002」モデルが利用されていることがわかります。

{
      "@odata.type": "#Microsoft.Skills.Text.AzureOpenAIEmbeddingSkill",
      "name": "#2",
      "description": "Azure OpenAI Embedding Skill",
      "context": "/document/pages/*",
      "resourceUri": "https://*****.openai.azure.com",
      "apiKey": "*****",
      "deploymentId": "emebedding-ada-002",
      "authIdentity": null
}

マッピングされる検索インデックスのスキーマ

マッピング対象の検索インデックスは選択できず自動で作成されます。そのためマッピング対象のスキーマには制限があります。
以下は作成される検索インデックスのスキーマになります。

インデックスプロジェクション

この操作で構築されるインデクサーは内部でインデックスプロダクションという機能を利用しています(2024年1月時点でプレビュー機能)。
これは通常のインデクサーが1ファイルを1つのドキュメントにマッピングするのに対し、1ファイルを複数のドキュメントにマッピングさせる機能です。
これはチャンク分割、ベクトル化の処理で1対多のマッピングが必要なためです。

learn.microsoft.com

Azure OpenAI Service側からのAzure AI Searchとの連携(Add your data)

続いてAzure OpenAI Service側からAzure AI Searchと連携する操作を試してみます。この機能は「Add your data」とも呼ばれています。

Azure AI Studioのチャットプレイグラウンドの「データの追加(プレビュー)」の「データソースの追加」より操作可能です。

「データの追加」では対象のデータソース、データを保存するAzure AI Searchリソース、検索インデックス名の指定などをします。ここでAzure AI Searchの「インデクサー」という単語が登場しています。
またベクトル検索を有効にするかのチェックボックスが存在します。

「データ管理」ではAzure AI Searchで行う検索の種類、チャンクサイズを指定します。

構成が完了するとAzure OpenAI ServiceのチャットプレイグラウンドでAzure AI Searchをデータソースとして回答生成させることができます。

Azure AI Search側に作成されるインデクサー

Azure OpenAI Service側から連携した場合も、Azure AI Searchのインデクサーが利用されます。
そのためAzure AI Searchにデータソース、スキルセット、インデクサーが作成されます。 インデクサーの処理で重要となる、スキルセット内で利用されるスキルについて紹介します。

利用されるスキル

Azure OpenAI Serviceから連携した場合に作成されるインデクサーでは「カスタム Web API」スキルが利用されます。

learn.microsoft.com

以下はスキルの定義のJSON情報(一部抜粋、パラメータマスク済み)です。

    {
      "@odata.type": "#Microsoft.Skills.Custom.WebApiSkill",
      "name": "{スキルセット名}",
      "description": null,
      "context": "/document/content",
      "uri": "https://******.openai.azure.com/openai/preprocessing-jobs?api-version=2023-03-31-preview",
      "httpMethod": "POST",
      "timeout": "PT3M",
      "batchSize": 10,
      "degreeOfParallelism": 10,
      "inputs": [],
      "outputs": [],
      "httpHeaders": {
        "original-request-id": "{検索インデックス名}",
        "num-tokens": "1024",
        "embedding-deployment-name": "{ベクトル化に利用するモデルのデプロイ名}",
        "api-key": "{Azure OpenAI ServiceのAPIキー}",
        "connection-string": "{Blobストレージへの接続文字列}",
        "container-name": "{Blobストレージのコンテナ名}"
      }
    }

カスタムWeb APIはAzure OpenAI Serviceのpreprocessing-jobsというエンドポイントを呼び出しており、チャンク分割のトークン数はHTTPヘッダーのnum-tokensで送信していることがわかります。
なおpreprocessing-jobsの詳細な情報は公開されている情報を見つけることができませんでした。

ここまででわかったことは、Azure OpenAI Service側からAzure AI Searchと連携する場合とAzure AI Search側からAuzre OpenAI Serviceと連携する場合で似たパラメータを設定しますが内部で利用されるスキルなどは異なっているということです。

マッピングされる検索インデックスのスキーマ

マッピングされる検索インデックスのスキーマは以下のようになっています。

検索インデックスのスキーマもAzure OpenAI Service側からAzure AI Searchと連携する場合とAzure AI Search側からAuzre OpenAI Serviceと連携する場合で異なります。
こちらの検索インデックスのスキーマも自動で作成されるため、マッピング対象のスキーマには制限があります。

まとめ

本記事ではAzure AI Serchを理解するために基本的な用語や概念の説明からAzure OpenAI Serviceとの連携部分まで紹介しました。
Azure OpenAI Serviceとの連携では、Azure AI Serch側から連携する場合とAzure OpenAI Service側から連携する場合のどちらもインデクサーを利用しますが、その処理内容に違いがあることを取り上げました。

処理の違いについては、時系列的にAzure OpenAI Service側から「Add your data」によるAzure AI Serchとの連携機能の方が先に公開されていた影響が大きいかと個人的には考えています。
なおどちらの場合も、現在(2024年2月)はプレビュー版の機能が使われており、プロダクションへの適用は難しい状態です。
しかしながらスキルなどのパラメータは十分カスタマイズ可能なものが用意されているので、今後、SDKなどからの操作がサポートされると安定して使えそうなビジョンがあります。

本記事の内容が役に立てれば幸いです。

執筆:@yamada.y、レビュー:@wakamoto.ryosuke
Shodoで執筆されました