はじめに
先日、社内の資料を整理していたら、2年前に作ったLogic Appsのコードが出てきました。
中身を読み返して思ったのは、「我ながら、この時は狂っていたな」ということです。
当時、私はとある会社でヘルプデスクを担当していました。プログラミングは一切できません。PythonもJavaScriptも書けません。それなのに、Azure Logic Appsを使って以下の2つのTeamsボットを作り上げていました。
- 社内ナレッジを検索して回答するRAGチャットボット(Azure OpenAI + AI Search + Cosmos DB)
- 会話と画像生成を自動で切り替えるAIボット(Azure OpenAI + DALL-E 3 + Cosmos DB)
- さらに、RAGボットのナレッジを自動で最新に保つデータパイプライン(SharePoint → Blob Storage自動同期、Logic Apps 4本)
結論から言うと、これらのボットは結局ほとんど使われませんでした。
それでもこの記事を書いているのは、2年前の自分への供養と、「プログラミングができなくても、ここまでやれるんだ」という記録を残しておきたかったからです。
同じように「コードは書けないけど、AIで何か作ってみたい」と思っている方に、少しでも参考になれば嬉しいです。
目次
- そもそもなぜ作ったのか
- ボット①:社内ナレッジを検索して回答するRAGボット
- 裏方:ナレッジを自動で最新に保つデータパイプライン
- ボット②:会話+画像生成を自動で切り替えるボット
- 2026年の今、同じものを作り直すなら
- おわりに
そもそもなぜ作ったのか
きっかけは上役の一言でした。
「AIを何とか社内で使おう」
2023年当時、ChatGPTが世の中を騒がせていて、うちの会社でも「何かやらないと」という空気がありました。ヘルプデスク担当だった私に白羽の矢が立ち、「Teamsで使えるAIボットを作れないか」というざっくりした指示が降ってきました。
ヘルプデスクの仕事では、毎日のように同じ質問が飛んできます。「○○プランの月額はいくら?」「工事の手順を教えて」「解約の違約金はどうなる?」――答えは社内のマニュアルに書いてあるのですが、資料の量が膨大で、探すのに時間がかかる。これをAIに任せられたら、と考えました。
問題は、私にプログラミング経験がないことです。PythonでRAGを組む記事は山ほどありましたが、自分には無理。そこで選んだのがAzure Logic Appsでした。
ノーコード/ローコードのツールなので、GUIでフローを組めます。ただし逆に言えば、コードで書けば数行で済む処理を、JSONの解析や変数操作を何段階も重ねて実現しなければなりません。普通に考えれば「素直にPythonを覚えた方が早い」かもしれません。
でも、制限があるからこそ突き抜けられた面もあったと思います。「Logic Appsでやれるところまでやる」と決めたことで、迷わず一気に作り切ることができました。
この記事のポイント
プログラミング未経験 → Logic Apps(ノーコード)を選択 → 制限があるからこそ迷わず突き進めた。コードが書けないことは、必ずしもハンデではありませんでした。
ボット①:社内ナレッジを検索して回答するRAGボット
全体像
Teamsのチャネルで「aoaiさん」とメンションして質問すると、社内のナレッジベース(PDFやWordの資料)を検索し、その内容をもとにAIが回答してくれるボットです。
いわゆるRAG(Retrieval-Augmented Generation)というアーキテクチャで、「AIが自分の知識だけで答える」のではなく、「まず社内資料を検索してから、その内容に基づいて答える」という仕組みです。
使ったサービス
| サービス | 役割 |
|---|---|
| Logic Apps | 全体のオーケストレーション(フローの制御) |
| Azure OpenAI(GPT-3.5) | ユーザーの質問からキーワードを抽出 |
| Azure OpenAI(text-embedding-ada-002) | キーワードをベクトル化 |
| Azure AI Search | ベクトル+テキストのハイブリッド検索 |
| Azure OpenAI(GPT-3.5 別デプロイ) | 検索結果を200文字に要約 |
| Azure OpenAI(GPT-4) | 要約+会話履歴をもとに最終回答を生成 |
| Azure Cosmos DB | 会話履歴とナレッジ要約を永続化 |
| Azure Functions | テキストのクレンジング(記号除去など) |
| SharePoint Online | ナレッジ元ファイルの置き場(後述のデータパイプラインで自動同期) |
| Azure Blob Storage | AI Searchのデータソース |
| Microsoft Teams | ユーザーとの入出力 |
……我ながら、よくこれだけのサービスを繋げたなと思います。今振り返ると狂気を感じます。ちなみにこのテーブルはRAGボット本体の話だけで、ナレッジを最新に保つためのデータパイプライン(Logic Apps 4本)は別にあります。そちらは後ほど解説します。
処理の流れ
こだわった設計ポイント
モデルの使い分け
全部GPT-4で処理すれば楽なのですが、コストとレスポンス速度を考えて、キーワード抽出と要約にはGPT-3.5を使い、最終回答のみGPT-4を使う構成にしました。
前回と同じトピックかどうかの判定
ナレッジのCosmos DBには、前回検索したキーワードも保存しています。今回のキーワードと前回のキーワードをAOAIに送って「この2つは同じトピックですか?」と聞き、「いいえ」と判断された場合のみ、過去のナレッジ要約も会話に含める設計にしました。これにより、話題が変わったときに無関係なナレッジが混入するのを防いでいます。
ナレッジの件数制限
Cosmos DBに保存されたナレッジ要約が15件を超えた場合、古いものを切り捨てて直近15件のみを使います。トークンの上限を超えてエラーになるのを防ぐためです。
振り返り:改善したい点
セキュリティ上の問題: Azure FunctionのURLにAPIキーが直接書かれていました。Logic Appsのパラメータ(SecureString)に切り出すか、Key Vaultを使うべきでした。エンドポイントURLやリソース名がハードコードされている箇所も同様です。
- 処理ステップが多すぎる。 2026年現在では、Azure OpenAIの「On Your Data」機能(現在はFoundry IQに移行)を使えば、②〜⑤の処理がAPI1回で済みます。当時はこの機能の精度が低かったので手動で組みましたが、今ならもっとシンプルにできます。
裏方:ナレッジを自動で最新に保つデータパイプライン
RAGボットだけでは完成しない
RAGボットの話をすると、多くの人は「AIがナレッジを検索して回答する」部分に注目します。でも実際に運用で一番大事なのは、検索対象のナレッジをどうやって最新に保つかという裏方の仕組みです。
マニュアルが更新されたのに検索結果が古いまま、ファイル名が変わったのにリンクが切れている――こうなるとボットへの信頼は一瞬で失われます。
そこで、RAGボット本体とは別に、SharePointのドキュメントをAzure Blob Storageに自動同期するデータパイプラインをLogic Appsで4本作りました。
仕組み
ヘルプデスクのオペレーターが普段使っているのはSharePointです。ここにマニュアルやFAQを置けば、あとは自動的にRAGボットの検索対象に反映される、という状態を目指しました。
SharePointでファイルが作成・編集・削除されると、それぞれ対応するLogic Appsが起動し、Blob Storageに同じ操作を反映します。AI SearchはBlob Storageをデータソースとしてインデックスを構築しているので、結果的にRAGボットの検索対象が自動的に最新化されます。
4本のフローの役割
作成フロー: SharePointに新しいファイルが追加されると(1分間隔で監視)、ファイルの内容を取得してBlob Storageにアップロードします。
削除フロー: SharePointからファイルが消えると(10秒間隔で監視)、対応するBlobファイルも削除します。
編集フロー: ファイルが更新されると(10秒間隔で監視)、Blobを上書きします。ただし、ここに一つ厄介な問題がありました。**ファイル名の変更(rename)**です。SharePointではファイル名の変更も「編集」として検知されますが、Blob Storage上では旧ファイル名のBlobを削除して新ファイル名で再作成する必要があります。
仲介フロー(中央管理): 上の3つのフローから呼び出される司令塔です。HTTPトリガーで受け取ったリクエストの種類(create / update / delete / search)に応じて処理を振り分けます。具体的には、SharePointのフォルダパスに応じてBlobの保存先を決定し(「コール」「サポート」「運用管理」の3部門ごとにBlobコンテナが分かれている)、Cosmos DBにファイルのメタデータ(ID・タイトル・URL・パス)を保存・更新・削除します。
ファイル名変更の検知が一番苦労した
この4本の中で一番頭を使ったのが、ファイル名の変更を検知する部分です。
SharePointの「ファイルが編集されたとき」トリガーは、内容の編集もファイル名の変更も同じ「更新」イベントとして通知してきます。区別する手段がトリガー側にはありません。
そこで、仲介フローのCosmos DBに保存してある旧ファイル名と、今回受け取った新ファイル名を比較する方法を取りました。
- 一致 → 通常の内容更新(
judgment: "normal")→ Blobを上書き - 不一致 → ファイル名変更(
judgment: "rename")→ 旧Blobを削除してから新Blobを作成
仲介フローがHTTPレスポンスでjudgmentの値と旧ファイル名を返し、編集フロー側でそれを見て分岐する設計です。
設計のポイント: SharePointのトリガーでは「内容の編集」と「ファイル名の変更」を区別できません。そこでCosmos DBに保存した旧ファイル名との比較で検知するという方法を取りました。プラットフォームの制約を、別のサービス(DB)で補うパターンです。
もう一つの役割:RAGボットへのファイルURL提供
実は、仲介フローにはもう一つsearchというリクエストタイプがあります。これは、RAGボット本体の「参照ファイル」のリンクを生成するために使われていました。
RAGボットがTeamsに回答を返す際、「【参照ファイル:〇〇】」というリンクを付けていましたが、AI Searchの検索結果にはファイルタイトルしか含まれていません。ファイルのSharePoint URLを取得するために、RAGボットから仲介フローにsearchリクエストを送り、Cosmos DBからURLを引いていたのです。
こうして、RAGボット本体 → 仲介フロー → Cosmos DB → SharePoint URL という形で、検索結果にリンクを付けることを実現していました。
振り返り:改善したい点
セキュリティ上の問題: 仲介フローのコールバックURL(SASトークン付き)が、作成・編集・削除の3フロー全てにハードコードされています。このJSONを共有すると、仲介フローを誰でも呼び出せてしまいます。
- ポーリング間隔が攻めすぎている。 削除と編集が10秒間隔です。Logic Appsの実行回数はコストに直結するので、業務の性質を考えれば1〜5分間隔でも十分だったと思います。
- 部門ごとのBlob振り分けがSwitch文のハードコード。 部門が増えるたびに仲介フローを修正する必要があります。Cosmos DBやパラメータにマッピングテーブルを持たせれば、フローを変更せずに対応できました。
ボット②:会話+画像生成を自動で切り替えるボット
全体像
RAGボットを作り終えた時点で、Logic AppsとAzure OpenAIの扱いにはだいぶ慣れていました。
せっかくならもう一つ、今度は業務関係なく純粋に面白いものを作ってみたいと思いました。RAGボットが「業務のため」なら、こちらは完全に「趣味の範疇」です。
そこで作ったのが、同じく「aoaiさん」とメンションすると反応するボットですが、こちらは別のチャネルに配置しています。
特徴は、ユーザーの意図を自動判定して、普通の会話ならテキストで回答し、画像生成の依頼ならDALL-E 3で画像を生成して返すという点です。業務で画像生成する場面はほぼないだろうと分かっていましたが、「できるかどうか試してみたい」という好奇心が勝ちました。
設計の肝:Function Callingを「意図分類器」として使う
このボットで一番苦労したのが、「ユーザーが画像を作りたいのか、普通に会話したいのか」を判定する部分でした。
最初は正規表現で「描いて」「画像」「イラスト」などのキーワードを検出しようとしました。でも「猫の絵が見たいな」「風景を作ってほしい」「ロゴのアイデアある?」のように表現のバリエーションが多すぎて、キーワードマッチでは限界がありました。
そこで思いついたのが、Function Calling(tool_choice: auto)を意図分類に使う方法です。
{
"function": {
"name": "picture_print",
"description": "入力されたテキストが画像生成を希望する内容かどうかを判断し、希望する場合はその内容を正確に取得します。",
"parameters": {
"properties": {
"prompt": {
"description": "生成したい画像の説明文が入ります。必ず英語で返されます。",
"type": "string"
}
},
"required": ["prompt"]
}
},
"type": "function"
}
GPT-4にこの関数定義を渡してtool_choice: "auto"で呼び出すと、GPT-4自身が「この質問は画像生成の意図があるか?」を自動的に判断してくれます。
- 画像生成の意図がある →
finish_reason: "tool_calls"が返ってくる - 普通の会話 →
finish_reason: "stop"で通常の回答が返ってくる
あとはfinish_reasonの値で条件分岐するだけです。
実はこれ、本来のFunction Callingの使い方(外部APIを呼ぶ)とは違います。私は「関数を呼ぶ」のではなく、「GPT-4にユーザーの意図を分類してもらう」ためだけに使っています。後から知ったのですが、これは「ルーターパターン」と呼ばれる設計手法に近いそうです。
設計のポイント: Function Callingの本来の用途は「外部APIを呼ぶ」ことですが、tool_choice: "auto" + finish_reason の値で分岐させることで、意図分類器として活用できます。キーワードマッチでは対応しきれない自然言語の意図判定を、LLM自身に任せるアプローチです。
画像生成の2段階プロンプト
Function Callingで画像生成と判定された場合、もう一つ工夫があります。
Function Callingのargumentsから得られる英語プロンプトをそのままDALL-E 3に渡すのではなく、もう一度GPT-4に「DALL-E 3に最適な30〜50語の英語プロンプト」を生成してもらうというステップを挟んでいます。
ユーザー:「かっこいいロボットの絵を描いて」
↓
Function Calling → prompt: "a cool robot"(短すぎる)
↓
GPT-4でプロンプト最適化 → "A futuristic humanoid robot, metallic silver body, glowing blue eyes, standing in a neon-lit cyberpunk cityscape, dramatic lighting, detailed mechanical joints, cinematic composition"
↓
DALL-E 3 → 高品質な画像を生成
このひと手間で、生成される画像のクオリティがかなり上がりました。
会話履歴の管理
会話履歴はCosmos DBに保存していますが、AOAIに送る際は直近3往復(6件)に制限しています。全ての履歴を送るとトークンを大量に消費してコストが跳ね上がるためです。
ただし、Cosmos DBには全履歴を保存しているので、制限を変えたくなったときにも対応できます。
処理の流れ
振り返り:改善したい点
- DALL-E 3の画像URLには有効期限がある。 後からスレッドを見返すと画像が表示されません。Azure Blob Storageに保存してからTeamsに返すべきでした。
- DALL-E 3のエンドポイントがハードコードされている。 GPT-4側はパラメータ化できたのに、DALL-E 3側は忘れていました。
- 中間メッセージのタイミングが遅い。 「画像を生成しています…」のメッセージは、プロンプト生成完了後に出しています。Function Calling直後に出すべきでした。
2026年の今、同じものを作り直すなら
この2つのボットは2023年頃に作ったものです。あれから3年近く経ち、Azureのサービスは大きく変わりました。
RAGボットの場合
Azure OpenAIの「On Your Data」機能は非推奨となり、Foundry IQ + Foundry Agent Serviceが後継です。Foundry IQでは「アジェンティック検索」という仕組みが導入され、クエリの計画→複数ソースの検索→結果の合成をAIが自動で行います。Microsoftの評価では、従来のRAGと比較して応答の関連性が最大36%向上しているそうです。
Teamsへの展開もワンクリックで可能になっているため、Logic Appsで複雑なフローを組む必要はなくなっています。
データパイプラインについても、Foundry IQはSharePointを直接データソースとして接続できるため、Blob Storageへの同期フロー4本がまるごと不要になる可能性があります。あの4本のLogic Appsで一番苦労したファイル名変更の検知も、プラットフォーム側が吸収してくれるわけです。
画像生成ボットの場合
Function CallingにはStructured Outputs(strict: true)が追加されました。これを使えば、Function Callingの戻り値が必ず定義したJSONスキーマに従うことが保証されます。私のフローにあった多段階のJSONパース処理が大幅に簡略化できます。
また、最新のResponses APIではimage_generationがビルトインツールとして用意されています。つまり、「画像を生成するかどうか」の判定をAPI側が自動でやってくれるため、私が苦労して実装したルーターパターンが、標準機能で実現できるようになりました。
おわりに
冒頭にも書きましたが、このボットたちは結局ほとんど使われませんでした。
理由はいくつかあります。RAGボットは、ヘルプデスクの現場では「AIの回答を確認してからお客様に伝える」という二度手間が発生し、直接マニュアルを見た方が早いという声がありました。画像生成ボットに至っては、そもそも趣味で作ったものなので、業務で使われないのは最初から分かっていたことです。
遊びの方が良いものを生む: 皮肉なことに、技術的に一番面白い設計(Function Callingを意図分類器に使うルーターパターン)が生まれたのは、業務のためのRAGボットではなく、趣味で作った画像生成ボットの方でした。
それでも、この経験は無駄ではなかったと断言できます。
Logic Appsの制限の中でFunction CallingやRAGの仕組みをゼロから組み上げたことで、「AIがどうやってナレッジを検索しているのか」「Function Callingの正体はテキスト分類であること」を、コードを書ける人以上に深く理解できました。データパイプラインまで含めると、Logic Appsだけで合計7本のフローを作っていたことになります。制限があったからこそ、一つひとつの処理の意味を嫌というほど考える必要があり、それが結果的に力になりました。
技術は3年で大きく変わります。2023年に何十ステップもかけて組んだRAGの処理が、2026年にはFoundry IQのAPI1回で済みます。私が苦労して実装したFunction Callingのルーターパターンも、今ではResponses APIのビルトイン機能で実現できます。
でも、当時あの苦労をしたからこそ、新しい技術が「何を簡単にしてくれているのか」が分かる。遠回りだったけど、悪くない遠回りでした。
この記事が、2年前の自分への供養と、同じように「コードは書けないけど何か作ってみたい」と思っている誰かの背中を押すことになれば幸いです。
最後に、当時右も左も分からない中で参考にさせていただいた数々の技術記事・ブログの執筆者の皆さまに、この場を借りて感謝申し上げます。皆さんの記事がなければ、このボットたちは生まれていませんでした。
- Azure Logic Apps(合計7フロー)
- Azure OpenAI Service(GPT-3.5 Turbo / GPT-4 / text-embedding-ada-002 / DALL-E 3)
- Azure AI Search
- Azure Cosmos DB
- Azure Blob Storage
- Azure Functions
- SharePoint Online
- Microsoft Teams
※本記事の執筆にはAI(Claude)を活用しています。内容・設計・体験談は筆者自身のものです。