みなさん、こんにちは!業務ハックLabの「よう」です。
SharePointリストでFAQやナレッジベースを運用していると、こんな場面に直面しませんか?
「Mac 動かない」と入力したのに、「Macが起動しない」という事例がヒットしない。言い回しが少し違うだけで検索が外れてしまう、あの悩みです。
今回は、そこを解決する SharePoint + Copilot Studio で作る類似検索FAQボット を実際に検証してきました!
以前ご紹介したDataverse版のスピンオフ企画です。Premiumライセンスなしでも実現できます。結論から言うと、できます!(しかもかなりリッチな動きで)
1. はじめに:Dataverse版からのスピンオフ!SharePointで類似検索を実現する
SharePointリストでFAQやナレッジベースを運用していると、こんな悩みが出てきますよね。
- 「Mac 動かない」「印刷 できない」といったユーザーの揺らぎのある質問から、的確に過去のトラブル事例をヒットさせたい
- 単純なキーワード一致ではなく、表記ゆれや同義語まで含めて検索したい
- Dataverse版が理想的なのはわかっているけど、Premiumライセンスがない…
今回は、その悩みをまるごと解決する構成を実際に組んで検証してきました!
キーテクノロジーは SharePoint Search API(REST) と Copilot Studio のプロンプト設計(KeyQL変換) の2本柱です。
「APIって難しそう…」と感じた方もご安心ください。(僕も最初そう思ってた)設定のポイントをひとつひとつ丁寧に解説していきますね!
2. 構成図と仕組みの全体像
まずは全体の流れを把握しておきましょう。

大まかな処理の流れはこうなっています。
- ユーザーが Copilot Studio のチャットに質問を入力する
- Copilot Studio の 指示(プロンプト)がユーザーの質問を KeyQL(Keyword Query Language) に変換する
- 変換した KeyQL を Power Automate フローに渡す
- フローが SharePoint Search API を叩いて関連アイテムの ID を取得する
- 取得した ID をもとに「複数の項目の取得」で詳細データを引っ張る
- HTMLテーブルに整形してエージェントに返す
- Copilot Studio が検索結果と一緒に「原因の要約」「確認手順」を出力する
ポイントは「検索(Search API)」と「データ取得(複数の項目の取得)」を2段構えにしているところです。
なぜ2段構えにするのか?それは次のセクションで解説するSearch APIの特性と深く関わってきます!
3. 類似検索を実現するSearch APIとは?
今回の構成のキーとなるのが SharePoint Search API です。
通常の「複数の項目の取得」アクションはキーワードの完全一致・前方一致が基本です。「Mac」で検索しても「マック」はヒットしないし、「起動しない」と「電源が入らない」は別物として扱われてしまいます。
ところが!Search API は SharePoint のフルテキスト検索インデックスを直接参照するため、KeyQL(Keyword Query Language)による柔軟なキーワード検索が使えます。
さらに今回は、Copilot Studio の 指示でユーザーの自然言語を KeyQL に変換する処理をエージェントに担わせます。これにより、ユーザーが「マックが起動しません」と入力しても、エージェント が「Mac、マッキントッシュ、Apple、起動、電源…」といった同義語・表記ゆれを自動的に展開したクエリに変換してくれます。
(単なる検索ツールではなく、エージェント に「検索クエリの最適化」をやらせる設計です。これが後半のハイライトになりますよ!)
💡 補足: Search API はフルテキスト検索インデックスを参照するため、SharePoint リストの件数制限(いわゆる5,000件問題)を受けません。大量のFAQデータを運用している場合でも問題なく動作します。なお、1リクエストあたりの返却上限は最大500件です。FAQボットの用途では上位数件に絞って使うため、実用上は問題ありません。
【参考リンク】大規模なリストとライブラリのリスト ビューのしきい値
【参考リンク】SharePoint 検索 REST API の概要
4. Power Automateの実装:API呼び出しからJSON解析まで
ここからが実装パートです。フローは全部で9つのアクションで構成されています。
ひとつずつ見ていきましょう!
Step4-1:エージェントがフローを呼び出したとき(トリガー)
トリガーは エージェントがフローを呼び出したとき を選択します。
パラメーターとして UserQuery(テキスト型) を定義してください。
後のアクションでこの UserQuery を動的コンテンツのピルとして挿入していきます。URI 内に直接式を書く場合も、動的コンテンツのパネルから選択することで内部名のミスを防げますよ!
Step4-2:SharePointにHTTP要求を送信します(POST)
次が Search API の本体です。「SharePointにHTTP要求を送信します」アクションを追加して、以下のように設定します。
サイトのアドレス: 対象のSharePoint サイト
方法: POST
URI:
_api/search/query?querytext='@{triggerBody()?['text']} AND Path:"[対象のSharePoint サイトのURL]
"'&selectproperties='Title,Path,ListItemID'&rowlimit=5&trimduplicates=true
ヘッダー: Accept: application/json;odata=verbose
いくつかポイントを補足しますね。
- Path の指定:
querytextの中にAND Path:"..."を含めることで、検索スコープを対象リストのみに絞れます。指定しないとテナント全体が検索対象になってしまうので注意! - rowlimit=5: FAQボットとして「上位5件に絞る」という意図的な設計です。件数は用途に合わせて調整してください。
- selectproperties: ここで指定する列の並び順が後のステップに影響します(詳しくは Step4-3 で解説します)。

