
はじめに
こんにちは!テクノロジー戦略室AIMLチームで半年間内定者インターンをしていたYFです。
「AIエージェントってそんなにプログラム書くわけでもないし、1週間くらいでサクッと作れるでしょ?」 …開発を始める前の私は、正直そう思っていました。
今回は、私が内定者インターンとして開発に携わったクラウドコスト調査AIエージェント「マッコーリー」の開発の裏側をお話しします。 結論から言うと、プロトタイプは3日で動きましたが、そこから実業務に耐えうるレベルまでエージェントを安定稼働させる道のりは決してスマートなものではありませんでした。
全く安定しない応答、データの直接処理によるコンテキスト爆発、ハルシネーションとの戦い、などなど...。
本記事では、最新のAI技術をプロダクトに落とし込む過程で直面した「理想と現実のギャップ」と、それを乗り越えようとした「泥臭い試行錯誤のリアル」を包み隠さずお伝えします!
(ちなみに「マッコーリー」という名前は、オーストラリアで独自の貨幣制度を確立した偉人にあやかっています!)
課題と背景
クラウドインフラ(GCPやAWSなど)のコスト管理は、システム規模が大きくなるほど複雑になります。開発現場では、毎週のミーティングのたびに手動でSQLを叩いてコスト推移を表示し、変動の原因を調べるのに毎回20~30分ほど時間を取られていました。同じような作業が複数のチームで行われており、会社全体で見ると見過ごせない工数になっていました。
「AIエージェントにSQLを叩かせれば、このコスト監視業務を完全に自動化できるのでは?」
マッコーリーは、そんな着想から「人間の手によるコスト監視を不要にする」ことを目標に誕生しました。社内のインフラコストを集約したデータベースを連携させ、AIエージェントが自律的にSQLでデータを取得し、コストの監視や分析を行う仕組みです。フロントエンドは業務で最も使われているSlack との連携を選択しました。
主な技術スタックは以下です。
| エージェント | Google Agent Development Kit (ADK) |
|---|---|
| データソース | BigQuery |
| バックエンド | Agent Engine Cloud run functions (slackとAgent Engineの通信を仲介) |
| フロントエンド | Slackボット |
第一形態:モデルが直接SQLを生成・実行
まずは、1つのAIエージェントからデフォルトのSQL実行ツール(execute_sql)を直接呼び出してクエリを実行するシンプルな構成を試しました。

【結果】
構成がシンプルな分、応答速度は非常に早かったです。しかし、複数テーブルの結合や「上位の項目のみ表示させる」といった複雑な処理が苦手でした。特に、存在しない架空のテーブルスキーマをでっち上げてエラーを出してしまう現象が多発し、プロンプトのチューニングだけでは解決できない壁にぶつかりました。
第二形態:マルチエージェントで高度な処理をこなす
シングルエージェントの限界を突破するため、Googleが公開しているdata_scienceエージェントの仕組みを参考に、複数のエージェントが協調して動くマルチエージェント構成へと舵を切りました。 さらに、コードを実行するためのエージェントも追加したことで、データの取得だけでなく「データの加工」「グラフの生成」「将来コストの予測」まで行えるようになりました。

