マルチテナント+マルチプロダクト SaaS への AI Agent の組み込み方
2025年6月18日、ファインディ株式会社が主催するイベント「AI Engineering Summit」が開催されました。本記事では、株式会社ナレッジワークでCTOを務める川中 真耶さんによるセッション「マルチテナント+マルチプロダクト SaaS への AI Agent の組み込み方」の内容をお届けします。
2025年は「AIエージェント元年」と言われており、AIエージェントを組み込んだ開発スタイルが浸透してきています。
しかし、マルチテナントSaaSへの組み込み手法については、まだ最適解が見つかっていません。イベントでは、マルチテナント+マルチプロダクト環境でSaaS開発をしているナレッジワークの事例を参考に、アーキテクチャ設計やテナント毎のカスタマイズ運用のポイントについて解説していただきました。
マルチテナント+マルチプロダクト SaaSゆえの課題
エンタープライズ向けセールスイネーブルメントAI「ナレッジワーク」
川中:株式会社ナレッジワークでCTOをしている川中と申します。
本日は、ナレッジワークの事例を交えて、マルチテナントSaaS環境でAIエージェントを導入・運用するための実践的手法についてお話しします。
はじめに、会社についてご紹介させてください。ナレッジワークは2020年に創業した会社で、営業担当者を支援するセールスイネーブルメントAI「ナレッジワーク」を開発・提供しています。
ナレッジワークは大手企業様に数千名規模でご利用いただいているのが特徴で、開発をする際にはそれゆえの課題とも向き合わなくてはいけません。
本日は、そういった背景も踏まえてお話しします。
会員限定コンテンツ無料登録してアーキテクチャを見る
川中:それでは本題に入ります。
現在はさまざまなAIエージェント開発フレームワークが生まれており、日本ではMastraが人気ですね。
PythonのフレームワークであるAgnoも一大勢力だと思います。GoogleからはAgent Development Kitというフレームワークも出ています。
AIエージェントを動かすだけであれば簡単にできる時代ですし、個人的にMastraやAgnoを触ってみた際は、10分ほどで動かすことができました。ただ、プロダクトとして中に組み込んで提供するには、まだ難しいところがあると思います。
会員限定コンテンツ無料登録してアーキテクチャを見る
川中:実際にナレッジワークでAIエージェントを組み込もうとした際には、認証、権限、開発プロセスやデプロイ戦略との整合などの課題が発生しました。
というのも、ナレッジワークはマルチテナント+マルチプロダクトであるため、ナレッジワークという箱の中に複数のプロダクトがあり、各プロダクトごとにサーバーを立てています。そのため「このデータを読み取っていいのか?」「このプロダクトのライセンスを持っているのか?」など、チェックすべき項目が多くなってしまいがちです。
また一つのサーバーをアップデートしたい場合には、AIサーバーと繋がっている部分のデプロイ戦略をどうするのかも考えなくてはいけません。
このような課題に対して、どういった対応をしていくのか。本日はその一例を示したいと思います。
ナレッジワークのシステムアーキテクチャ
川中:前提として、今回は一定の複雑さを持ったシステムかつ一定の複雑さを持った顧客ユースケースを想定しています。システムアーキテクチャの前提は以下の通りです。
①マルチナントアーキテクチャで1つのシステムに多数のテナントが相乗りしている
②一つのシステムに複数のプロダクトを抱えるマルチプロダクトで、それぞれでAIエージェントを動かしたい
③サービスごとにサーバーが分れていて、プロダクト間通信は認めない
④テナント単位で挙動を変更したい
④について、ナレッジワークの場合はクライアントによって業務フローが異なり、会社ごとにプロンプトをカスタマイズして最適化することが求められているため、このような前提としています。
ナレッジワークの実際のアーキテクチャは下図の通りです。
会員限定コンテンツ無料登録してアーキテクチャを見る
川中:4つのレイヤーがあり、バックエンドレイヤーには全てのプロダクトが利用できる共通基盤やプロダクト、プロダクト横断機能のサーバーがあります。そのほかに、サブシステムも複数あるといった形です。
これだと少し複雑ですので、本日は下図のようなものをイメージしていただければと思います。
会員限定コンテンツ無料登録してアーキテクチャを見る
川中:ナレッジワークでは同じレイヤー内での相互アクセスは基本的に禁止にしていて、左から右へのアクセスは可能ですが、縦のレイヤーではアクセスできないようにしています。
ちなみに、AIエージェントのフレームワークが整備されていない時代は、各プロダクトサーバーでOpenAIやVertex AI(Gemini)と通信していました。LLMもあまり賢くなかったため、step by stepでLLMを利用して、結果をプログラムで変換して次のLLMを呼び出す、といった形でした。プロンプトはバイナリに埋め込まれていて、テナント単位でのカスタマイズはできませんでした。
1~2年前はこれでも問題なかったのですが、AIが浸透してきて各プロダクトにAIエージェントが組み込まれ、顧客がAIを使うようになってくると、精度という観点で困ることが増えてきてしまいました。
そこで、より良い価値を提供していくために、AIエージェントフレームワークを組み込んで、プロンプトをカスタマイズできるようにしようと考えるようになったのです。
AIエージェントを組み込む前にぶつかった課題
マルチテナントに伴う二つの課題
川中:とはいえ、マルチテナントゆえの課題が二つありました。
まずは「テナントによって使って良いモデルが異なる」ということ。
エンタープライズでセキュリティ意識の高いクライアントから「データを国内に閉じて欲しい」「LLMのサーバーの中にデータを保存しないで欲しい」といった要望をいただくことは珍しくありません。とくに金融や保険、医療業界は要件が厳しいです。
ただ、データを国内に閉じようとすると、なかなか世知辛い問題があり……。
というのも、主要クラウドベンダーだと以下のモデルのみで、大手の最新モデルが日本リージョンで動いていないのです。
・Google Cloud:Gemini 1.5flash 32k tokenモデルのみ
・Azure Open AI:o4のみ
・AWS Bedrock:Claude Haiku 3.0 or Sonnet3.5
また日本から使えるといってもデータが日本に閉じているわけではなく、海外にデータが送られる可能性もあります。そうするとインシデント扱いになりかねませんし「グローバルリージョン」と書いてあるものは危険です。
ちなみに、Azure Open AIにはabuse monitaringという便利な機能があるのですが、ログに残ってしまうため使用をNGにしているクライアントも多いです。
もう一つの課題は「テナントごとにプロンプトをカスタマイズしたい」という要望があること。
ナレッジワークは業務に根付いたSaaSであり、最適な顧客体験を提供するためには、プロンプトをカスタマイズできるようにしておく必要があります。
全てをカスタマイズしたいわけではないものの、テナントによってカスタマイズしたい場所が異なるため、一定のカスタマイズ機能を提供しなくてはいけません。また、カスタマーサクセスやコンサルが並走してカスタマイズしていくため、彼らが設定できる程度の機能が求められます。
マルチプロダクトに伴う複数の課題
川中:マルチプロダクトの面でも課題がありました。
まずはライセンスや権限に対する課題です。ユーザーによって保持しているプロダクトライセンスが異なるため、該当ユーザーが使えるプロンプトだけを抽出する必要があります。
アーキテクチャにも課題がありました。連携が必要な機能はクロスプロダクトに置く必要があるのですが、AIの機能上どうしてもプロダクト連携が必要な部分があるのです。
開発プロセスに対する課題もあり、各プロダクトがAIを勝手に使うことによって、機能の不一致が起こってしまいます。
またプロダクト単位で開発をしていると、外界にMCPサーバーやA2Aのようなものを作りたくても「どのチームが作るの?」といった疑問が生まれがちです。ナレッジワークではプロダクトを横断して開発するクロスプロダクトチームがあるのですが、そういったチームがない場合は難しいのではないかと思います。
プロダクトとAIエージェントフレームワークのデプロイサイクルの不一致といった課題もあります。プロダクトのリリースとともに新しいエージェント定義を動かしたいのですが、AIエージェントフレームワークが別のプロダクトでも動いている場合、リリース可能なものと開発途中のものが混ざってしまいます。
課題解決に向けて考えた解決策(サマリー)
川中:これらの課題にどのように対応するかというと、マルチテナントに対しては、テナントによってどの言語モデルが使えるかを一律で定義して、勝手に言語モデルが使えないようにします。
さらに、同じエージェントでもテナントによってプロンプトの定義を変更できるように、同じ名前のデフォルトエージェントとカスタマイズエージェントを定義します。
マルチプロダクトについては、エージェント定義をプロダクト単位でできる構成にし、プロンプト定義やツール定義をプロダクト側から設定できるようにします。
ナレッジワークはprotobufでいろんなものを定義しているため、protobufでツールを作ったりエージェントを定義したりできるようにしています。
エージェントの実行を抽象化しておいて、エージェントのprotobufで定義することで、プロダクト開発者は詳細を見なくても良いようにもしています。
現段階での最適な解決策とは
AIエージェントのアーキテクチャ
川中:AIエージェントをどのようなアーキテクチャで開発しているのかについてもお話しします。
下図がAIエージェントのアーキテクチャ外観です。
会員限定コンテンツ無料登録してアーキテクチャを見る
川中:なぜこのようなアーキテクチャにしているのかというと、全てのプロダクトからエージェントを定義したいからです。
エージェントの実行は全てのプロダクトからデータを取得できるようにしたかったため、このように分けざるを得ませんでした。
エージェントの実行を全てのプロダクトから行いたいというのも関係しています。
フロントエンドにUIが出るのであればフロントエンドからエージェント実行部にアクセスをすればいいのですが、プロダクトから呼び出したい場合もあるため、今回は目をつぶる形でこのように実装をしています。
ただ、どうしても実行途中にデータが足りなくなってしまい、ユーザーからの入力が難しくなるため、あまり推奨はしません。
エージェントの定義と実行について
川中:エージェントを実行するためのAIエージェントフレームワークは複数あり、どれに乗っていくのがいいのか、現段階では判断しづらいというのが正直なところです。
MastraやAgnoなどを見ていると、その多くがワークフローやエージェント評価機能などに多くの価値があると思います。
ただプロダクトがすでにあるとドメイン知識が分散してしまうため、ワークフローをフレームワークに組み込みにくい。プロダクトのドメイン知識はプロダクトサーバーだけに載せておきたいため、ナレッジワークでは、フレームワークから独立した形でいろんなものを作っておくことにしました。
ナレッジワークでは単体のエージェントやチームエージェントが動かせれば十分ですし、しばらくはこの形でいけるだろうと考えています。ワークフローは結局プログラムですし、何かあれば自分でif文を書けば同じものを作れるかなと。
エージェント定義については、protbufを使用しています。実行時にはprotbuf定義からAIエージェントフレームワークの形式に変換して利用します。
会員限定コンテンツ無料登録してアーキテクチャを見る
川中:MastraやAgnoも見てみたのですが、現状ではどれも同じような形式で定義できるため、新しいAIエージェントフレームワークが現れて採用することになっても大丈夫なのではないかと考えています。
次に、テナントによるエージェントのカスタマイズについてもお話しします。
前提として、UI上でエージェントを動かしたいときは目的を持ってエージェントを実行しようとしているため、どのエージェントを呼び出したいのかは決まっています。
そのため、デフォルトエージェントとカスタマイズエージェントを同じ名前で定義しておいて、呼び出せるようにしています。最初にテナント単体のカスタマイズエージェントを探して、なければデフォルトエージェントを使うという形です。
会員限定コンテンツ無料登録してアーキテクチャを見る
川中:ただ、定義を凡庸的にしすぎたかなとも思っていて。プロンプトを変更できれば良いですし、現時点でここまでエージェントを定義する人はいないため、少しやりすぎたかもしれません。
次に、エージェントを動かすにはセッションを作成する必要がありますよね。エージェントは一般的にセッション単位で会話を覚えなくてはいけません。
ナレッジワークの場合はUIに溶け込んでいるエージェントが多く、どういったコンテキストでエージェントを動かそうとしているのか、という点が自然とセッションの中に組み込まれている必要があります。
会員限定コンテンツ無料登録してアーキテクチャを見る
川中:clineなどは@で文章(ファイル)を指定して文脈を読み込ませることができますが、必ずしもクライアントのリテラシーが高いわけではありません。
ITに詳しくない人が何もせずに必要なものが自然とセッションの中に組み込まれている形が求められるため、session_context(JSON)のような定義をセッション中に定義しています。
エージェントの実行フローについては、下図のようになっています。
会員限定コンテンツ無料登録してアーキテクチャを見る
川中:ややこしく見えるかもしれませんが、権限チェックやエージェント定義、セッションコンテキスト定義などを自然な場所でやるためには、フローを回りくどくする必要がありました。
フローの方を簡単にすると権限チェックやコンテキスト定義や自然な場所でなくなってしまいますし、今後プロダクトが増えることを想定すると、こういった構成の方がメリットが大きいだろうと判断したわけです。
ちなみに、プロダクトからエージェントを実行する場合は、下図のようなフローにしています。
会員限定コンテンツ無料登録してアーキテクチャを見る
ツール定義とツールの実行について
川中:続けてツールについてもお話しします。
LLMは外部機能をツールとして呼び出すことができますが、ツールの実装時には、定義する以外にも認証や認可などの面でチェックしなくてはいけないことがあります。
また、プロダクトサーバーをデプロイすることによって、エージェント実行部に手を入れなくてもツールやエージェントを更新できるようにしたいという思いもあります。
実際にはどのようにしたのかというと、最近はツールをMCPで実装する流れができつつあるため、その流れに乗ることにしました。
ナレッジワークではprotobuf+connect rpcでさまざまなものが定義され、権限チェックやライセンスチェックもprotobufに書いておくと、自動でチェックコードが生成されます。
ただMCPはJSON-RPCであるため、なんらかの返還が必要でした。
その解決策としては、ツール自体をプロダクトサーバーに実装しました。ツールの基本形をprotobuf+connect rpcとして実装し、JSON-RPCのアダプタを自動生成すれば勝手にツールができるのではないかと考えたのです。現状では、この構成でうまくいっています。
ツールの定義についてもprotobufで下図のようにMcpToolServiceを定義しています。
会員限定コンテンツ無料登録してアーキテクチャを見る
川中:実際にツールをprotobufで定義した例もお見せします。
会員限定コンテンツ無料登録してアーキテクチャを見る
川中:このようにprotobufにtool nameやtoolのディスクリプションを書いておくと、MCP上のtoolの定義となるようにアダプタを作れます。引数の説明も同様に可能です。
実行について、アダプターをどう動かしているのかというと、結構面白い仕組みをしています。
会員限定コンテンツ無料登録してアーキテクチャを見る
川中:内部向けMCPプロキシについては、実行部からのリクエストを受け付けるportを用意しています。MCPサーバーはstreamable httpで作るのが1番最新の方法ですし、この形で作っています。
ツールの呼び出しについては一つだけハックがあり、connnect rpcはendpointさえわかっていればContent-Type:application/jsonで呼び出せます。
そのため、RequestとResponseをどちらもStructと呼ばれるJSON型だと思って呼び出せば、型がわからなくても呼び出せます。もらったresponseをJSONにしてtoolからの返却値だと言ってagentに返すだけですので、ここは型がついていなくても大きな問題はありません。
外部向けMCPについては、使えるツールが違うため、別の定義が必要です。
会員限定コンテンツ無料登録してアーキテクチャを見る
川中:MCPサーバーはJSON-RPCを受け付けるため、認証のレイヤーでJSON-RPCを受けて、横断backendで中身を処理する形です。
こちらも内部向けMCPと同じ記法で外向けツールを定義することができるようになっています。
より良い方法を模索し続ける
川中:最後に、一言だけお伝えさせてください。
本日お話ししたことは、あくまで一例であり、これが正解だと言いたいわけではありません。
私たちが試行錯誤するなかで見つけた「現段階での最適解」についてお話ししました。
今後ずっと同じことを続けていくのではなく、他に良い方法が見つかれば、変更する可能性もあります。
技術の進化やトレンドの動きをウォッチしながら、これからもより良い方法を模索していくつもりです。
本日はご清聴ありがとうございました!
▶株式会社ナレッジワーク/Zenn
https://zenn.dev/p/knowledgework
当日は参加者からの質問にも答えていただきました。
詳細はアーカイブ動画にてご覧ください。
https://findy-tools.io/events/f16402d72cecbf57f083