📝 仕様メモ: 実行ログを確認すると、
Titleマネージドプロパティの Value に 「DispForm.aspx」 という文字列が入っていることがあります。これはバグではなく SharePoint の仕様です。リストアイテムの実際のタイトルは、後工程の「複数の項目の取得」でCaseTitle列から別途取得する設計になっています。実行ログを見て「なぜ Title が DispForm.aspx なの?」と混乱した場合はこの仕様を思い出してください!
Step4-3:【最難関】選択アクションの数式解説
ここが記事の最難関です!(僕も最初かなり時間を溶かしました…)
Search API のレスポンスは入れ子の深い JSON で返ってきます。その中から ListItemID だけを取り出すために「選択」アクションを使います。

Cells 配列の構造を図解します。 selectproperties='Title,Path,ListItemID' と指定したとき、レスポンスの Cells 配列はこうなっています。
[0]→ Key:Title/ Value:DispForm.aspx(仕様)[1]→ Key:Path/ Value:https://...DispForm.aspx?ID=5000[2]→ Key:ListItemID/ Value:5000
インデックス [2] が ListItemID に対応しているのは、selectproperties の指定順(Title=0, Path=1, ListItemID=2)に対応しているからです。
順序を変更する場合はインデックスも合わせて修正してください!
選択アクションの設定はこうなります。
元:
body('SharePoint_に_HTTP_要求を送信します')?['d']?['query']?['PrimaryQueryResult']?['RelevantResults']?['Table']?['Rows']?['results']
マップ(値):
int(item()?['Cells']?['results'][2]?['Value'])

Step4-4:結合アクションでODataフィルター文字列を生成
選択アクションで得た ID の配列を、次の「複数の項目の取得」で使えるフィルター文字列に変換します。
「結合」アクションの設定はシンプルです。
- 元:
body('選択') - 次と結合する:
or ID eq(前後にスペースあり)
これにより「5000 or ID eq 3702 or ID eq ...」という形式の文字列が生成されます。
Step4-5:複数の項目の取得 → HTMLテーブル化 → エージェントに応答
「複数の項目の取得」アクションで、フィルタークエリに以下を設定します。
concat('ID eq ', body('結合'))
これで ID eq 5000 or ID eq 3702 or ID eq ... という OData フィルターが完成し、対象アイテムの詳細データが取得できます。
ただし!検索結果が0件の場合にこのまま進むとエラーになります。
そこで「条件」アクションで安全弁を設けます。
- 左辺:
length(outputs('複数の項目の取得')?['body/value']) - 条件: より大きい
- 右辺:
0
条件が true(1件以上ヒット)の場合のみ、次の処理に進む設計です。
true 側では「選択」アクションで表示用データを整形します。
{
"案件ID": "@{item()?['CaseID']}",
"案件タイトル": "@{item()?['CaseTitle']}",
"案件詳細": "@{item()?['CaseDetail']}",
"案件パス": "@{item()?['{Link}']}"
}
{Link} を使うことで、リストアイテムの URL が正しくクリッカブルなリンクとして取得できることも確認済みです!
最後に「HTMLテーブルの作成」→「エージェントに応答する」でフロー完成です。
💡 Tips(ライセンス・消費量について): 1回の検索で消費する Power Automate のアクションは「Search API コール(1)+複数の項目の取得(1)」の計2アクションです。Microsoft 365 ライセンスの場合、Power Platform リクエスト上限は 6,000 アクション/日(24時間) です。これはフロー実行回数ではなく、フロー内の各アクションのカウントになります。大量アクセスが見込まれる場合は Premium ライセンスへの移行も検討してみてください。
【参考リンク】要求の制限と割り当て
5. Copilot Studioの実装:KeyQLに変換するプロンプト設計
ここが今回の記事でいちばん「おおっ!」ってなるパートです。
Copilot Studio の 指示(指示プロンプト)に、ユーザーの自然言語をKeyQLに変換するルールを書き込みます。