構成は一気に複雑になりましたが、正確なSQLを実行させるために以下のステップを踏むように設計しています。
- 親エージェント: ユーザーからのリクエスト(自然言語)を受け取り、データ取得担当の監督エージェントに依頼を送信する。
- データ取得監督エージェント: リクエストの意図を解釈し、必要なデータを「SQL生成エージェント」に分かりやすく説明する。
- SQL生成エージェント & 実行: 監督からの説明をもとに精緻なSQLを生成し、監督エージェントがツール(
execute_sql)を使ってそれを実行する。
【結果と技術的なメリット】
この構造の最大のメリットは、「ユーザーの曖昧な質問を解釈するフェーズ」と「厳密なSQLを生成するフェーズ」を分離できたことです。
例えば、ユーザーが「今月の〇〇プロジェクトのコストを調べて」とリクエストしたとします。
- 監督エージェントの思考: 「今月は12月だな。プロジェクトと指定しているからGCPのデータだな…」と、足りない情報を補完して要件を整理します。
- SQLエージェントの思考: 「特定のプロジェクトなら
project.idでフィルタリングすればいいな」と、データベースの構造(スキーマ)に合わせることに集中できます。
役割を分担させることで、LLMのポテンシャルを最大限に引き出し、精度の高いデータ抽出が可能になりました。
【マルチエージェント化の代償】
できることが大幅に増えた一方で、マルチエージェント化による致命的なデメリットも明らかになりました。現在直面している主な課題は以下の3つです。
- エラーの無限ループ: SQLやコードの実行に失敗すると、エージェントが自律的に何度もリトライを繰り返すため、エラーの沼にハマると解決(またはタイムアウト)までに10分以上かかることがありました。
- 処理のボトルネック: エージェントやツール間のやりとりをデータを含めすべて自然言語で行うため、扱うデータ量が多くなるとLLMが文脈を読み込むのに時間がかかってしまいます。
- チューニングの難易度: エージェントが階層化されているため、同じ質問をしても「エージェント間の会話の微妙な揺らぎ」によって最終的な応答が大きく変わることが頻繁にありました。プロンプトの変更がどのエージェントにどう影響するかの把握が難しく、調整が非常に厄介です。
第三形態:サブエージェントを「カスタムツール化」してスリムに
マルチエージェント化による「処理の遅さ」と「動作の不安定さ」というボトルネックを解消するため、思い切って一部のサブエージェントを廃止し、カスタムツール(プログラム化された関数)として実装し直すアプローチをとりました。
コードの記述量は増えてしまいますが、LLM特有の「不確実性」を排除できるのが最大の強みです。また、データの受け渡しを自然言語のやり取りではなく、プログラム上の構造化データとして直接処理できるようになったため、LLMが長文を読み解く負担が一気に減りました。
様々なリクエストに対応させるため、ツールの設計はかなりモリモリです。例えば「SQL生成ツール」は、フィルタ条件、期間、集計単位、内訳の有無など11個もの引数を取ります。しかし、デフォルト値をうまく設定し、LLMが必ず指定すべき必須変数を3つに絞ることで、エージェント側の推論の負担を最小限に抑えています。

【結果】
各ツールの実行はローカル環境で1秒未満で完了するようになり、当初5分ほどかかっていた複雑な処理が、なんと30秒へと劇的に短縮されました! さらに、固定のプログラムツールを通すことでエージェントの応答がブレなくなり、細かい修正も確実に反映できるようになりました。
失敗からの学び:タスクの切り分けは人間と同じ
ちなみに、親エージェントとSQLツールの橋渡しをしていた「データ取得監督エージェント」もツールへの統合を試みました。しかし、こちらは逆に処理時間が30秒から40秒へと延びてしまう結果に。
原因は、「SQLを生成して、それを実行する」という一連の密接なフローが、親エージェント側に他のツールと一緒に統合されたことで、LLMにとって作業の文脈が認識しづらくなってしまったためだと考えられます。 「LLMにタスクを振る時は、人間にお願いする時と同じように、適切な粒度でタスクを切り分けることが大切」という貴重な学びを得ました。
デモでデータを捏造しまくる
カスタムツール化で動作が高速・安定したため、デプロイして実際に社内で使ってもらいました。ところが、ここで新たな問題が発生します。
作成中は特定のプロジェクト名が指定されることを前提に設計していたのですが、ユーザーから「〇〇事業全体のコストは?」といった粒度の違う想定外の質問が飛んできたのです。 するとエージェントはツールを一切呼び出さず、平然と架空のコストデータを捏造して回答してしまいました。
プロンプトで「データの捏造は禁止」と強く指示してはいたのですが、「ツールを使うための前提情報が足りない質問」に対する例外処理をきちんと定義していなかったため、LLMがただの「一般的な質問」として受け取ってしまい、無理やり回答を作ってしまったのが原因でした。
テストを回してゴリ押す
この捏造問題を防ぐため、エージェントがハルシネーションを起こしそうな「意地悪な質問」や「情報不足の質問」をパターン分けし、合計100個のテストケースを作成しました。
実際に自動テストを回してみると、メインの回答だけでなく、裏側のメタデータやエラー発生時の提案メッセージなど、思わぬところで不適切な応答がちらほら見つかりました。テストと評価を何度も繰り返し、プロンプト(システム指示)に以下の改修を加えました。
- 対応できる質問・できない質問の境界線を明確に定義する
- 「ツールを実行する前に、データ取得に必要な情報がすべて与えられているか確認する」という思考ステップを追加する
チューニングは泥臭い作業でしたが、このチューニングによって、想定外の質問に対しても「〇〇の情報が足りないため、取得できません」と、期待通りに正しく制御された応答を返せるようになりました。
最終形態:シングルエージェント・大量のカスタムツール
エージェントの基本機能は完成しましたが、実用化する上で大きな問題がふたつありました。
①エラーをツールのせいにする問題
例えば、ユーザーの指示が曖昧で、サブエージェントが生成したSQLに構文エラーが出たとします。本来なら「〇〇の条件が足りないため検索できません」とユーザーに聞き返してほしいのですが、親エージェントは「すみません、ツールの不具合でご期待に沿えませんでした」ととりあえず謝罪してしまうケースが散見されました。
プロンプトで「原因を具体的に説明して」と強く指示しても、結局「ツールの不具合です」と言い張るか、「詳しくは開発チームにお問い合わせください」と放棄してしまうかの二択になってしまい、根本的な解決には至りませんでした。