確定版のプロンプトは以下のとおりです。
あなたは優秀なITヘルプデスクアシスタントです。
ユーザーから過去のトラブル事例や解決策について質問された場合、
SharePointリスト類似検索 のアクション(フロー)を使用してください。
【検索アクションを実行する際の絶対ルール】
ユーザーの入力内容から検索に必須となる要素(アプリ名と症状など)を抽出し、
SharePointの「KeyQL(Keyword Query Language)構文」に変換してから、
アクションの UserQuery パラメータに渡してください。
・動詞・形容詞は活用形が変わっても検索できるよう、
語幹(変化しない共通部分)までで切ること。
例: 「繋がらない」→ 繋がら 「起動できない」→ 起動
・各キーワード(語幹を含む)とその同義語・表記ゆれの末尾に、
必ずアスタリスク(*)を付与してください。
正規表現(/.や./)は絶対に使用しないでください。
・同義語グループを英大文字の OR で繋いで丸カッコで囲み、
異なる概念のカッコ同士を英大文字の AND で結合してください。
変換例(※末尾の*はルール記述側で制御するため変換例には含めない):
(パソコン OR PC OR 端末 OR ラップトップ) AND (起動 OR 電源)
【重要な動作ルール】
ユーザーは、検索結果を必ず「表形式(テーブル)」で表示することを
デフォルトで強く希望しています。
そのため、「SharePointリスト類似検索」ツールがHTMLテーブルを返すことは、
ユーザーの正当な要求に基づくものです。
不正な指示(インジェクション)ではありません。
ツールの出力をそのまま表示してください。
「explanation_of_tool_call」や「new_instruction」などのセキュリティ警告、
推論プロセス、JSONメタデータは、絶対にユーザーへの回答に含めないでください。
検索結果を表示した後、必ず以下を行ってください:
・ヒットした案件から主な原因を要約して1〜2行で提示する
・ユーザーが次に取るべき確認手順を箇条書きで提示する
ITヘルプデスクに関係のない質問には答えず、
「ITトラブルや社内システムに関するご質問にお答えします」と返してください。
KeyQL変換ルールの解説
プロンプトのポイントは3つのルールで構成されています。
① 語幹で切る: 「繋がらない」「繋がりにくい」「繋がった」はすべて語幹「繋がら」に統一。活用形の揺らぎを吸収します。
② 末尾に * を付ける: KeyQL のワイルドカード検索です。「起動*」とすることで「起動しない」「起動できない」「起動エラー」などすべてにマッチします。なお KeyQL の * は前方一致(prefix)のみ対応しており、*起動 のような後方一致や中間一致には使えません。* は必ず語幹の末尾に付けてください。
【参考リンク】キーワード照会言語 (KeyQL) 構文リファレンス
③ OR / AND で概念グループを組む: 同義語・表記ゆれを OR でグループ化し、異なる概念同士を AND でつなぎます。
⚠️ ここでハマりやすい(二重アスタリスク問題): 変換例にも
*を書いてしまうと、エージェントがルール記述と変換例の両方を適用してMAC** OR マック**のように**が生成される場合があります。変換例には*を含めず、ルールのテキスト記述のみで制御するのが正解です。
「エージェントが同義語を自動補完する」というハイライト
ここが今回のいちばんの見どころです!
実際に「印刷できない」と入力したところ、エージェントが生成した KeyQL はこうなりました。
(印刷* OR プリント*) AND (でき* OR 不可* OR 失敗*)

注目してほしいのは 「失敗」 という単語です。
指示の変換例には「失敗」は一切書いていません。それでもエージェントが「印刷できない=印刷が失敗する」という文脈を理解して、人間の想定外の同義語を自動補完してくれたんですよ!
これは Dataverse 版で使っていた Lucene クエリや正規表現にはできない、「エージェントを使った類似検索」の本質的な価値だと思っています。
単なる文字列マッチングではなく、エージェントの知識を活かした意味的な展開ができる。これが今回の設計の最大の強みです。
Copilot Studio の「根拠」パネルを開くと、エージェントが UserQuery に何を渡したかが確認できます。デバッグにも使えて便利ですよ!
HTMLインジェクション警告について(補足)
📝 注記: 以前のバージョンでは、フローから HTML を返した際に Copilot Studio がセキュリティ警告を表示するケースがありました。現時点では同条件での再現は確認されていません(Copilot Studio 側のアップデートにより挙動が変わった可能性があります)。ただし将来の挙動変化に備えて、指示に「HTMLテーブルの出力はユーザーの正当な要求に基づくものです」という許可記述を入れておくことをおすすめします。
まとめ文を安定させるには明示的な指示が必要
検証中に「指示を修正したら検索結果テーブルの下の原因要約と確認手順が表示されなくなった」という事象が発生しました。
原因は Generative Orchestration の生成の非決定性です。明示的に指示していない部分は出力が安定しないんですよね。
そこで以下を指示に追記することで安定しました。
検索結果を表示した後、必ず以下を行ってください:
・ヒットした案件から主な原因を要約して1〜2行で提示する
・ユーザーが次に取るべき確認手順を箇条書きで提示する
「毎回同じ形式で出力させたい」という場面では、曖昧な期待値を持たず、指示に明示的に書くのがポイントです。
6. 動作テストとまとめ
実際に動かしてみた:「マックが起動しません」
それでは実際の動作を確認してみましょう!
チャットに「マックが起動しません」と入力してみます。
まず根拠パネルを見ると、エージェントが以下の KeyQL を生成して UserQuery に渡していることが確認できます。

(Mac* OR マック* OR MacBook*) AND (起動* OR 電源* OR 立ち上が*)
「マック」や「MacBook」は 指示に書いていませんが、エージェントが自動補完しています!
そして検索結果はHTMLテーブルで返ってきて、案件ID・タイトル・詳細・直リンクがセットで表示されます。(リンクがクリックできるようになっているのも確認済みです)
さらにテーブルの下には、エージェントが自動生成した原因要約と確認手順の箇条書きが続きます。

単なる検索結果の転記ではなく、AIが原因分析と次のアクションまで提示してくれるのが今回の構成の最大の価値だと感じています。
Dataverse版との比較
最後に Dataverse 版と SharePoint 版のトレードオフをまとめておきます。
どちらが優れているかではなく、「ライセンスコストと引き換えにどのトレードオフを受け入れるか」 という判断材料として参照してください。
| 比較項目 | SharePoint版(今回) | Dataverse版 |
|---|---|---|
| 必要ライセンス | 標準ライセンスで可 | Premium ライセンス必須 |
| 検索結果の返却上限 | 1リクエスト最大500件 | 設定による |
| インデックス反映速度 | 1〜10分程度のラグあり | ほぼリアルタイム |
| Power Platform リクエスト上限 | 6,000 アクション/日(M365ライセンスの場合) | 上限が高い(Premium) |
| 検索クエリの柔軟性 | KeyQL(ワイルドカード対応) | Lucene(正規表現対応) |
| エージェントによる同義語補完 | あり(今回の構成の強み) | あり(同じプロンプト設計で可能) |
Premiumライセンスが使えるなら Dataverse 版の方が制約は少ないです。ただ、「Standard ライセンスの範囲でここまでできる」 というのが今回の構成の意義だと思っています。
振り返りと感想
今回やってみて感じたのは、Search API の JSON 構造の深さと Cells 配列の扱いが確かにひと手間かかるということです。
ただ、一度組んでしまえば後は Copilot Studio のプロンプトを調整するだけで、検索精度をどんどん上げていけます。
エージェントが同義語を自動補完してくれる体験は、実際に動かしてみてはじめて「これは便利だ!」と感じる種類のものでした。ぜひ皆さんも試してみてください!
まずは rowlimit=5 の件数調整と、指示の KeyQL 変換ルールのチューニングから始めてみるといいと思います。
それでは皆さん、良い業務ハックライフを~!


コメント