この責任逃れ問題を解決するため、シングルエージェント構成に戻し、1つのエージェントに全ての行動の責任を持たせるという大きな変更を決断しました。エージェントが扱うツールの種類を最小限にするため、「SQLの生成」と「SQLの実行」という2つのステップも1つのカスタムツールに統合しました。

【結果と技術的なメリット】 このアーキテクチャ変更は、予想以上に大きなメリットがありました。
- 的確なエラーメッセージ: エージェントが自身の責任として、「ユーザーの指定のどこに問題があったのか」を適切にフィードバックするようになりました。
- コンテキスト圧縮と安定性の向上: これまでは親がサブエージェントに自然言語で指示を出していましたが、統合ツールへパラメーターとして条件を渡す構造に変わりました。これにより、エージェントが「システムで何が取得できるか」をより的確に理解するようになり、エージェント間の会話のブレが消え、動作が圧倒的に安定しました。
もちろん代償として、ツール(プログラム)側でSQL作成・実行時のあらゆるエラーパターンを網羅してハンドリングする機能実装が必要になりましたが、結果としてシステム全体の堅牢性は劇的に向上しました。
②データ処理の最適化
社内で使ってもらううちに、「もっと長期間の、より多くのコストデータを一度に見たい」という要望が出てきました。
以前の構成では、データのでっち上げを防ぐためSQLから取得した生データをそのままエージェントにテキストとして渡し、回答を作らせていました。このため、コンテキストの圧迫や処理落ちを防ぐべく、データの取得上限を最大1000件に制限していました。
そこで今回、「扱うデータ」と「エージェント(LLM)」を完全に分離させるように構造を根本から見直しました。
具体的には、エージェントが「SQL生成・実行ツール」を呼び出した後、その結果の生データはLLMに読み込ませず、専用の「データ表示ツール(表の作成など)」へ直接引き渡すフローを義務付けました。これにより、エージェントがデータを間違って表示するリスクもゼロに抑えられます。
この改修中、エージェントが高度なデータ分析を終えた後に、ただ生データを表示するだけの表作成ツールを無駄に呼び出してしまうケースが発生しました。 この問題はツールの名称や変数の名前をLLMが直感的に役割を理解しやすいものにリネームすることで解消できました。関数自体の名前はプロンプトと同様かそれ以上にエージェントの挙動に影響していそうです。

まとめ
技術的な学び:正確性をどう担保するか
- プロトタイプは1週間、安定運用への道のりは長い LLMを使ったエージェントは、動く原型を作るだけなら1週間程度でサクッとできてしまいます。しかし、そこから実業務に耐えうるレベルまでエージェントの挙動を評価し、エラーを改善し、安定運用させるための泥臭い調整に圧倒的な時間を費やしました。
- LLMの「責任範囲」は極小化する 今回のような「正確な数値」が求められるタスクでは、LLMに何でもやらせるのは危険です。マッコーリーでは、LLMの役割を「ユーザーの意図を分析し、必要なデータと表示方法を判断すること」に限定しました。数値の異常判定など、LLMでもできそうな処理であっても、可能な限り固定のプログラム(ツール)に任せることで予期しない回答をするリスクをなくせます。
- Text-to-SQLの限界とコンテキストの壁 自然言語からSQLを生成させる際、スキーマやカラム名を全てプロンプトに入れ込めば正確に作ってくれそうに思えますが、現実はそう簡単ではありません。 例えば「期間」ひとつとっても、コストの「利用開始時間」「利用終了時間」「請求月」など複数の解釈があります。これらを正確に判定させるための条件を細かく指定していくと、プロンプトのコンテキスト量が膨大になり、結果的にLLMが混乱して動作が不安定になってしまいました。
- ハルシネーションを回避するための2つのアプローチ 文章を生成するAIと違い、データベースから数値を引いてくるAIの場合、ユーザーは「その結果が本当に正しいか」を裏付けられません。そのため100%の信頼性が求められます。前述のText-to-SQLの課題を乗り越えるため、以下の対策を取りました。
- データ抽出の完全ツール化: LLMにはSQLを書かせず、リクエストの解釈とパラメータ抽出のみを行わせる。
- 解釈プロセスの言語化: 出力結果に「〇〇年〇〇月の、〇〇を基準にしたデータを表示しています」と十分な説明を添えさせる。「週の開始日」や「年」など、ユーザーの入力から抜け落ちやすい条件をエージェントがどう補完したかを明示し、ユーザー自身が意図通りか確認できるようにしました。
運用・プロダクトとしての学び:ユーザーの真の課題は何か
- スコープの拡大とアーキテクチャの限界 当初は「小規模チームが自チームのコストを見る」想定でしたが、いざ公開すると「全社のプロジェクトを横断して監視したい」という大規模な要望も寄せられました。また、今回は特定のデータウェアハウス(BigQuery)に特化して構築したため、「他のメトリクスや外部ツールも繋ぎたい」という要望にはすぐに応えられませんでした。汎用性を上げれば使い勝手は向上しますが、データソースを増やすほどLLMの意図解釈は難しくなります。拡張性を見据えた設計の難しさを痛感しました。
- 「毎回チャットで対話する」必要はあるのか? マッコーリーは最終的にSlackと連携し、Slackのワークフローから定型文で呼び出される運用が大半になりました。 ここで思ったのは、「毎回同じ関数の呼び出しを期待するなら、間にLLMを挟む必要はないのではないか?」ということです。毎回AIに解釈させるのではなく、「コスト監視を定期実行するワークフローを生成・設定してくれるAI」へとピボットした方が、APIコストの削減や応答品質の保証に繋がるかもしれません。
- 技術ベースではなく課題ベースで 「最新のAI技術でこれを使いたい!」という思いに囚われすぎず、解決したい問題とユーザーを明確にすることが何より重要だと学びました。事前のヒアリングはもちろん大切ですが、今回のように「作って使ってもらって初めて分かること(想定外の質問、予想外の使われ方)」も多々あります。事前の設計と、その後の軌道修正のバランスは難しく、これから開発を積んで学びたいです。
いかがだったでしょうか?AIエージェントによる業務の完全自動化のハードルはまだまだ高そうですが、特定の要素に特化して工数を削減するエージェントは今後増えてきそうだと思いました。これから同じようなAIエージェントを開発しようとしている方の参考になれば幸いです!
レバレジーズに少しでも興味を持っていただけた方は、こちらからエントリーをお願いします。 leverages.jp