TECH PLAY

株式会社Insight Edge

株式会社Insight Edge の技術ブログ

190

この記事は、 Insight Edge Advent Calendar 2025 の11日目の記事です!! はじめに こんにちは。アジャイル開発チームでエンジニアリングマネージャーをしている三澤です。本記事では今年実施したチームビルディング施策をご紹介します。 弊社Insight Edgeは、住友商事グループ各社のDXを推進するためにプロジェクト単位でチームを組成するスタイルをとっています。 そのため、「これまで一緒のプロジェクトに入ったことがないメンバーと次のプロジェクトでチームを組む」という状況が発生します。 だからこそ、プロジェクトをまたいでコミュニケーションを取り、互いの思考や技術観を知ることが重要となります。そこで今回実施したのが「アーキテクチャ大喜利」 です。 ここからは、実施背景から実施までに準備したこと、当日の様子、そして実際にやってみて分かった知見をご紹介します。 目次 なぜアーキテクチャ大喜利をやることにしたのか 当日までに準備したこと 当日の進め方 やってみて得られた効果と改善点 今後の展開と応用の可能性 なぜアーキテクチャ大喜利をやることにしたのか 企画のきっかけとなったのは、昨年読んだFindy Toolsの記事「 アーキテクチャ大喜利『もし、⚪︎⚪︎な仕様のサービスを立ち上げるなら、あなたはどんなアーキテクチャを組みますか?』 」でした。 著名なエンジニアが同じ仕様をテーマにそれぞれの視点から設計案を語る形式となっており、読み物としても非常に面白いものでしたが、これを読んだ時にふと思いました 「この形式、社内コミュニケーションにも使えるのでは?」 と。 同じ技術課題に対して人によってどう発想が分かれるのかを見ることは、それだけで相互理解と技術向上につながります。 そこで、この記事のコンセプトはそのままに、以下の2つを目的としたチーム活動として実施することに決めました。 実施の目的 普段関わらないメンバー同士のコミュニケーション促進 技術力の底上げ(特にアーキテクチャ思考) 当日までに準備したこと チーム活動としてのアーキテクチャ大喜利の成否は「お題」で9割決まると言っても過言ではありません。ゆえに、参加者が楽しみながらも、技術的な思考を深められるような絶妙なバランスのお題を作る必要がありました。 そのために今回はChatGPTと対話を重ねながらお題をブラッシュアップしていく方法を取りました。 具体的には 1. 議論の方向性を揃えるための「制約」 2. 個性を引き出す「設計の余白」 3. 2時間で完結させるための「スコープ管理」 の3つのポイントを意識しながら作成しました。 1. 議論の方向性を揃えるための「制約」 お題に制約がないと注目すべき論点がバラバラになり比較がしづらくなります。よって共通の論点を確実に着目させるための仕組みが必要になります。そこで今回は以下のような制約を設定しました。 テーマ: LLM(AIエージェント)での大規模ドキュメント要約 時間的制約: 1件約1分の個別要約を最大20件、さらに3分の統合処理を経て、全体を15分以内に返す。 負荷要件: 10〜30件の並行リクエストに耐えられること。 技術的課題: 共通APIから取得できるのはメタ情報のみ。PDFやテキストなどのドキュメント本体は自前で解析・抽出する必要がある。 LLMの応答遅延や失敗を考慮したリトライ処理が必須。 このような制約を設けることにより、単純な逐次処理のアーキテクチャでは不可能になります。これにより、参加者は「並列・非同期処理」「スケーラビリティ」といった論点を議論することが可能になります。 2. 個性を引き出す「設計の余白」 一方で制約をガチガチに設定すると正解が一つに近づいてしまい大喜利としての面白さが薄れます。 そこで今回は同じ要件でも複数の解き方が存在する状態を作りました。 データ前処理の流儀 ドキュメント形式がバラバラなため、「どこまで丁寧に前処理するか」でチームの個性が出る はず です。「最初にすべてを共通フォーマットに変換する堅実派」や「プロンプトエンジニアリングで吸収する柔軟派」など、アプローチが分かれることを狙いました。 UX要件の解釈を委ねる 「ユーザが進捗を見られること」とだけ定め、具体的な方法は問いませんでした。これにより、「WebSocketでリアルタイム性を追求するチーム」や「ポーリングでシンプルさを選ぶチーム」など、何を大切にするかで技術選定が変わってくる はず です。 (余談ですが、最終的には似た構成に寄ったのですが、それはそれで「Insight Edgeの設計文化の一貫性」が見えて面白かったポイントです。) 3. 2時間で完結させるための「スコープ管理」 最後に、2時間という限られた時間で議論が脇道に逸れないよう考えなくていいことを明確にしました。 あえて周辺要件をシンプルに データソースを「共通リポジトリ1箇所」に限定し、複雑な認証などをスコープ外に。これにより、参加者は「AIエージェントの非同期並列処理」という本丸に集中できます。 議論の道標を提示 お題の最後に「考慮すべき観点」としてヒントを提示し、議論が迷子にならないよう工夫しました。 以下が実際にイベントで使用したお題です。 作成したお題 # アーキテクチャ大喜利:大規模リサーチ用「ドキュメント要約・統合エージェント」 以下の前提・要件を満たすシステムを **どんな技術スタックで** 構成するかを議論・発表してください。 2時間の検討時間内で、クラウドサービスやミドルウェアを含めた設計アイデアをまとめましょう。 --- ## 前提(シナリオ) - **企業規模 / 利用者:** - 研究者・アナリスト・社員など数百~数千名がいる大企業を想定。 - 彼らが、日常的にドキュメント(PDF, テキスト, スライド等)を参考にして新しいリサーチやレポートをまとめるニーズがある。 - **目的:** - ユーザがまとめて指定した複数のドキュメントを、**AIエージェント(LLM)** で自動的に要約したい。 - 個別要約だけでなく、最終的に **全体を統合したサマリ**(重複排除や結論の集約)を作成して返す。 - **ドキュメント保管:** - 各部署が作成したファイルは **1つの共通リポジトリ**(S3や企業内ストレージ)に集約されている前提で**共通APIでアクセスできる**ものとします。 - ただし、**共通APIから取得できるのは“インデックス(メタ情報)”のみ**で、実際のドキュメントは**PDFや生テキストのまま**格納されており、システム側でテキストの解析・抽出が必要になる。 (例:PDFパーサーやテキスト抽出、OCRなど。どう実装するかは自由) - APIではカテゴリとファイルの種類、件数を指定することでメタデータを取得することができる - カテゴリは1,000種類 --- ## 要件 1. **処理時間の制限** - 依頼から **15分以内** に、最大20件のレポート要約と統合要約を完了し、ユーザへ結果を返すこと。 2. **1レポート要約にかかる時間** - 1件あたり **約1分** を想定(テキストであればという前提)。 3. **複数ユーザからの同時リクエスト** - 1度に **10~30件** 程度の並行リクエストが来る可能性がある(高負荷を想定)。 4. **ドキュメントの検索・抽出** - 共通APIから取得できるのはレポートのメタ情報(タイトル、更新日、URL等)のみ。 - 実際のドキュメントコンテンツはPDFやテキストのまま別途ダウンロードし、**自前で必要な箇所を抽出・解析**する必要がある。 5. **進捗表示 / 結果受け取り** - ユーザが「何件終わったか」「残りの所要時間は?」などのステータスを確認できる仕組みを用意する。 - 全要約完了後、最終的な統合サマリを取得できる(Web UI / PDF / JSON など形式は自由)。 6. **AIエージェント(LLM)の利用** - 外部API(例:OpenAI)や自前GPUクラウド(Kubernetes上のモデルなど)、いずれでも可。 - セキュリティ・コスト・性能を考慮し、採用理由を示すこと。 7. **再実行** - 異なるユーザが同じレポートを指定する可能性がある。 8. **統合要約の工程** - 個別要約がすべて揃った後、重複・矛盾を排除し **最終レポート** を生成。 - この統合要約工程に **約3分** かかる想定。 --- ## 考慮すべき観点 - **並列実行と非同期フロー** - 1件1分×20件 + 3分 = 23分をどう15分以内に短縮するか? - オートスケーリング/キュー&ワーカー/並列化など。 - **モニタリング・運用** - 高負荷時にどのようにスケールさせる? - LLM呼び出しが遅延した/失敗したときのリトライ&エラーハンドリングは? - LLMOpsはどうする? - **コスト / セキュリティのトレードオフ** - 外部LLMだと開発が簡単だが機密データや費用は? - 自前LLMの場合はGPUリソース管理が必要。 - **ユーザビリティ / 部分的可視化** - レポートの一部が要約完了した時点で結果を先行表示するか、すべて終わってから返すか。 - 進捗状況をどう把握してユーザに通知するか(WebSocket, Push, Polling等)。 --- ## 大喜利のお題 - **目的**: 「どんな技術スタックやクラウドサービスで、上記要件を満たすシステムを作るか」 - **ゴール**: - 2時間の検討時間内に、「自分たちならこう作る!」というアーキテクチャ案をまとめ、**構成図・主要コンポーネント・フロー**など発表頂きます。 - 発表では **なぜその技術スタック・構成を選んだのか**、**どのように時間・高負荷・ドキュメント解析の課題を解決するか**、**コストやセキュリティとのトレードオフ** など、考えたことを自由に語ってください。 - **ポイント**: - 「どんなクラウドサービス・技術を使ってこの要件を満たすか?」 - 「どのように15分以内で最大20件の要約と統合要約を処理するか?」 - 「複数ユーザの同時依頼をどうさばくか?」 - 「進捗表示やエラー処理をどのように実装するか?」 当日の進め方 当日は、最大3人の小規模チームを複数つくり、チームに分かれて2時間の検討+1時間の発表という合計3時間のスケジュールで実施しました。 ポイントは、ジュニアとシニアをバランスよく混ぜたチーム編成にしたことです。 また、今回はあえて事前にお題は共有せず、時間制限のある中で考えてもらう形式としました。 そのようにすることで、 シニアの思考プロセスを間近で学べる ジュニアの柔軟な発想が議論に新しい視点をもたらす 普段同じPJにいないメンバー同士で自然に役割分担が生まれる という効果が期待できます。ただし、事前にお題を共有しない点については、終了後アンケートで事前共有しても良かったのではないか、とのコメントもあったため、目的と手段のバランスはしっかりと考える必要があります。 また、このアーキテクチャ大喜利は「技術発表会」ではないので、 正解よりもなぜその構成を選んだのかという思考を明らかにすること 人によるアプローチの違いがあること を理解して進めることが重要です。事前にそのような目的をメンバーに伝えることで、より適切な議論が生まれてくると考えます。 やってみて得られた効果と改善点 実施後のアンケート結果を見ると、参加者の満足度は非常に高いものとなりました。 今回の取り組みは楽しかったですか?:平均 4.7点 / 5点 またこのようなイベントはやりたいですか?:平均 4.7点 / 5点 勉強になりましたか?:平均 4.5点 / 5点 時間は十分でしたか?:平均 3.2点 / 5点 難易度はどうでしたか?:平均 2.8点 / 5点 (本設問のみ3がちょうど良いレベルと設定) この結果を見ると、「楽しさ」と「学び」が高水準で両立できており、イベントとしては成功だと考えられます。一方で、「時間は十分でしたか?」という質問に対する点数は低く、運営上の課題も見えました。 以下、具体的にもらったコメントを交えて良かった点と改善点を振り返ります。 良かった点 普段関わらないメンバーの 技術のクセ が分かった 「この人はユースケースから考えるんだ」「この人はデータ構造を先に固めるタイプだ」など、発想の違いがよく見えました。実際に以下のようなコメントがありました。 メンバごとの得意領域が異なるおかげでそれぞれをカバーしながら議論をしつつ、メンバの考えも理解することができて有意義だった 普段一緒に仕事していないメンバーとアーキテクチャ設計の議論できて、それぞれが持っている知識、業務からの知見、日々感じている課題を共有できた。共通言語がまた一つ増え、今後一緒に仕事したら役に立ちそうだと思った これらの結果は、実際のプロジェクトでチームを組むときのコミュニケーションコストを下げることができると考えます。 思考過程の共有で学びが多い アーキテクチャ設計は結果だけ見ても学べる量が限られます。 しかし検討中のコミュニケーションや大喜利形式での発表を通じて、トレードオフの優先順位など、普段は見えにくい部分を明らかにすることができました。実際に以下のようなコメントがありました。 普段使わないもの(AWS ※1)で設計できたので勉強になってよかった 議論を通じて、自分が知らない部分、他のチームメンバーもあまり分かっていない部分を把握できるようになり、今後しっかり調べて、情報発信すべきポイントなども掴むことができました このように、単に設計知識を得ただけでなく、お互いにどこが分かっていて、どこが分からないか、を更新できたことは大きな収穫でした。 ※1 弊社はプロジェクト毎にクラウドを使い分けており、普段Google CloudやAzureをメインで使っているメンバもいます。 改善点 設計にばらつきが出なかった 事前に「個性が出る仕掛け」を用意したつもりでしたが、結果的にはどのチームも似た構成になってしまいました。実際に以下のようなコメントがありました。 各チーム似たりよったりになったので、LLM自前みたいな普段やらないような制約があるともっとおもしろくなるかなと思いました これは先にも書いた通り、ある意味弊社メンバのスキルが安定していると言うことも意味しますが、大喜利としての幅・技術力向上という意図に対してはもう少し広げたかったところです。 時間配分と事前準備 「時間は十分でしたか?」の回答は平均3.2点でしたが、内訳を見ると「2(足りなかった)」と「4(十分だった)」に意見が割れました。 当日は30分延長することになったため、時間内に収める工夫が必要と考えられます。実際に以下のようなコメントがありました。 お題を先に共有して、それぞれ事前にリサーチして持ってくるのも良いではないかと思います。ある程度調べられて事前準備がある状態だと議論もよくなり、より良い設計もできるのではないかと思いました 日々の業務を進める中で事前準備の時間を作るのも難しいかもしれませんが、事前準備をすることで議論に深みや幅を持たせられる可能性があり、今後検討していきたいと考えています。 これらの結果を踏まえると、今回のお題設定における①議論の方向性を整える制約と②個性が出る余白、そして③時間内で深めるスコープ管理はまだ改善の余地があったと言えます。 そのため、次回に向けては、 明確に複数の方向性があり得るお題 要件に明らかな矛盾を仕込む(例:コスト最優先 vs 精度最優先) 技術選定の自由度が高いテーマ(その場合には事前調査時間を作る) などを混ぜたいと考えています。そうすることで今回でなかったバリエーションを増やせそうだと感じました。 ただ総じて交流イベントとしては成功だったと考えています。 今後の展開と応用の可能性 アーキテクチャ大喜利の考え方は他の場面にも応用可能だと考えています。 今後のアイデア例 新人・若手向けの設計トレーニング お題形式だと、ハンズオンより「思考の深さ」が見えやすい。 プロジェクト開始前のキックオフでのアイスブレイクアクティビティ 「この人はこう考えるんだ」が最初に掴めると、初動が圧倒的に速い。 今後もテーマを変えながら継続し、Insight Edgeの文化として根付かせていければと考えています。 この記事が、皆さんのチームや組織のコミュニケーションを活性化させる、何かのヒントになれば幸いです。
この記事は Insight Edge Advent Calendar 2025 の10日目です。 こんにちは、Insight Edgeの齊藤です。 生成AIサービスの進化は著しいものがあります。「会話しながらアプリを作る」「文章で要件を書くだけで構造を提案してくれる」といった体験が、いよいよ現実の選択肢になってきました。 私自身は普段エンジニアとしてコードを書いていますが、「ノーコード/ローコードでどこまで実現できるのか」「どの領域からは従来どおりコードを書いた方がよいのか」を見極めることが、技術の目利きや自身の価値の把握、さらにはキャリアを考えるうえでも重要だと感じています。 そこで今回は、その試金石としてCopilot StudioとDataverseだけで、ノーコードでデータ分析エージェントを作成してみました。 Power Platform と Copilot Studio の概要 データ分析エージェントの作成にあたり、利用したサービスについてご紹介します。 Power Platform Microsoft Power Platformは、ざっくりいうと「業務アプリをノーコード/ローコードで作るためのプラットフォーム」です。 Power Apps:業務アプリを作る Power Automate:ワークフロー/RPAを作る Dataverse:アプリの裏側で動くデータベース Power BI:可視化・ダッシュボード Copilot Studio:生成AIエージェントをローコードで作るツール 参考: 公式ドキュメント(Microsoft Learn) Copilot Studio Copilot Studioは、Power Platform上で動作する「生成AIエージェントをローコードで作るためのツール」です。主に下記の内容を実現可能です。 会話フローの設計 : GUIでトリガー・質問・条件分岐などをつなげて、業務に沿った会話フロー(トピック)を定義できます。 ナレッジを使った回答(RAG) : Dataverse、SharePoint、ドキュメント、Webサイトなどをナレッジソースとして登録し、それらを元にした生成AI回答を行えます。 各種サービスとの連携・アクション実行 : Power Platformのコネクタやプラグインを通じて、Microsoft 365や外部SaaS、自社APIにアクセスし、データの参照・更新まで行えます。 Microsoft 365 / 各チャネルへの展開 : 作成したエージェントは、Microsoft 365 Copilotの拡張としてTeams / Outlookなどから呼び出せるほか、WebサイトやPower Appsなどにも公開できます。 Copilot Studioのエージェント設定画面 Power Apps のプランデザイナー Power Appsは、業務アプリをノーコード/ローコードで構築するためのツールです。今回はその中の「プランデザイナー(Plan designer)」だけを使い、Dataverseのテーブルとエージェントを短時間で準備しました。 「解決したいビジネス課題」を文章で入力すると、「要件エージェント」「プロセス エージェント」「データ エージェント」「ソリューション エージェント」が、Power Platform製品群をどう組み合わせるかを含めて具体案に落とし込んでくれます。内容が想定と違う場合も、生成AIに追加で指示することで修正できます。 今回はPower Appsのアプリ本体は作らず、プランデザイナーだけを使ってテーブルとエージェントを短時間で準備しました。 プランデザイナーの初期画面 データ分析エージェント作成 それでは実際に、データ分析エージェントを作成していきます。 今回は次の構成で、ダミー案件情報からグラフを可視化するエージェントを作ります。 データ:Dataverse(案件・担当者・工数記録・予実) 実行:Copilot Studio + コードインタプリタ(Python集計・描画) 出力:グラフとサマリをチャット出力 1. プランデザイナーにプロンプト入力 Power Appsのプランデザイナーを開き、下記だけ入力しました。 これによりプランデザイナーが要件定義からデータ定義、システム構成まで案を作成してくれました。 copilot studioとDataverseを使って、案件に関する予実や工数を確認するデータ分析エージェントを作りたい プランデザイナーへの初期入力 2. Dataverse確認 プランデザイナーが作成したDataverseを確認します。 ユーザ側で修正可能ですが、テストデータ含め問題なさそうなので次に進みました。 Dataverseテーブルの関連図 Dataverse案件データの例 3. システム構成の調整 プランデザイナーからは、データ分析エージェント以外に「工数記録サポート」「工数集計ダッシュボード」なども提案されました。 今回はMVPを意識し、「コードインタプリタを使うのでBIダッシュボードは不要」「エージェントは1つにして」と指示しました。 プランデザイナーへ修正を指示する例 これにより案件・担当者・工数記録・予実テーブルと単一エージェントだけに整理されました。 修正後のシステム構成 4. エージェント設定変更 プランデザイナーにより、Copilot Studioを用いたエージェントについて、プロンプト案やDataverseへの接続はすでに作成されています。 今回は以下だけ設定変更しました。 エージェント設定を最初にオンにする 「設定」>「はい、利用できるツールやナレッジを適宜使用し、応答を動的にします。」をオン 「コードインタプリタ」をオン プロンプト修正 プロンプトは 「案件、担当者、工数記録、予実データのテーブルを活用し、グラフとサマリを返答するエージェント。」 としました。 結果1 ここまでの手順だけで、 Dataverse+Copilot Studioのデータ分析エージェント 条件入力 → 集計 → グラフ画像をカード表示 という流れを、ほぼノーコードで形にできました。 完成したエージェントに実際に質問した際の挙動は下記の通りです。 案件と工数の関係を可視化 一方で挙動は安定せず、「案件ごとの予定工数vs実績工数」という質問の回答は失敗しました。 Dataverse自体は参照できているものの、エージェントはスキーマの関係性を十分に理解できていないことが推測されます。 失敗例 修正:テーブル定義をプロンプトに渡す Power Appsのテーブル定義画面からスクリーンショット画像を取得し、生成AIでテキスト化した「テーブル項目説明」をそのままプロンプトに追記しました。 追記した定義文(抜粋) 案件、担当者、工数記録、予実データのテーブルを活用し、グラフとサマリを返答するエージェント。 ##### データベーステーブル項目説明 ## 1. 案件テーブル - 案件名: プロジェクトの名称(案件A、案件B等) - 開始日: プロジェクトの開始予定日 - 終了日: プロジェクトの終了予定日 - ステータス: 案件の進捗状況(進行中、完了、計画中、中止) - 担当者数: 案件に割り当てられている担当者の人数 ## 2. 予実データテーブル - 案件名: 対象案件の識別名 - 案件ID: 案件テーブルとの紐付けキー - 予算工数時間: 計画上の作業時間 - 実績工数時間: 実際に費やした作業時間 - 予算金額: 計画上の予算額 - 実績金額: 実際にかかった金額 - 計画との差異: 予算と実績の差額(正値は予算オーバー、負値は予算内) ## 3. 担当者テーブル - 担当者名: 社員の氏名 - 役割: 職務上の役職(経営者、プロジェクトマネージャー、現場担当者等) - メールアドレス: 連絡先メールアドレス ## 4. 工数記録テーブル - 工数記録名: 作業記録の識別名 - 担当者ID: 作業を行った担当者の識別ID - 案件ID: 作業対象の案件識別ID - 作業日: 作業を実施した日付 - 工数時間: 作業に費やした時間数 - 作業内容: 具体的な作業内容(設計作業、開発作業、テスト作業等) 結果2 テーブル定義をプロンプトに定義した後は、予定vs実績の棒グラフや担当者×案件のヒートマップなど、複雑な集計もプロンプトだけで生成できるようになりました。 主な改善点は次のとおりです。 Joinが案件↔予実↔工数記録の粒度・方向で正しく組まれるようになった 予算工数・実績工数など抽出項目の取りこぼしや取り違えが減った Pythonコード(集計・可視化)の生成精度が上がり、そのままコードインタプリタで実行できるケースが増えた 予算工数と実績工数の比較グラフ 担当者×案件の工数ヒートマップ エージェントの挙動変化と現状の限界 2025/11/25時点では「生成オーケストレーションモード」をオンにするとエージェント自身が回答を返せていました。 その後2025/12/3時点の確認では、回答が一瞬生成されたあとすぐ消え、システムがデフォルトのフォールバックである「会話の強化」トピックへ自動切替する挙動になっていました。 その結果、少なくとも私の環境ではうまくグラフを生成できなくなってしまいました。 より安定的に動かすのであれば、「ユーザーから条件を聞く → Dataverseからデータを取得 → Pythonで集計・グラフ生成 → 結果を返す」といった流れを、Copilot Studioのトピック(会話フロー)として明示的に組む必要があります。 参考:Power Platformコミュニティの類似報告 https://community.powerplatform.com/forums/thread/details/?threadid=0c090dac-098e-f011-b4cc-000d3a1b2a56&utm_source=chatgpt.com また、その他にも下記のような制約が明らかになりました。 ローコード特有の制約:細かいカスタマイズが難しい/作成したロジックの再利用・移植に工数がかかる/環境分離やバージョン管理がコードベースより煩雑 Copilot Studio特有の制約:Reflection(自己評価ループ)など複雑なフローを組みづらい/エージェント実行が約2分を超えると失敗するため、複雑なタスク実行に不向き これらの情報は日々のアップデートにより変わる可能性も大きいため、公式のドキュメントを確認いただければと思います。 まとめ 今回の検証では、Copilot Studioを含むPower Platform環境により、10分程度でデータ分析エージェントを構築できました。初回の試行錯誤を含めても1時間ほどでグラフ生成まで完了できました。 一方で、内部の設定はブラックボックスな部分も多く、挙動もサービス更新によって変わる可能性があります。安定的な動作やカスタマイズ性などが重要な場合は、現状はスクラッチでの開発が無難だと感じました。 最後に、記事執筆中にもGoogle Workspace Flowsが発表されるなど、生成AI関連のローコードツールの進化は目まぐるしいものがあります。今後も「ノーコードでどこまで任せるか/どこからコードで実装するか」を継続的にキャッチアップしていきたいと思います。
こんにちは!データサイエンティストの白井です。 今日は、私が第35回人工知能学会金融情報学研究会(SIG-FIN)で発表した LLMs による利益予測の分析とアウトオブサンプル評価 について紹介します。 本記事は、 Insight Edge Advent Calendar 2025 の9日目の記事となっております。 また Advent Calendarの7日目 には、35回SIG-FINの包括的なレポート記事もありますので、ご興味があれば覗いてみてください! はじめに EDINETについて 有価証券報告書の紹介 データについて EDINET-BENCHについて EDINET-BENCHでの予測方法 LLMでの未来予測の検証における注意点 LLMを用いたアウトオブサンプル評価 おわりに はじめに 今回は、「LLMを用いた、企業の1年後利益の増減予測」に関する、LLMのナレッジカットオフ以前と以降の精度を確認する内容です。 LLMを用いた未来の予測は、企業の利益だけでなく、色々な場面での活用余地があるかと思いますので、何かのご参考になれば幸いです。 記事全体の流れは以下です。 EDINETについて EDINET-BENCHについて LLMを用いた未来予測における注意点 アウトオブサンプル評価について EDINETについて EDINETとは、金融庁が運営する有価証券報告書等の提出・閲覧システムです。 有価証券報告書は、上場企業などが金融庁(EDINET)を通じて提出する、事業内容や財務状況などを報告するための書類です。 有価証券報告書の紹介 有価証券報告書の例は以下です。 ※1 利益や売上、従業員数などのデータが、直近5年などでまとめられている形式が多いです。 各企業がどんな売上や利益の推移をしているかなどが、確認できるようになっています。 また、有価証券報告書は、上記のような財務指標だけでなく、事業リスクのような数値で表せないテキスト情報も含まれています。 ※1 データについて EDINETは、上記の有価証券報告書等を保管しており、そのデータはAPI Keyを登録することで、無料で取得できます。 XBRLという形式で保管されており、これを理解するのはなかなかハードルが高いのですが、2024年4月以降はCSV形式でダウンロードできるようになり、かなり扱いやすくなっています。 CSVファイルは、以下のような形式です。 ※1 値の意味や対象時期、連結か単体か、などが整理されて格納されており、APIで取得できるためシステム連携もしやすくなっています。 また、数値データだけでなく、テキスト情報も格納されています。 ※1 このように、企業の活動内容が、数値とテキストの両方で確認できるデータとなっています。 EDINET-BENCHについて EDINET-BENCH は、Sakana AIが2025年6月9日に発表した、EDINETを用いた金融向けのベンチマークデータセットです。 EDINET-BENCHは、上記で説明したEDINETのデータと、3種類のラベル(不正会計・利益増減・業種)で作られています。 ※2 これらのラベルを予測するモデルを複数作り、どのモデルが良いかを横並びで比較できるベンチマークとなっています。 EDINET-BENCH内では、ロジスティック回帰や複数のLLMでの精度評価をしており、LLMにテキストデータも含めて予測させると、予測精度が上がったことが報告されています。 ※2 EDINET-BENCHでの予測方法 タスクの一つ、企業の利益増減予測を対象にした、LLMでの予測は、以下のプロンプトテンプレートで実行します。 Please predict whether the "親会社株主に帰属する当期純利益" (Net income attributable to owners of the parent) in the next fiscal year's securities report will increase compared to the current fiscal year, based on the information available in the current year's securities report. - The input is extracted from a Japanese company's securities report. - Some information may be missing and represented as "-" due to parsing errors. - Some attributes are missing and the total does not equal the sum of the parts. Respond in the following format: JSON: `json` { "prob": float (between 0 and 1, probability that the profit will increase), "prediction": int (0: Decrease, 1: Increase), "reasoning": "string", The current year's extracted securities report is as follows: {report_values} 来年の当期純利益が増加するか減少するかを、JSON形式で確率(0〜1)・予測結果(1:増加、0:減少)、理由を返すというプロンプトになっています。 コンテキストに{report_values}を渡せるようになっていて、これは具体的には以下のような形式です。 bs: {"現金及び預金": {"Prior1Year": "2090173000", "CurrentYear": "2045720000"}, "現金及び現金同等物": {"Prior2Year": "1330697000", "Prior1Year": "2090173000", "CurrentYear": "2045720000"}, cf: {"当期利益": {"Prior1Year": "217213000", "CurrentYear": "285490000"}, "税引前当期純利益": {"Prior1Year": "330891000", "CurrentYear": "399227000"}, "減価償却費及び償却費": {"Prior1Year": "9126000", "CurrentYear": "9933000"}, " pl: {"売上高": {"Prior1Year": "6146959000", "CurrentYear": "6233729000"}, "売上原価": {"Prior1Year": "4806251000", "CurrentYear": "4844096000"}, "売上総利益又は売上総損失(△)": {"Prior1Year": "1340708000", "CurrentYear": "1389632000"}, text: {“沿革”: “xxxxxxxxxxx”, “事業リスク”: “yyyyyyy”, "...": "....."} EDINET-BENCH内で指定した、PL(損益計算書)・BS(貸借対照表)・CF(キャッシュフロー計算書)・text(テキスト項目)を、それぞれキーが項目名、値が値のJSON形式で入力します。 これを実行すると、以下のようなresponseが得られます。 { "prob": 0.65, "prediction": 1, "reasoning": "To predict future profit trends, I'll analyze several key factors from the financial data:\n\n1. Revenue and profit trends:... } このprobやpredictionと、実際の増減のラベルを使うことで、ROC-AUCやAccuracyが計算できるようになっています。 LLMでの未来予測の検証における注意点 LLMを用いた予測を実施する際、注意すべき観点があります。 それは、「LLMにとって、既知な内容であるかもしれない」ということです。 LLMの学習では、Web上のテキストや電子書籍、ニュース記事など膨大なテキストを利用しています。 そのため、予測する内容によっては、LLMにとって既知であることがあり得ます。 例えば、EDINET-BENCHの利益増減予測で最高精度が確認されたClaude 3.7 Sonnetに、2022年のトヨタの利益は前年に比べて増加したか?を聞いてみましょう。 Q: トヨタの2022年の利益は前年に比べて増加しましたか? A: トヨタ自動車の2022年3月期(2021年4月〜2022年3月)の決算情報によると、 営業利益は前年比36.3%増の3兆円、純利益は26.9%増の2.85兆円となり、 前年と比較して増加しました。 今度は、2025年に変えて聞いてみます。 Q: トヨタの2025年の利益は前年に比べて増加しましたか? A: 申し訳ありませんが、2025年のトヨタの利益に関する情報は現時点では持っていません。 2025年はまだ到来していないため、その年の財務結果はまだ発表されていません。 トヨタの最新の財務情報や将来の予測については、トヨタの公式ウェブサイト、投資家向け情報、 または信頼できる経済ニュースソースで確認されることをお勧めします。 2022年に対しては「増加した」と回答しており、2025年に対しては「分からない」と回答しています。 これは、LLMの学習データの最終日付(ナレッジカットオフ日)が影響していると考えられます。 Claude-3.7-sonnetのナレッジカットオフ日は2024/10/31で、この日までにwebなどで公開されたテキストはLLM自体の学習に利用されていると考えられます。 よって、2022年の利益の増減は学習データに入っているため、LLMにとっては既知の内容で、回答ができます。 一方、2025年の利益の増減は学習データに入っていないため、LLMにとっては未知の内容で、回答ができないということです。 LLMを用いた未来の予測は、本来は知り得ない内容を予測したいのですが、バックテストでは上記の点を考慮しないと本当に計測したい精度が確認できない可能性があります。 これが、LLMを用いた未来の予測における注意点です。 LLMを用いたアウトオブサンプル評価 EDINET-BENCHは日本企業を対象とした、金融向けベンチマークとしてとても貴重な内容です。 しかし、目的はベンチマークの構築で、ナレッジカットオフを考慮した評価は報告されておりません。 そこで、EDINET-BENCHの利益増減予測をアウトオブサンプルで評価するとどうなるかを確認してみます。 以下が、アウトオブサンプル評価のイメージです。 利用するモデルは、Claude 3.7 Sonnetを用います。 ナレッジカットオフ日は2024/10/31です。 この日以前に公開された情報に対する予測をインサンプル評価、2025/6/1〜2025/8/31に公開された情報に対する予測をアウトオブサンプル評価とします。 そして、この2つの精度を比較することで、予測精度の汎化性能を確認しました。 インサンプルはEDINET-BENCHのテストデータで予測ができた447件、アウトオブサンプルは対象期間で予測ができた2212件です。 結果は以下です。 アウトオブサンプルにおいても、大幅な精度劣化は見られないという結果となりました。 予想に反し、アウトオブサンプルにて精度が上がっています。 ただし、こちらの比較は、以下の点であまり良い比較ができていないと思っております。 評価の対象数が5倍ほど異なる。 評価の対象年度がインサンプルは2020年〜2024年と幅があるが、アウトオブサンプルは2025年6月〜2025年8月の3ヶ月間のみ。 LLMの予測結果はブレがあり(完全再現せず)、本来は何回か実行してその平均を取るのが良いのですが、今回は予測実行は1度のみ。 これらのことから、精度が上がったというよりも、「大幅な精度劣化が見られなかった」という程度で受け止めています。 また、2024/10/31がカットオフ日であることも、厳密なアウトオブサンプル評価となっておりません。 例えば、2025年3月期決算の会社は、2024年9月に半期の結果なども公開しており、ここに通期予測などが比較的確度の高い情報として記載されているはずです。 これらのことから、今後は以下を考慮した評価をしたいと思っています。 インサンプルとアウトオブサンプルの期間/件数の統一 半期報告書などが提出されていない、本当の意味で1年後が未知な状態での比較 おわりに 今回は、LLMを用いた1年後の利益増減予測のアウトオブサンプル評価を紹介しました。 LLMでの未来予測は、様々な利用シーンが考えられます。 その際に、予測精度を検証する場合は、ナレッジカットオフに注意しないと検証時と運用時で異なる精度となってしまうことが想定されます。 本記事が、誰かの参考になると幸いです。 出典 ※1 : 金融庁EDINET ※2 : Sakana AI「EDINET-BENCH」
こんにちは、Insight Edgeの小林まさみつです。本記事は Insight Edge Advent Calendar 2025 の8日目の記事です。 最近は生成AIをソフトウェア領域に応用した開発をしていますが、今回は趣向を変えてハードウェアと組み合わせたシステムを作成してみたので紹介します。 目次 1. はじめに 1.1 なぜ作ったのか 1.2 完成システムの紹介 1.3 この記事で分かること 2. システム概要 2.1 全体構成図 2.2 使用技術スタック 2.3 動作の流れ 3. ハードウェア編:振動モーター制御回路 3.1 必要な部品リスト 3.2 回路図と配線 3.3 動作確認とコード 4. ソフトウェア編:姿勢判定システム 4.1 カメラ設置とPythonでの画像取得 4.2 生成AI(Bedrock Claude Sonnet 4)との連携 4.3 Arduino との通信(シリアル通信) 4.4 統合プログラム 5. 実際に使ってみて 5.1 効果を実感した点 5.2 振動の強さについて 5.3 生成AIの判定精度 6. 今後の展望 7. まとめ 1. はじめに 1.1 なぜ作ったのか 長時間のデスクワークで肩こりや腰痛に悩まされ、「姿勢を良くしなければ」と思いつつも、集中していると姿勢のことなど忘れてしまいます。 市販の姿勢矯正グッズも検討しましたが、高価なものが多く、効果も不透明。そこで「技術で解決できるのでは?」と考え、自作することにしました。 当初はエアバッグで物理的に姿勢を矯正する構想もありましたが、コストと複雑さを考慮し、 振動で姿勢の悪化を通知する シンプルなシステムに方針転換しました。 従来の画像認識ライブラリでは、照明条件や服装の変化で精度が不安定になりがちです。一方、生成AIであれば自然言語で評価基準を定義でき、より柔軟な判定が期待できます。そこで 生成AIで姿勢を評価し、振動で身体に直接フィードバックする 実験的なシステムを構築しました。 1.2 完成システムの紹介 システムは以下の3つの要素で構成されています: Webカメラ : 横から姿勢を撮影 PC : 生成AIで姿勢評価 Arduino+振動モーター : 姿勢の悪い箇所をユーザーに通知 Arduino とは、電子工作を簡単に始められる小型のコンピューター基板(=マイコン)です。 姿勢が悪くなると、該当する部位が振動して通知されます。例えば猫背なら腰が、左に傾いていれば左太ももが振動する仕組みです。 動作イメージは以下の通りです。 Webカメラが5分ごとにユーザーの姿勢を撮影 左の画像は良い姿勢、右は悪い姿勢の例です。左足が浮いており、腰も前傾していることがわかります。右側のように姿勢が悪くなったタイミングで振動モーターを動作させます。 結果に応じてArduinoに指示を送り、振動モーターを動作 結果に応じて、左足・右足・腰のいずれか、または複数が振動して姿勢の悪化を通知します。これを人の足にテープなどで固定して使用します。 1.3 この記事で分かること 本記事では、以下の内容を解説します: 生成AI活用 : AWS Bedrock Claude Sonnet 4による姿勢評価の実装方法 ハードウェア制御 : Arduino + 振動モーターの制御回路の設計と配線 システム統合 : Webカメラ ⟷ PC ⟷ Arduino間の通信の実装 2. システム概要 2.1 全体構成図 システムの全体像は以下の通りです。 カメラで撮影した画像をPCで取得し、生成AIに送信して姿勢を評価。その結果に基づいてArduinoに指示を送り、適切な振動モーターを動作させます。 各コンポーネントは疎結合で、それぞれ独立してテスト・改善できる構成になっています。 2.2 使用技術スタック システム全体で使用した技術は以下の通りです。 レイヤー 技術 用途 言語 Python 3.13 システム全体の制御 AI推論 AWS Bedrock Claude Sonnet 4 姿勢評価 画像取得 OpenCV (Python) カメラ制御 通信 pySerial PC-Arduino間 マイコン Arduino Uno モーター制御 また、利用したツールは以下の通りです。 ツール 用途 Arduino IDE Arduinoコード開発/マイコンへの書き込み Tinkercad 回路設計とシミュレーション Webカメラ こちら を参考にiPhoneをWebカメラ化しました 2.3 動作の流れ システムの動作フローは以下の通りです: 画像取得 :Webカメラが5分ごとにユーザーの姿勢を撮影する AI評価 : PythonでAWS Bedrock(Claude Sonnet 4)に画像を送信し、姿勢を評価 コマンド送信 :評価結果に応じてシリアル通信でArduinoに指示 各部位のスコアをもとに、振動すべき箇所を3ビットで表現する 左足・右足・腰の順番に0/1とし、問題がある箇所を 1 に設定 例: 000 (全て良好)、 100 (左足が悪い)、 111 (全て悪い) モーター制御 :Arduinoが該当する振動モーターを動作させる ループ :1に戻り、継続的に監視 評価頻度は5分に1回としました。 当初は30秒ごとの評価も検討しましたが、以下の理由で5分間隔を選択しました。 APIコストの削減 姿勢改善には継続的な意識づけが重要で、頻繁すぎる通知は逆効果になるため 3. ハードウェア編:振動モーター制御回路 3.1 必要な部品リスト 回路の構築に用いた部品は以下の通りです。 部品名 型番・仕様 個数 用途 マイコン Arduino Uno 1 モーター制御 振動モーター FM34E 3 触覚フィードバック トランジスタ DTC143EL 3 スイッチング 抵抗 1kΩ 3 ベース電流制限 ダイオード 1N4007 3 逆起電力保護 ブレッドボード 標準サイズ 1 配線用 ジャンパーワイヤー オス-オス 11本 回路の接続用 ジャンパーワイヤー オス-メス 6本 振動モーター接続用 ジャンパーワイヤー (必要に応じて) オス-メス 任意の本数 振動モーター延長用 振動モーターを延長する場合は、オス-メスのジャンパーワイヤーを追加で24本程度用意することを推奨します。 3.2 回路図と配線 回路図の作り方 回路設計の経験が無かったため、生成AIに回路図の作成を依頼しました。 出力された回路図をもとに、 Tinkercad でシミュレーションと配線図の作成をしました。 初心者でも簡単に始められ、無料で利用できるため非常に便利です。 基本回路(1個のモーター) まず、1個の振動モーターを制御する基本回路を説明します。 回路の動作原理: Arduinoのデジタルピン(D3)から信号を出力し、抵抗を経由してトランジスタのベースに接続します。トランジスタはスイッチとして機能し、ベースに電圧がかかるとコレクタ-エミッタ間が導通し、モーターに電流が流れます。 ダイオードはモーターと並列に逆向きで接続され、モーター停止時の逆起電力を吸収します。これにより、Arduinoや他の回路が逆電圧で破損することを防ぎます。 PWM制御の必要性: 使用する振動モーターの定格は3Vですが、Arduinoの出力は5Vです。そのまま接続すると過電圧になるため、PWM(Pulse Width Modulation)制御を使って実効電力を調整します。具体的には、 analogWrite(pin, 153) とすることで、約60%のデューティサイクル(153/255)で動作し、平均電圧を約3Vに下げることができます。 完全な配線(3個のモーター) 次に、3個のモーターを制御する完全な回路です。 配線した回路 配線時の重要な注意点: トランジスタの向き :平らな面を手前に向けて挿入します。左からエミッタ(E)、コレクタ(C)、ベース(B)の順です。 ダイオードの向き :銀色の帯のある方がカソード(+側)で、モーターのプラス側に接続します。逆に接続すると短絡の原因になります。 PWM対応ピンの使用 :Arduino Unoでは、D3/D5/D6/D9/D10/D11がPWM対応です。今回はD3/D5/D6を使用しています。 3.3 動作確認とコード Arduino IDEのシリアルモニタから手動でコマンドを送信して、動作を確認できます。 Arduinoコード // ピン定義 const int LEFT_LEG_PIN = 3; // 左脚用振動モーター const int RIGHT_LEG_PIN = 5; // 右脚用振動モーター const int WAIST_PIN = 6; // 腰用振動モーター // 振動設定 const int VIBRATION_DURATION = 5000; // 振動時間(ミリ秒) const int VIBRATION_INTENSITY = 153; // PWM値(0-255) 5V電源をモーターに約3Vで供給するため153に設定 String receivedData = ""; bool dataComplete = false; void setup() { // シリアル通信を開始(9600 baud) Serial.begin(9600); // ピンを出力モードに設定 pinMode(LEFT_LEG_PIN, OUTPUT); pinMode(RIGHT_LEG_PIN, OUTPUT); pinMode(WAIST_PIN, OUTPUT); // 初期状態:全モーター停止 analogWrite(LEFT_LEG_PIN, 0); analogWrite(RIGHT_LEG_PIN, 0); analogWrite(WAIST_PIN, 0); // 起動確認用フラッシュ startupFlash(); } void loop() { // シリアルデータが利用可能かチェック if (Serial.available()) { String receivedString = Serial.readString(); if (receivedString.length() > 0) { receivedData = receivedString; dataComplete = true; } } // データ受信完了時の処理 if (dataComplete) { processPostureFeedback(receivedData); receivedData = ""; dataComplete = false; } } void processPostureFeedback(String data) { // 3桁のバイナリ文字列を期待(例:"101") if (data.length() != 3) { Serial.println("Error: Invalid data format. Expected 3 digits."); return; } // 各桁をチェックして対応する振動を制御 bool leftLegVibrate = (data.charAt(0) == '1'); bool rightLegVibrate = (data.charAt(1) == '1'); bool waistVibrate = (data.charAt(2) == '1'); // 振動パターンを実行 executeVibrationPattern(leftLegVibrate, rightLegVibrate, waistVibrate); } void executeVibrationPattern(bool leftLeg, bool rightLeg, bool waist) { // 全モーター停止 stopAllMotors(); // 振動が必要な部位があるかチェック if (!leftLeg && !rightLeg && !waist) { return; } // 対象部位を振動 if (leftLeg) { analogWrite(LEFT_LEG_PIN, VIBRATION_INTENSITY); } if (rightLeg) { analogWrite(RIGHT_LEG_PIN, VIBRATION_INTENSITY); } if (waist) { analogWrite(WAIST_PIN, VIBRATION_INTENSITY); } // 振動時間待機 delay(VIBRATION_DURATION); // 全モーター停止 stopAllMotors(); } void stopAllMotors() { analogWrite(LEFT_LEG_PIN, 0); analogWrite(RIGHT_LEG_PIN, 0); analogWrite(WAIST_PIN, 0); } void startupFlash() { // 起動時に全モーターを短時間点灯してテスト Serial.println("System test - All motors flash"); for (int i = 0; i < 3; i++) { analogWrite(LEFT_LEG_PIN, VIBRATION_INTENSITY); analogWrite(RIGHT_LEG_PIN, VIBRATION_INTENSITY); analogWrite(WAIST_PIN, VIBRATION_INTENSITY); delay(200); stopAllMotors(); delay(200); } Serial.println("System test completed"); } 4. ソフトウェア編:姿勢判定システム 4.1 カメラ設置とPythonでの画像取得 前提:姿勢を表すためのモデル定義 以下のPydanticモデルを用いて、生成AIからの姿勢評価を受け取りArduinoに送信する形式へ変換します。 姿勢評価のPydanticモデルコード from pydantic import BaseModel, Field POSTURE_THRESHOLD = 0.7 # 姿勢スコアの閾値 class FeedbackModel (BaseModel): left_leg_score: float = Field(..., description= "左脚の姿勢スコア" , ge= 0.0 , le= 1.0 ) right_leg_score: float = Field(..., description= "右脚の姿勢スコア" , ge= 0.0 , le= 1.0 ) waist_score: float = Field(..., description= "腰の姿勢スコア" , ge= 0.0 , le= 1.0 ) remarks: str = Field( "" , description= "スコア評価の備考" ) def to_should_vibrate (self) -> str : """ 振動フィードバックが必要かどうかを判定 3桁の0, 1の組み合わせで返す。 左足・右足・腰の順番 例: - 000: 全て良好 - 100: 左足が悪い - 111: 全て悪い """ res = "" res += "1" if self.left_leg_score < POSTURE_THRESHOLD else "0" res += "1" if self.right_leg_score < POSTURE_THRESHOLD else "0" res += "1" if self.waist_score < POSTURE_THRESHOLD else "0" return res Pythonでの画像取得 OpenCVを使ってカメラから画像を取得します。 画像取得のコード import cv2 from datetime import datetime # Webカメラのキャプチャを開始 cap = cv2.VideoCapture( 0 ) # キャプチャがオープンしている間続ける while (cap.isOpened()): ret, frame = cap.read() if ret: # カメラ映像を表示 cv2.imshow( 'Webcam Live' , frame) # 's'キーが押されたらスクリーンショットを保存 if cv2.waitKey( 1 ) & 0xFF == ord ( 's' ): timestamp = datetime.now().strftime( "%Y%m%d_%H%M%S" ) filename = f "screenshot/screenshot_{timestamp}.png" cv2.imwrite(filename, frame) # 'q'キーが押されたらループから抜ける if cv2.waitKey( 1 ) & 0xFF == ord ( 'q' ): break else : break # キャプチャをリリースし、ウィンドウを閉じる cap.release() cv2.destroyAllWindows() ポイント: cv2.VideoCapture(0) の引数は環境によって異なります。複数のカメラが接続されている場合は、番号を変えて試してください。 動作確認用のため、 's' キーでスクリーンショットを保存するようにしています。最終的には5分ごとに自動で保存するロジックを追加します。 4.2 生成AI(Bedrock Claude Sonnet 4)との連携 AWS Bedrockのセットアップ AWS Bedrockを使用するには、事前にAWSアカウントとCLIの設定が必要です。 必要な準備: 1. AWSアカウントの作成 2. IAMユーザーの作成(Bedrock実行権限付与) 3. AWS CLIのインストールと設定( aws configure ) 4. Bedrock APIへのアクセス権限の確認(リージョンは ap-northeast-1 など) 姿勢評価の実装 Claude Sonnet 4に画像を送信して姿勢を評価するクラスを実装します。 Claudeを呼び出し姿勢を評価するコード import json import boto3 from model import FeedbackModel MODEL_ID = "" # Bedrockで使用するモデルIDを指定してください。 class BedrockClient (): def __init__ (self): self.client = boto3.client( 'bedrock-runtime' ) # 姿勢評価 def evaluate_posture (self, img_bytes: bytes ): system_prompt = ( "ユーザーから提供されるbase64エンコードされた画像を解析し、姿勢評価を行ってください。 \n " "画像横から撮影した人が映っています。座り姿勢が良いか悪いかを判定し、以下のJSON形式で返してください。 \n\n " "1. left_leg_score: 左脚の姿勢スコア(0-1のfloat、1が最良) \n " "2. right_leg_score: 右脚の姿勢スコア(0-1のfloat、1が最良) \n " "3. waist_score: 腰の姿勢スコア(0-1のfloat、1が最良) \n\n " "JSON形式の例: {{ \" left_leg_score \" : 0.8, \" right_leg_score \" : 0.9, \" waist_score \" : 0.7}}" ) messages = [{ "role" : "user" , "content" : [ { "image" : { "format" : "png" , "source" : { "bytes" : img_bytes } } } ] }] response = self.client.converse( modelId=MODEL_ID, system=[{ "text" : system_prompt}], inferenceConfig={ "temperature" : 0 }, messages=messages, toolConfig={ "tools" : [ { "toolSpec" : { "name" : "extract_Feedback_Model" , "description" : "inputSchemaに沿ってFeedbackというPydanticモデルを出力するツール" , "inputSchema" : { "json" : FeedbackModel.model_json_schema()} } } ], "toolChoice" : { "tool" : { "name" : "extract_Feedback_Model" } } } ) # Bedrockのレスポンスから姿勢評価結果のJSONを抽出し、FeedbackModelに変換して返す tool_res = response[ "output" ][ "message" ][ "content" ][ 0 ][ "toolUse" ][ "input" ] if isinstance (tool_res, str ): tool_res = json.loads(tool_res) return FeedbackModel(**tool_res) ポイント ユーザーから提供される画像を読み取り、バイト列としてClaude Sonnet 4に送信します。 BedrockにおけるClaudeはtoolConfigを利用することで、Pydanticモデルを用いたJSONレスポンスを受け取ることができます。一方、必ず意図した形式で返ってくるとは限らないため、必要に応じてエラーハンドリングを追加してください。 4.3 Arduino との通信(シリアル通信) シリアル通信の基礎 PythonからArduinoに指示を送るには、シリアル通信を使用します。pySerialライブラリをインストールする必要があります。 Arduinoコントローラーの実装 姿勢評価結果に基づいてArduinoに適切なコマンドを送信するクラスを作成します。 Arduinoコントローラーのコード import serial from model import FeedbackModel def run_vibration_feedback (arduino: serial.Serial, feedback: FeedbackModel) -> None : """ Arduinoを使用して振動フィードバックを提供する関数 """ # 1. フィードバックを解析 should_vibrate = feedback.to_should_vibrate() # 2. フィードバック信号をArduinoに送信 arduino.write( bytes (should_vibrate, encoding= "ascii" )) # 生成AIからのフィードバックをモック化 feedback = FeedbackModel( left_leg_score= 0.4 , right_leg_score= 0.6 , waist_score= 0.7 , remarks= "Test feedback" ) # Arduinoのシリアルポートとボーレートを設定。環境に応じて適切に変更してください。 ARDUINO_PORT = '/dev/cu.usbmodem114301' ARDUINO_BAUDRATE = 9600 # Arduinoに接続する arduino = serial.Serial(ARDUINO_PORT, ARDUINO_BAUDRATE) # 振動フィードバックを実行 run_vibration_feedback(arduino, feedback) # 接続を閉じる arduino.close() ポイント 1. 振動フィードバックの実行には、Arduinoが接続されている必要があります。 2. Arduinoのシリアルポートやボーレートは環境によって異なります。後述の確認方法を参考に適切に設定してください。 COMポートの確認方法: Windows : デバイスマネージャーで「ポート(COMとLPT)」を確認。 COM3 , COM4 など。 Mac : ターミナルで ls /dev/cu.* を実行。 /dev/cu.usbserial-XXXX など。 Linux : ターミナルで ls /dev/ttyUSB* または ls /dev/ttyACM* を実行。 Arduino IDEのシリアルモニタを開いている場合は、ポートが占有されているため、Pythonから接続できません。必ず閉じてから実行してください。また、ポート番号はArduino IDEで確認すると便利です。 4.4 統合プログラム すべてのコンポーネントを統合し、システム全体を動作させるメインプログラムです。 コード全文 main.py # main.py from datetime import datetime import cv2 import time import serial from bedrock import BedrockClient from model import FeedbackModel # Arduinoのシリアルポートとボーレートを設定。環境に応じて適切に変更してください。 ARDUINO_PORT = '/dev/cu.usbmodem114301' ARDUINO_BAUDRATE = 9600 interval_sec = 300 def get_image (frame) -> bytes : """OpenCVのフレームを画像バイトに変換して返す""" timestamp = datetime.now().strftime( "%Y%m%d_%H%M%S" ) filename = f "screenshot/screenshot_{timestamp}.png" # スクリーンショットを保存 cv2.imwrite(filename, frame) res: bytes = b "" # 画像をバイトに変換 with open (filename, "rb" ) as img_file: res = img_file.read() return res def run_capture_analysis (frame): # -> Feedback: """ OpenCVでキャプチャしたフレームを元に姿勢分析を行う関数 """ # 1. frameをbyteで送信 img_bytes = get_image(frame) # 2. Bedrockに送信して姿勢推定 bedrock_client = BedrockClient() return bedrock_client.evaluate_posture(img_bytes) def run_vibration_feedback (arduino: serial.Serial, feedback: FeedbackModel) -> None : """ Arduinoを使用して振動フィードバックを提供する関数 """ # 1. フィードバックを解析 should_vibrate = feedback.to_should_vibrate() # 2. フィードバック信号を送信 arduino.write( bytes (should_vibrate, encoding= "ascii" )) def main (): # ウェブカメラのキャプチャを開始 cap = cv2.VideoCapture( 0 ) arduino = serial.Serial(ARDUINO_PORT, ARDUINO_BAUDRATE) # キャプチャがオープンしている間続ける while (cap.isOpened()): time.sleep(interval_sec) ret, frame = cap.read() if not ret: break # デバッグ用にフレームを表示 cv2.imshow( 'Webcam Live' , frame) feedback = run_capture_analysis(frame) run_vibration_feedback(arduino, feedback) # 'q'キーが押されたらループから抜ける if cv2.waitKey( 1 ) & 0xFF == ord ( 'q' ): break # キャプチャをリリースし、ウィンドウを閉じる cap.release() cv2.destroyAllWindows() arduino.close() if __name__ == "__main__" : main() bedrock.py # bedrock.py import json import boto3 from model import FeedbackModel MODEL_ID = "" # Bedrockで使用するモデルIDを指定 class BedrockClient (): def __init__ (self): self.client = boto3.client( 'bedrock-runtime' ) # 姿勢評価 def evaluate_posture (self, img_bytes: bytes ): system_prompt = ( "ユーザーから提供されるbase64エンコードされた画像を解析し、姿勢評価を行ってください。 \n " "画像横から撮影した人が映っています。座り姿勢が良いか悪いかを判定し、以下のJSON形式で返してください。 \n\n " "1. left_leg_score: 左脚の姿勢スコア(0-1のfloat、1が最良) \n " "2. right_leg_score: 右脚の姿勢スコア(0-1のfloat、1が最良) \n " "3. waist_score: 腰の姿勢スコア(0-1のfloat、1が最良) \n\n " "JSON形式の例: {{ \" left_leg_score \" : 0.8, \" right_leg_score \" : 0.9, \" waist_score \" : 0.7}}" ) messages = [{ "role" : "user" , "content" : [ { "image" : { "format" : "png" , "source" : { "bytes" : img_bytes } } } ] }] response = self.client.converse( modelId=MODEL_ID, system=[{ "text" : system_prompt}], inferenceConfig={ "temperature" : 0 }, messages=messages, toolConfig={ "tools" : [ { "toolSpec" : { "name" : "extract_Feedback_Model" , "description" : "inputSchemaに沿ってFeedbackというPydanticモデルを出力するツール" , "inputSchema" : { "json" : FeedbackModel.model_json_schema()} } } ], "toolChoice" : { "tool" : { "name" : "extract_Feedback_Model" } } } ) # Bedrockのレスポンスから姿勢評価結果のJSONを抽出し、FeedbackModelに変換して返す tool_res = response[ "output" ][ "message" ][ "content" ][ 0 ][ "toolUse" ][ "input" ] if isinstance (tool_res, str ): tool_res = json.loads(tool_res) return FeedbackModel(**tool_res) model.py # model.py from pydantic import BaseModel, Field POSTURE_THRESHOLD = 0.7 # 姿勢スコアの閾値 class FeedbackModel (BaseModel): left_leg_score: float = Field(..., description= "左脚の姿勢スコア" , ge= 0.0 , le= 1.0 ) right_leg_score: float = Field(..., description= "右脚の姿勢スコア" , ge= 0.0 , le= 1.0 ) waist_score: float = Field(..., description= "腰の姿勢スコア" , ge= 0.0 , le= 1.0 ) remarks: str = Field( "" , description= "スコア評価の備考" ) def to_should_vibrate (self) -> str : """ 振動フィードバックが必要かどうかを判定 3桁の0, 1の組み合わせで返す。 左足・右足・腰の順番 例: - 000: 全て良好 - 100: 左足が悪い - 111: 全て悪い """ res = "" res += "1" if self.left_leg_score < POSTURE_THRESHOLD else "0" res += "1" if self.right_leg_score < POSTURE_THRESHOLD else "0" res += "1" if self.waist_score < POSTURE_THRESHOLD else "0" return res motor.ino // ピン定義 const int LEFT_LEG_PIN = 3 ; // 左脚用振動モーター const int RIGHT_LEG_PIN = 5 ; // 右脚用振動モーター const int WAIST_PIN = 6 ; // 腰用振動モーター // 振動設定 const int VIBRATION_DURATION = 5000 ; // 振動時間(ミリ秒) const int VIBRATION_INTENSITY = 153 ; // PWM値(0-255) String receivedData = "" ; bool dataComplete = false ; void setup () { // シリアル通信を開始(9600 baud) Serial. begin ( 9600 ); // ピンを出力モードに設定 pinMode (LEFT_LEG_PIN, OUTPUT); pinMode (RIGHT_LEG_PIN, OUTPUT); pinMode (WAIST_PIN, OUTPUT); // 初期状態:全モーター停止 analogWrite (LEFT_LEG_PIN, 0 ); analogWrite (RIGHT_LEG_PIN, 0 ); analogWrite (WAIST_PIN, 0 ); // 起動確認用フラッシュ startupFlash (); } void loop () { // シリアルデータが利用可能かチェック if (Serial. available ()) { String receivedString = Serial. readString (); if (receivedString. length () > 0 ) { receivedData = receivedString; dataComplete = true ; } } // データ受信完了時の処理 if (dataComplete) { processPostureFeedback (receivedData); receivedData = "" ; dataComplete = false ; } } void processPostureFeedback (String data) { // 3桁のバイナリ文字列を期待(例:"101") if (data. length () != 3 ) { Serial. println ( "Error: Invalid data format. Expected 3 digits." ); return ; } // 各桁をチェックして対応する振動を制御 bool leftLegVibrate = (data. charAt ( 0 ) == '1' ); bool rightLegVibrate = (data. charAt ( 1 ) == '1' ); bool waistVibrate = (data. charAt ( 2 ) == '1' ); // 振動パターンを実行 executeVibrationPattern (leftLegVibrate, rightLegVibrate, waistVibrate); } void executeVibrationPattern ( bool leftLeg, bool rightLeg, bool waist) { // 全モーター停止 stopAllMotors (); // 振動が必要な部位があるかチェック if (!leftLeg && !rightLeg && !waist) { return ; } // 対象部位を振動 if (leftLeg) { analogWrite (LEFT_LEG_PIN, VIBRATION_INTENSITY); } if (rightLeg) { analogWrite (RIGHT_LEG_PIN, VIBRATION_INTENSITY); } if (waist) { analogWrite (WAIST_PIN, VIBRATION_INTENSITY); } // 振動時間待機 delay (VIBRATION_DURATION); // 全モーター停止 stopAllMotors (); } void stopAllMotors () { analogWrite (LEFT_LEG_PIN, 0 ); analogWrite (RIGHT_LEG_PIN, 0 ); analogWrite (WAIST_PIN, 0 ); } void startupFlash () { // 起動時に全モーターを短時間点灯してテスト Serial. println ( "System test - All motors flash" ); for ( int i = 0 ; i < 3 ; i++) { analogWrite (LEFT_LEG_PIN, VIBRATION_INTENSITY); analogWrite (RIGHT_LEG_PIN, VIBRATION_INTENSITY); analogWrite (WAIST_PIN, VIBRATION_INTENSITY); delay ( 200 ); stopAllMotors (); delay ( 200 ); } Serial. println ( "System test completed" ); } プログラムの構成: 初期化フェーズ :カメラとArduinoを初期化 メインループ : 5分待機 カメラから画像を取得 AIで姿勢評価 Arduinoにコマンド送信 終了処理 :終了時リソースを適切に解放 5. 実際に使ってみて システムを1週間使用した結果、以下のような効果と課題が見えてきました。 5.1 効果を実感した点 最初はほぼ毎回振動させられ、そのたびに「ハッとして」姿勢を直していました。振動というフィードバックは、視覚や聴覚と比べて邪魔にならず、作業を中断させない点が良かったです。 4日目くらいから、少しずつ姿勢が改善され、振動される頻度が減ってきました。座り姿勢を意識する習慣がついたように感じます。 5.2 振動の強さについて 使用した3V振動モーターは、服の上からでもはっきりと分かるため、通知としての役割は十分果たせています。 一方で、長時間使用すると慣れてしまい、振動に対する感度が下がる傾向も見られました。振動パターンをランダム化する、または強弱をつけるなどの工夫が必要かもしれません。 5.3 生成AIの判定精度 Claude Sonnet 4の姿勢評価は想像以上に精密で、「確かにその通り」と思える判定が多くありました。 特に、微妙な体の傾きや脚の位置なども的確に指摘してくれるため、自分では気づかない姿勢の問題を発見できました。 6. 今後の展望 このシステムは実験的なプロトタイプですが、以下のような発展性があります。 複数部位への拡張 : 現在は3箇所ですが、首、肩、背中など5〜7箇所に増やすことで、より細かい姿勢の矯正が可能になります。 モーターの種類変更 : 振動モーター以外に、エアポンプとエアバッグを利用することで良い姿勢に矯正させることができます。 バッテリー駆動化 : USB給電からバッテリー駆動に変更し、ケーブルレスで使用できるようにできます。 同様の「自然言語での評価基準定義 + 物理フィードバック」のパターンは、評価が主観的になりがちな他の分野でも参考になるかもしれません。 7. まとめ 本記事を書くにあたり、大学以来久しぶりに電子工作をしました。生成AIを活用して回路図を作成し、シミュレータで動作確認をすることで、初学者でも簡単に取り組めました。ソフトウェアアプリケーションの領域だけでなく、ハードウェア制御の分野にも生成AIを活用できることを実感し、自身で取り組める範囲が広がったと感じています。本記事が、同様の課題を持つ方々の参考になれば幸いです。
はじめまして!Data Scientistの白井と市川です。 今回は、先日第35回 人工知能学会 金融情報学研究会(SIG-FIN) に行ってきましたので、そのレポートをさせて頂ければと思います。 イベントの概要 発表の概要 人工市場(4件) (01) 人工市場を用いた取引単位の違いが裁定取引に与える影響の分析 (03) 人工市場を用いた決済期間が異なる市場間での裁定取引が各市場に与える影響の分析 (04) 人工市場を用いたサーキットブレーカーの性能調査 投資戦略(4件) (05) 米国経済指標の集団的変動と産業セクター間の関係性の分析 (06) 多資産ネットワーク分析が示す暗号資産の独立性とポートフォリオ分散効果 (07) 長期相関を持つ成行注文流と価格インパクトのミクロモデル化に基づく株価の予測困難性の説明 (08) 戦略多様性と平方根則を取り入れた一般化LMFモデル テキストマイニング(5件) (09) 金融テキストごとの特徴分析とポートフォリオ評価 (10) 有価証券報告書テキストを用いた配当政策データの構築と分析 (11) LLMsによる利益予測の分析とアウトオブサンプル評価 (12) 適時開示テキスト埋め込みを用いたイベントスタディにおける累積異常リターンの予測 (13) 大規模言語モデルを用いたアンサンブル手法による J-REIT物件情報データセットの効率的な構築方法 データマイニング(4件) (14) トランザクションレンディングにおける法人のデフォルト分析 (15) Fiedlerベクトルと情報エントロピーを用いた株式ネットワークの構造変化検知 (16) 暗黙の政府保証を加味した国内地方債スプレッドの評価 (17) 本邦中古スマートフォン市場における価格形成に対する機種ブランドと為替レートの影響 機械学習(5件) (18) 事前エクスポージャー情報を活用した部分空間正則化付き主成分分析 (19) 財務諸表監査のための逐次検定:試査手続の統計学的な定式化と理論保証 (20) 学習期間が異なる株価予測機械学習モデルのアンサンブル学習による投資戦略の構築 (21) マルチモーダルデータを用いた機械学習モデルによる企業の業績修正予測 (22) 生成AIを用いた決算説明サプライズの定量化手法の提案 機械学習/テキストマイニング(4件) (23) 指値配分を連続確率分布化した深層学習によるマーケットメイキング (24) 3値ポートフォリオ最適化に対するQAOAミキサーの性能比較 (26) LLM-PEAD.txt:日本株式市場におけるLLMを用いたサプライズ抽出と決算後ドリフトの実証分析 (27) 有価証券報告書のサステナビリティ記述に関する分類および体系化 雑感 この記事は、 Insight Edge Advent Calendar 2025 7日目の記事です。 イベントの概要 人工知能学会 金融情報学研究会(SIG-FIN) は人工知能学会の第二種研究会です。 詳細は上記リンクに譲るのですが、近年より広い方々の金融市場への関心が高まっています。このような背景で、ファイナンス分野への人工知能技術の応用を促進するための研究会になります。人工知能分野の研究者や金融市場の現場の技術者が参加する、大変ユニークな研究会になっています。 余談ですが、今回も別業界の方がいらっしゃるなど、なかなか出会えない方とお話しすることができました。 最近、かなり発表量が増加傾向にあり、聴いているだけでも忙しい研究会です。例年、土曜日の1日のみの開催でしたが、発表数の増加に伴い、土日を両方使う研究会となりました。 概要は以下の通りです。 * 日時:2025年10月11日(土) および 10月12日(日) * 開催形式:会場およびオンライン(Zoom使用)のハイブリッド開催 * 会場:慶應義塾大学日吉キャンパス 来往舎1階シンポジウムスペース 第35回研究会 発表の概要 こちらの研究会はありがたいことに 各発表の概要pdfが公開されています 。 以下、著者の敬称略とさせて頂きます。 人工市場(4件) (01) 人工市場を用いた取引単位の違いが裁定取引に与える影響の分析 則武 誉人 (三井住友DSアセットマネジメント), 八木 勲 (工学院大学), 水田 孝信 (スパークス・アセット・マネジメント) 取引単位が異なる2つの先物市場間で行われる裁定取引について、小さい単位で取引できるミニ先物の取引単位の大きさが、裁定取引の発生にどのような影響を与えるかを、人工市場モデルを用いて分析した研究です。 研究の背景と目的 現実の金融市場では、同じ資産を対象としながら取引単位の異なる複数の先物市場が存在します。これら市場間で価格差が生じた際に、裁定取引が行われることが指摘されていますが、取引単位の小口化が裁定取引に与える具体的な影響は十分に解明されていません。これは、現実の市場では裁定取引の観測が困難であることや、多くの外部要因が価格に影響を与えるため、取引単位の違いだけを分離して分析することが難しいことが理由です。そこで本研究では、マルチエージェント型の人工市場を構築し、ミニ先物の取引単位を変化させることで、その影響を明らかにすることを目的としています。 分析モデル この研究では、取引単位が大きい「ラージ市場」と、小さい「ミニ市場」の2つの市場をモデル化しています。それぞれの市場には注文を出す「標準エージェント」が存在し、さらに両市場間で裁定取引を行う「裁定エージェント」を1体設定しています。 裁定エージェントは、以下の2つの要件が満たされた場合にのみ取引を実行します。 価格要件: 一方の市場の最良買い気配価格が、他方の市場の最良売り気配価格を上回る。 利益要件: 実際に売買を行った際に、売却代金が買い付け代金を上回る。 → 成行注文で成立することのみを考えている ラージ市場の取引単位を1に固定し、ミニ市場の取引単位( )を9段階に変化させてシミュレーションを行いました。 分析結果 シミュレーションの結果、以下の点が明らかになりました。 取引単位の小口化と発注行動の変化: ミニ市場の取引単位( )が小さくなると、価格要件は満たしても、ミニ市場側の最良気配の注文数量がラージ市場の取引単位に満たないため、利益要件を満たさずに裁定エージェントが発注を見送るケースが増加しました。図1は、 の例で、ラージ市場の最良買い気配(価格104)がミニ市場の最良売り気配(価格103)を上回っていますが、ラージ市場の取引単位1に合わせてミニ市場で買い付けると、次の価格帯(105)からも調達する必要があり、結果的に損失が出るため利益要件が成立しない状況を示しています。 利益要件を満たす機会の増加: 一方で、 の小口化はミニ市場のビッドアスクスプレッド(売値と買値の差)を縮小させる効果がありました。これにより、利益要件を満たす機会が相対的に増加し、結果として裁定取引の発注割合は の小口化とともに緩やかに増加し、一定の水準で収束することが示されました。具体的には、 の場合と比較して、 が小さくなるほど、価格要件と利益要件の両方を満たして発注される数量の割合が増加する傾向が見られました。 結論 取引単位の小口化が裁定取引に与える影響を明らかにしました。取引単位が小さくなると、最良気配の数量不足から裁定取引が見送られるケースが増える一方で、ビッドアスクスプレッドの縮小により利益機会が増え、全体としての裁定取引の割合はわずかに増加して安定することが示されました。この結果は、取引単位という制度設計が市場間の裁定取引の量に影響を与えることを示唆するもの。 (03) 人工市場を用いた決済期間が異なる市場間での裁定取引が各市場に与える影響の分析 著者:福家 緋莉(工学院大学)、水田 孝信(スパークス・アセット・マネジメント)、八木 勲(工学院大学) 目的/背景 T+N(遅延決済)と即時決済が併存する環境では、両市場間の裁定には株式借入が必要となるパターンがあり、貸株コストや供給制約があると裁定が働きにくい。 そこで、人工市場で外生要因を排し、借株コストが価格水準・流動性・裁定取引回数に与える影響を定量評価する。 アプローチ - 即時決済市場とT+N市場の2市場を仮定し、各市場にエージェントでの売買を発生させて、連続ダブルオークションでシミュレーション環境を構築。 - 各市場に一般投資家エージェントをそれぞれ1000体配置し、市場横断の裁定エージェントを配置してシミュ - 裁定パターン(6種): - i. 即時決済市場で買い、T+N市場で売りが発生。 - ii. T+N市場で買い、即時決済市場で売り(借株コストC: 価格×係数α)。 - iii/iv T+N側に指値で買いと売りを発生。 - v/vi 即時決済側に指値で買いと売りを発生。 - 実験設定 - コスト係数 α ∈ {0, 1e-4, 1e-3, 1e-2, 0.1} - 各条件30試行。 - 評価指標は各市場の平均的な最良気配/価格乖離/板の厚み(Depth:最良気配±100ティックの累計)/裁定発注回数(パターン別)。 価格水準の固定化:αが大きいほど、T+Nは割安・即時決済は割高に固定化。 例:平均最良売り— T+Nの SF はα上昇で低下(10,005.26→9,998.19)、即時市場の SL は上昇(10,005.26→10,012.97)。価格乖離は |SF−SL|, |BF−BL| ≈ 0.05→14.7 付近まで拡大、α≥0.01 で頭打ち。 板構造の非対称化: T+N市場:SellDepth > BuyDepth へ(売り厚↔下押し=割安化)。 即時市場:SellDepth < BuyDepth(買い厚↔上押し=割高化)。 裁定機会の蒸発:α上昇で全パターンの発注回数が減少。特にコスト付き((ii),(v),(vi))は α≥0.01 でほぼ消滅。結果、価格乖離を修正するはずのフローが細り、価格偏りが固定化。 所感/示唆 - 「速さ」の陰のコスト:即時決済を極端に推し進めると、借株依存の裁定が詰まりやすく、価格水準のバイアスが残存・固定化する。制度設計では、決済短縮と貸株市場の厚みをセットで議論すべき。 - 実務アルゴへの示唆:コストを内生化した条件式(式(3)(5)(7))での発注可否判定は必須。α感応度が高いパターン(特に(ii),(v),(vi))は在庫・貸株の確保やヘアカット前提でロジックを分岐させるのが良い。 - ポリシー面:T+1 移行や即時化の議論では、「裁定の自己修復力」を損なわないために、貸株供給や清算インフラ(在庫移動・担保受渡)の整備が鍵。 (04) 人工市場を用いたサーキットブレーカーの性能調査 著者:早瀬 竜希(工学院大学大学院)、水田 孝信(スパークス・アセット・マネジメント)、八木 勲(工学院大学) 目的/背景 サーキットブレーカー(CB)の「参照期間(過去価格を参照する窓)」Tr1 と「停止期間(取引停止の長さ)」Tr2 を切り分け、誤発注ショック下での価格下落抑制と回復スピードへの影響を人工市場で定量評価する。 アプローチ - シミュレーションに使う環境は、ザラバ方式の連続ダブルオークションを利用。 - ノーマルエージェント n=1000 が、ファンダメンタル/テクニカル/ノイズの3戦略を重み付きで発注。 - ティックサイズ δP=1、ファンダメンタル価格 Pf=10000と設定。 - 誤発注ショック:期間 tms=30000〜tme=60000、確率 pm=15% で成行売りに置換。 - CBルール:過去 Tr1 の価格から Pr=100 以上の下落(または上昇)で発動し、Tr2 の間は新規注文・キャンセル停止。 - パラメータ走査:Tr1∈{1000,2000,5000,10000}、Tr2∈{2000,5000,10000,20000} などの組合せで20試行平均。 - 評価指標は「最大下落幅」と「Pf への回復時刻」。 実験&結果 - 下落抑制:Tr1・Tr2 を大きくするほど最大下落幅は縮小した。(例:小さめ設定の Tr1=1000, Tr2=2000 で約955、対して大きめ設定の Tr1=20000, Tr2=10000 で約203)。 - 回復スピード:一般に Tr1・Tr2 を大きくすると Pf への回復は遅延(例:Tr1=1000, Tr2=2000 で約86,410時刻後、Tr1=20000, Tr2=2000 で約117,122時刻後に復活)。 - 参照期間Tr1を拡大するだけでも、下落は抑制された。ただし Tr1 がある閾値を超えると、初回発動タイミングはほぼ同じになり、効果は頭打ち。 - Tr1>>Tr2 だと連続発動が発生する。(停止期間終了後の判定が「下落前の高い価格」を参照し続けるため、実質的な停止が長引く)。 - 組合せ最適化の兆し:Tr1=5000 の列では Tr2=2000→5000 にすると、下落抑制と停止回数の減少が両立し、Pf への回復時刻が短縮するケースが観察された。 所感/示唆 - 設計トレードオフ:Tr1/Tr2 を大きくすれば下落は抑えられるが、回復は遅くなる。 - 初動抑制と回復スピードのバランス設計が要点。 - 実務の指針: - 短すぎる Tr2 は反動・連続発動を招きやすい一方、長すぎる Tr2 は回復遅延を招く。 - ショック終了見込みや清算工程を踏まえ、イベント窓に整合する中庸の Tr2 を探索する必要がある。 - Tr1 は「発動感度」を決めるダイヤルの役割を果たしている。そのため、市場のノイズ水準や誤発注頻度に合わせて、発動感度の頭打ち領域を超えない範囲で調整するのが重要。 - 監視・運用面では、Tr1>>Tr2 の連続発動リスクを常時モニタし、非常時は一時的にパラメータを切替えられる運用ルールが有効。 投資戦略(4件) (05) 米国経済指標の集団的変動と産業セクター間の関係性の分析 著者:北浦 崇弘(旭化成)、稲垣 祐一郎(旭化成ホームズ)、松浦 大将(みずほリサーチ&テクノロジーズ)、越山 祐資(みずほリサーチ&テクノロジーズ)、西野 洋平(みずほリサーチ&テクノロジーズ)、家富 洋(立正大学) 目的/背景 米国の主要な産業セクターレベルの経済指標とマクロ経済指標を対象に、複素ヒルベルト主成分分析(CHPCA)と多因子分析(MFA)を組み合わせた新しい分析フレームワークを提示。目的は、経済指標群が示す集団的な変動と景気循環の関係を明らかにすること、そして産業セクター間や指標間の先行・遅行関係を定量的に把握することである。 アプローチ - 複素ヒルベルト主成分分析(CHPCA)とMultiple Factor Analysis(MFA)を統合し、集団モードと位相を同時に推定。 - データ:FRED等から82指標(NAICS上位10セクター+マクロ指標)、期間1993/02〜2024/12。 - 有意モード判定にRRS(Rotational Random Shuffling)を採用。 - 安定性:特徴量のランダム削除、15年ウィンドウのスライディングで検証。 実験&結果 - 有意モードは3つ。寄与率は第一0.31、第二0.07、第三0.06。第一モードは景気後退期と整合し、累積強度はGDP下落率と近い関係。 - 安定性が高く、第一モードは特徴量9割削除でもcos類似度の中央値>0.9。 - 位相関係:労働時間(AWH)が雇用者数(AE)に先行/在庫売上比(ISR)→売上(SA)→在庫(TI)の順。 - セクター:小売(RT)が先行し、総合(TOT)が続く傾向。 - イベント別:ドットコムは集団運動が限定的、リーマンとCOVID-19は広範な波及。リーマンは後半で集団性が顕在化。 所感/示唆 - 第一モードは「景気の集団運動」の実用的proxy。景気後退の規模感比較や産業別波及の把握に有効。 - 実務では、雇用・在庫関連の合理的順序(AWH→AE/ISR→SA→TI)を前提に、サプライチェーンの先行把握と在庫調整のタイミング設計に活用可能。 - 分析は長期データとセクター網羅性に依拠するため、他国・最新系列への継続適用で頑健性をモニタリングしたい。 (06) 多資産ネットワーク分析が示す暗号資産の独立性とポートフォリオ分散効果 著者:水門 善之(慶應義塾大学) 目的/背景 暗号資産(ビットコイン)が伝統的資産(株・債券・コモディティ・為替)と比べてどの程度独立しているかをネットワークで可視化し、分散投資の有効性(Sharpe/Sortino)を検証する。 アプローチ - 週次リターンの相関行列からネットワークを構築(相関0.2〜0.3で閾値化)。 - 中心性(Degree/Eigenvector/Betweenness/Closeness)で構造把握。 - 主要株価指数・コモディティ先物・主要通貨・暗号資産を対象。 - モンテカルロで9資産(主要ETF群+BTC)の無空売りポートフォリオを1万通り生成し、Sharpe/Sortino最大点を探索(2020/01〜2025/04)。 実験&結果 - ネットワークではBTCが周縁に位置し、中心性が総じて低い=独立性が高い。相関閾値0.3ではBTCは他資産から切断されるケースも。 - 銅(Copper)は異市場を含めても中心性が一貫して高い(景気指標性)。 - Sharpe ratio:Sharpeが0.086→0.160に改善、BTCウエイト約0.29。 - Sortino ratio:0.19→0.32に改善、BTCウエイト約0.13(下方リスク配慮で比率は抑制)。 所感/示唆 - BTCは「低相関な衛星資産」として、総合リスク当たり収益を押し上げ得る。ただし下方リスク耐性を重視する運用では配分抑制が妥当。 - マクロ連動性が相対的に薄い局面で寄与が期待できる一方、制度・流動性イベントへの感応度が高いため、ガバナンス・流動性管理とセットで配分設計したい。 - コモディティでは銅の中心性が高く、景況変化の早期把握に有用なモニタ対象となる。 (07) 長期相関を持つ成行注文流と価格インパクトのミクロモデル化に基づく株価の予測困難性の説明 著者:佐藤 優輝、金澤 輝代士(京都大学) 目的/背景 成行注文流は長期記憶性により予測可能だが、価格は予測困難というパラドックスを、ミクロモデルで説明する。 アプローチ - Lillo–Mike–Farmer(LMF)型の注文分割行動モデルと、価格インパクトの平方根則 I(Q)=c√Q を結合した理論モデルを構築。 - 注文符号の自己相関 C(τ)∝τ^{-γ}(0<γ<1) を前提に、価格ダイナミクスの厳密解を解析。 実験&結果 - トレーダが分割執行を継続することで注文流は長期記憶を示し、平均的なインパクトは√Qに比例。 - その一方で、価格系列は拡散的(ランダムウォーク状)になりやすく、注文流の予測可能性と価格の予測不可能性が同居する条件を理論的に提示。 - モデルは市場横断で観測される経験則(長期記憶の符号系列、普遍的な平方根インパクト)と整合。 所感/示唆 - 注文流シグナルの活用は、インパクト・流動性制約を踏まえた実装でなければ超過収益に直結しにくい。 - 最良執行や最適発注(POV/TWAP等)の設計では、平方根インパクトと分割行動が生む自己相関を同時に考慮すべき。 - マーケットメイク/リスク管理では、長期記憶を前提にヘッジ頻度・在庫上限の調整が有効。 (08) 戦略多様性と平方根則を取り入れた一般化LMFモデル 著者:藤原 俊太(京都大学)、佐藤 優輝(京都大学)、金澤 輝代士(京都大学) 目的/背景 価格インパクトの平方根則 は普遍的とされる一方、比例係数 c の解釈・無次元化の妥当性が十分に整理されていない。 本研究は、近年の理論モデルを基に c の定義付けと統計解析手法の妥当性を検証し、取引コスト指標としての c の位置づけを明確化する。 アプローチ - 価格インパクトの定義を再確認し、出来高 VD・ボラティリティ σD による無次元化を前提に理論整備。 - 近年提案モデルを採用し、日次出来高・日次ボラの解析計算および数値計算を併用して c と無次元化の関係を導出。 - 既往研究(各市場で δ≈0.5 の検証)との差分整理:指数 δ と係数 c の役割分担を明確化。 - 実務上の取引コスト(流動性・板厚・スプレッド等)との対応づけを検討。 実験&結果 - べき指数 δ は既報通り 0.5 付近で安定。一方で c は市場横断に一定ではなく、無次元化の前提に依存して変動。 - モデルから、c は「価格応答の強さ=実効的な取引コストに比例」する関係が定量化され、銘柄・日付を跨いだ比較可能性の条件(VD・σD によるスケーリングの前提)を提示。 - 統計手法の検証:従来の無次元化に理論的根拠を与えつつ、過度な一律適用には注意が必要という結論。 所感/示唆 - 係数 c は「市場インパクトのコスト・メーター」とみなせる。運用実装では c の時系列推定(流動性レジーム検知)を組み込むと良い。 - 監視指標としては δ よりも c のドリフト/レジーム転換が重要。板厚・ボラ・回転率と併せた多次元モニタリングが有効。 - 企業横断や市場横断の比較では、無次元化の条件充足(VD・σD の安定性)を確認してから指標化すべき。 テキストマイニング(5件) (09) 金融テキストごとの特徴分析とポートフォリオ評価 著者:高野 海斗(野村アセットマネジメント) 目的/背景 ファンドマネージャーが投資判断に用いる決算短信やアナリストレポートなどの金融テキスト情報について、その定性的な情報を定量的な数値に変換し、資産運用戦略への活用可能性を探ることを目的とする。特に、従来のセンチメント分析が抱える課題(センチメント定義の曖昧さ、分類タスクによる限界など)を踏まえ、「将来の見通し」に焦点を当てた独自のセンチメント分析モデルを構築し、テキストの種類による特徴の違いを明らかした。 アプローチ - 対象テキスト:バイサイド/セルサイドのアナリストレポート、ニュース、四季報(業績記事コメント・材料記事コメント)。 - センチメント推定:辞書法と BERT ベース(回帰タスク、-2〜+2 の連続値)。同日複数文は平均化し、過去90日・半減期20日で時間加重。 - 検証: - イベントスタディ:テキスト公開日を起点に ±60 営業日で累積リターンとの相関。 - 分位ポートフォリオ:月末に5分位(Strong Neg.〜Strong Pos.)、等ウェイト/時価総額ウェイトで評価(AR/TE/IR/MaxDD/TR)。 実験&結果 - カバレッジ:四季報は銘柄数ベースほぼ常時100%、セルサイドは時価総額ベースで高水準。 - 事前の値動きとの整合:t 時点のセンチメントと t−60 営業日のリターンに正相関(事前の上昇=ポジ、下落=ネガが多い)。 - 公開後 60 営業日の相関は総じて小さい。 - アナリスト系は公開直後の短期で差が出やすい。 - ニュースは当日に反応するが持続は弱い。 - 四季報の公開後10営業日超で、ネガティブ分位が相対的に切り返す「リバーサル」傾向が見られた。 - ポートフォリオ: - アナリスト(Strong Pos.)で良好な ARが観測された。 - ニュースは Neutral を除き概ねプラスに寄与している。 - 四季報はネガ側の AR がプラス、売買回転率は低水準となっていた。 所感/示唆 将来の見通しに着目したセンチメント分析モデルを用いることで、金融テキストの種類ごとに異なる特性を明確化し、資産運用への実用的な示唆を与えました。特に、一般的に避けられがちな四季報のネガティブコメントが、逆張りの投資戦略において有効なシグナルとなり得る可能性を示した点は、大きな発見。今後は、本研究で得られたセンチメントスコアと財務指標などを組み合わせた、より高度な投資戦略の検討が期待される。 (10) 有価証券報告書テキストを用いた配当政策データの構築と分析 著者:竹下 蒼空(成蹊大学)、高野 海斗(野村アセットマネジメント)、仁科 慧(成蹊大学)、酒井 浩之(成蹊大学) 目的/背景 企業の配当政策に関する方針が主に有価証券報告書などのテキスト情報として記述されている点に着目し、これを自然言語処理技術(BERTやLLM)を用いて体系的にデータ化することを目的とする。手作業での分析や単純なキーワード検索では困難だった、大規模かつ高品質な配当政策データの構築手法を提案し、そのデータを用いて、企業の配当政策が投資パフォーマンスにどのような影響を与えるかを分析・検証した。 アプローチ - 文抽出:BERTopic+ModernBERT を組み合わせ、配当政策に有益な文を段階的に抽出する。 - マルチタグ付与:LLM few-shot で「増配(INC1〜9)」「減配(RED1〜6)」を文単位に多重ラベル化。(structured outputs で一貫性確保)。 - 評価:各タグで人手適合率を測定。 - 投資検証:2017/04〜2025/03、TOPIX500 対象。 - 増配系(increase)、非減配系(no reduction)、両方(progressive)のポートフォリオを月次で構築(等ウェイト/時価総額ウェイト)。 実験&結果 - タグ頻度:近年は増配志向が強まり、とくに「増配の意図(INC7)」と「累進配当採用(INC3)」が増加。 - コロナ期に一時的な減配言及(RED5/RED6)が増。 - 文分類性能:一部の時点判定タグ(当期導入・当期減配)は文単体では難しく適合率が低下するが、他は概ね良好な結果であった。 - パフォーマンス(例): - 等ウェイト:progressive の AR≈+4.1%、increase の AR≈+3.5%。 - 時価総額ウェイト:progressive の AR≈+3.3%。回転率は年1回の開示反映が中心で比較的低位。 - 特性:DY/DOE の中央値は True/False 間でたびたび交差し、単純な高配当指標では代替困難=テキスト固有情報を含む。 所感/示唆 - 開示テキストは「将来の配当方針」を直接反映するため、数値財務だけでは拾いにくいシグナルを提供。 - 実装の肝は「抽出→多ラベル化→保守的集計(月次)」の一貫パイプライン化。タグの時点解釈は企業内時系列(例年文)を併置して精度向上を図りたい。 - 運用面では、progressive/increase/no reduction を補助シグナルとしてバリュー・クオリティ因子と組み合わせると頑健化が見込める。 (11) LLMsによる利益予測の分析とアウトオブサンプル評価 著者:白井 祐典(Insight Edge, Inc.)、市川 佳彦(Insight Edge, Inc.)、中川 慧(大阪公立大学) 目的/背景 EDINET-BENCH を用いて、日本上場企業の「次期純利益の増減方向」予測における LLM の特性を検証。どの企業・業種で精度が出やすいか、また学習後に出現した未知データに対して汎化(アウトオブサンプル:OOS)できるかを評価する。 アプローチ - 予測方法:EDINET-BENCHを流用。 - モデル:Claude 3.7 Sonnet/カットオフ 2024-10-31を利用。 - インサンプルデータ:EDINET-BENCH の利益増減ラベル(テストは 2021–2024 年が中心)。 - OOSデータ:2025-06-01〜2025-08-31 に EDINET 提出の有報(学習後公開)を対象。 - 指標:ROC-AUC を「売上規模四分位」「東証17業種」別に算出し、インサンプル(IS)と OOS を比較。 - 事前検証:カットオフ後の自然現象を問うプロンプトで「後知識混入」兆候の有無をチェック。 実験&結果 - IS 全体 ROC-AUC:0.6075。売上下位 25%で低め(例:0.5330)など規模依存が示唆。業種間ばらつき大。 - OOS 全体 ROC-AUC:0.6327とインサンプルに対して減少せず。 - 業種差:銀行は OOS で大幅改善(+0.2899)、一方で「金融(銀行除く)」「鉄鋼・非鉄」などは低下。 所感/示唆 - LLM の利益予測は OOS でも一定の汎化を確認。ただし業種・規模で異質性が大きく、追加特徴の導入(業種特化のテキスト・数量情報)が鍵。 - 実務利用は「銘柄横断の一律モデル」より、業種別や規模別のハイブリッド設計(テキスト+数値+事前分布)でのチューニングが有効なのではないかとの示唆。 - 正真正銘の OOS を厳密化するため、評価ウィンドウとカットオフ設計(予測対象の1年前基準など)の標準化が望ましい。 (12) 適時開示テキスト埋め込みを用いたイベントスタディにおける累積異常リターンの予測 著者:伊藤 央峻(日興リサーチセンター) 目的/背景 企業の適時開示情報(TDnet)の「タイトル」から生成した高次元のテキスト埋め込み(テキストの数値ベクトル表現)が、開示直後の短期的な株価下落リスクを予測する上で有効な情報となるかを検証することを目的とする。従来のセンチメント分析など、テキスト情報を少数の指標に集約する手法では失われがちな微妙なニュアンスを、高次元の埋め込みを直接利用することで捉え、予測精度が向上するかを評価した。 アプローチ - データ:2020/04〜2025/04 の開示約69万件(タイトル・公開項目コード)を利用。 - 特徴量:日本語特化埋め込み ruri-v3-310m(768次元)、価格系ベース特徴、業種ダミー、公開項目コード。 - モデル・検証:LightGBM/Purged K-fold(K=5)、PR-AUC 最適化。クラス不均衡はアンダーサンプリングで調整。目的変数を「CAR(0,3)が分布の下位5%に入るか(=短期的な下落リスクの発生)」とする二値分類問題と設定。 - 可視化:PCA+クラスタリングで埋め込み空間の整合性と季節性を確認。SHAP で特徴寄与を解釈。 実験&結果 - 性能:Base(価格+業種)の F1=0.235・PR-AUC=0.164 → 埋め込み追加で F1=0.265・PR-AUC=0.192 に改善。 - 単独比較:Emb 単独は Code 単独より全指標で優位。Base+Emb と Base+Emb+Code は同水準=埋め込みがコード情報を内包。 - 構造:PCA 可視化で決算/ガバナンス/PR 等の意味的分離と季節パターンを確認。SHAP では直近リターンと埋め込み成分が上位に並ぶ。 所感/示唆 - タイトル埋め込みは「短期下落リスクの早期検知」に有効。 - 公開項目コードの事前カテゴリを超える情報を保持。 - 運用では、価格モメンタム/リバーサル系の簡便特徴に埋め込みを重ねる構成が費用対効果良。 - 次の改善は、本文・添付資料の統合、分位回帰など連続予測化、埋め込み次元の安定化の検討。 (13) 大規模言語モデルを用いたアンサンブル手法による J-REIT物件情報データセットの効率的な構築方法 著者:田中 麻由梨(日本取引所グループ)、土井 惟成(日本取引所グループ) 目的/背景 J-REIT 有報の物件情報は表・テキストが混在し自動構造化が難しい。報告書に記載される物件情報は、フォーマットが統一されておらず、テキストと表が混在しているため、自動でのデータ化が困難。 複数 LLM の Few-shot 出力をアンサンブルし、高精度な JSON 変換と人的修正の最小化を目指す。 アプローチ - 対象:J-REIT の「保有資産」等の HTML 断片(表+注記テキスト)。 - 前処理:style系の削除で構造を保持しつつトークン削減。Few-shot で JSON 仕様と注記取扱いを明示。 - モデル:ChatGPT-4o/Gemini 2.5 Pro/Claude 3.7 Sonnet、temperture=0で実施。 - アンサンブル方法:複数モデルの予測結果の3/3一致なら「採用」、2/3一致なら「多数決」、不一致なら単体精度最大モデルを採用。 - 改変検知:キー>20文字や値の過長などをルールで警告し、修正箇所を特定。 実験&結果 - Few-shot 効果:Zero→One→Two-shot で Accuracy が 32.7%→96.9%→98.37% に向上。 - LLM 単体(Two-shot):Accuracy は 98.37〜99.03%。 - 一致度別:3/3一致は 99.991%、2/3一致は 98.350%、全不一致は各モデル 86〜89%。 - 最終アンサンブル:全体 Accuracy 99.222%。不一致は約5%のみで人的確認対象を大幅に圧縮。 所感/示唆 - 「3/3または2/3一致は自動確定、0/3のみ人手確認」の運用が現実解。注記の表記揺れは正規化ルールで吸収可能。 - データセット拡張時も Few-shot 事例の管理とルール検知をセットにすれば、精度と省力化を両立できる。 - 表+注釈の混在ドキュメントにおける汎用的な構造化パターンとして他ドメインへの横展開が期待できる。 - 作成されたデータセットは、GitHubで公開される予定。 データマイニング(4件) (14) トランザクションレンディングにおける法人のデフォルト分析 著者:小林 司(東京大学)、山本 竜也(GMOあおぞらネット銀行)、成末 義哲(東京大学)、森川 博之(東京大学) 目的/背景 従来の財務諸表に基づく融資とは異なり、口座の取引履歴を基に審査を行う「トランザクションレンディング」における法人のデフォルト(債務不履行)要因を分析した。インターネット専業銀行の実際の融資データを用いて、法人の属性や取引情報がデフォルト率にどう影響するかを検証している。 アプローチ - 対象データ:インターネット専業銀行の契約18,199件(契約後1年のデフォルトを評価)。 - 属性軸:設立年数(3年未満/以上)、代表者の事業経験(初回/法人設立経験/個人事業主経験)、業種。 - 取引軸:入金先の集中度(顧客依存の強さ)をハーフィンダール・ハーシュマン指数(HHI)で定義。 - 比較:各セグメントのデフォルト率(LOW/MEDIUM/HIGH)と統計的有意性を検証。 実験&結果 - 設立3年未満の法人は、3年以上よりデフォルト率が有意に低い。 - 代表者が個人事業主経験・法人設立経験を持つ場合は相対的に低水準。 - 業種:デザイン/教育など“ソフト面”依存の業種で低水準、食品・飲料や小売・製造で高水準の傾向。 - 顧客集中:HHIが高いほどデフォルト率上昇。年数に依存せず一貫。 所感/示唆 - 「若い×経験あり」プロファイルを過度に抑制せず、顧客集中の分散度を厳格モニタする設計が有効。 - スコアリングは属性(年数・経験)×取引集中(HHI)の二軸で早期に差別化。与信額より行動指標に着目。 - 業種の“ソフト依存度”を補助変数化し、審査・モニタリングの優先順位に反映したい。 (15) Fiedlerベクトルと情報エントロピーを用いた株式ネットワークの構造変化検知 著者:星野 知也(株式会社三井住友銀行) 目的/背景 相関に基づく株式ネットワークの“構造変化点”を頑健に捉え、レジーム転換の早期兆候を検出。Minimum Spanning Tree(最小全域木)の、ショック時の不自然連結の限界を補う枠組みを提示。 アプローチ - 提案:階層的Fiedler疎性化(Hierarchical Fiedler Sparsification;HFS)で疎グラフ構築(Fiedlerベクトル×二分割×交差エッジ選択)。 - 指標:Fiedlerエントロピー[均質性]とワッサースタイン距離[構造差異]で変化点検知。 - データ:米国S&P500の49業種(日次、直近12週ロール、週次更新)。MST・Absorption Ratioと比較。 実験&結果 - 構造表現:コロナ期(2020/3)はHFSで業種クラスタが明瞭、MSTは直感に反する隣接が散見。 - 変化点:2020/2上旬(下落前)、3月後半(底形成期)、11月(選挙・ワクチン報)で検知が整合。 - 収益との関係:エントロピー↑の週は平均超のリターン、距離↑の週は低下傾向。上昇/下落トレンド別でも意味ある差分。 所感/示唆 - リスク上昇の前兆は「エントロピー低下+距離上昇」の組み合わせで早期拾い。監視ダッシュボードに組込みやすい。 - 相関ノイズに強く、業種構造の解釈性が高い。日本市場や個別銘柄粒度にも横展開が期待できる。 - 運用ではMST等との併用で“ショック依存の誤配線”を回避し、レジーム検知の信頼度を底上げ可能。 (16) 暗黙の政府保証を加味した国内地方債スプレッドの評価 著者:石原 龍太(みずほ第一フィナンシャルテクノロジー) 目的/背景 一般的に、日本の地方債は地方財政制度を通じた政府の支援により、信用リスクは低いと認識される。しかし、この政府保証は法的な根拠を持つものではなく、その信頼度(市場の信認度)を観測することは困難。過去には、国の補助金削減などを背景に地方財政への懸念が高まり、スプレッドが拡大した事例もあった。本論文では、国内地方債の発行スプレッドに内在する「暗黙の政府保証」の強さを定量推計し、説明力の向上と市場整合性を検証した。投資家・引受側・政策当局の判断材料を提供。 アプローチ - モデル:スプレッド=α+β₁×{実質公債費比率×(1−政府保証信頼度)}+β₂×{国債マイナス金利幅}。 - 推計:遺伝的アルゴリズムで「政府保証信頼度(月次)」を同月の都債との差分二乗誤差と平滑化で最適化。 - データ:2006/4–2024/3の市場公募地方債(10年)、都道府県の実質公債費比率、国債利回り。 実験&結果 - 信頼度の時系列:2006–2008年に低下局面(制度改革・スプレッド拡大と整合)、以降は高水準で安定。 - 当てはまり:政府保証信頼度を入れると決定係数R²が0.169→0.308に改善。 - 係数解釈:保証が消滅した極端仮定では、実質公債費比率20%の団体でスプレッドがおおよそ+20bp拡大。 所感/示唆 - 地方債の“準安全資産”性は制度と市場信認の産物。信認低下シナリオのストレス計測に有用。 - 実務では、保証信頼度のモニタ(時系列)×団体別ファンダ(実質公債費比率)で相対価値評価を高度化。 - 将来の制度変化や人口動態ショックに対し、信頼度推計を早期警戒指標として活用できる。 (17) 本邦中古スマートフォン市場における価格形成に対する機種ブランドと為替レートの影響 著者:市川 佳彦(Insight Edge, Inc.)、平野 友貴(住友商事)、居村 裕平(住友商事)、中條 悠介(住友商事)、桑本 奈緒(住友商事)、堤 鴻志郎(住友商事)、中川 慧(大阪公立大学) 目的/背景 中古スマホ市場の価格形成メカニズムを、実勢に近い「買取価格」を用いて定量検証。ブランド差や米ドル/円の為替変動が残価率にどう効くかを明らかにする。 アプローチ 2018–2024年の機種・容量別の月次買取価格(RMJ)から残価率(RVr)を定義し、①経過月数とRVrの関係(線形回帰/可視化)、②為替変動のラグ効果(1–6か月ウィンドウ×1–4か月ラグの相関)、③iPhoneを対象にXGBoost+SHAPで特徴量重要度を評価。 実験&結果 - 価格を最も規定するのは「発売からの経過月数」。Appleは他社に比べ減価が緩やか。 - 為替は短期の同時点では効きにくいが、iPhoneでは「過去1–2か月の為替変化が約3か月後のRVr変化と弱い正相関」。 - XGBoostの予測精度は高水準(R²≈0.898、MSE≈0.0020)。SHAPでは「経過月数」が圧倒的に重要。容量は非線形で、64GBはマイナス寄与、128/256GBは中立、512GB/1TBは相対的に不利。 所感/示唆 経過月数とブランド差(特にApple優位)を前提に、在庫評価や買取価格のガイド可。為替は即時反映ではなく遅行気味のため、為替ショック観測後の数か月先を見た在庫ポジション調整が有効。容量ミックスは「中容量を主軸」に設計するのが合理的。 機械学習(5件) (18) 事前エクスポージャー情報を活用した部分空間正則化付き主成分分析 著者:中川 慧(大阪公立大学・MONO Investment)、加藤 真大(みずほ第一フィナンシャルテクノロジー・大阪公立大学)、今村 光良(筑波大学) 目的/背景 資産運用で広く用いられる主成分分析(PCA)の安定性を向上させる新しい手法「部分空間正則化付き主成分分析」を提案。 株式や債券など複数の資産(マルチアセット)の価格変動を説明するために、マクロ経済指標と関連付けられるリスクファクター(マクロファクター)が利用される。このファクターを抽出する代表的な統計手法が主成分分析(PCA)となる。しかし、過去の一定期間のデータを用いて分析を逐次的に繰り返す「ローリングPCA」では、推定される主成分(ファクターへの各資産の感応度を示すエクスポージャー)が時間とともに大きく変動してしまう問題がある。この不安定性は、ファクターの経済的な解釈を一貫して行うことを困難にし、分析の信頼性を損う。 この課題を解決するため、本研究では「部分空間正則化付き主成分分析」を提案。この手法は、標準的なPCAの最適化問題に、利用者が経済的知見に基づいて事前に設定した「望ましいエクスポージャー構造(事前エクスポージャー情報)」を正則化項として加えるものである。 アプローチ ユーザが与える「事前エクスポージャー(US)」の張る部分空間への射影を正則化項として導入し、標本共分散との凸結合S_LS=λ(USUSᵀ)+(1−λ)Sに対するPCAとして解く。実証では6資産(月次、1997–2025)でk=3因子、Procrustes距離で安定性を評価し、リスク分解で説明力を確認。 実験&結果 - λを上げるほど主成分空間の時系列安定性が大幅改善(平均Procrustes距離:λ=0で2.46 → λ=0.9で0.12)。 - 説明力(Adj.R²やリスク分解の寄与)はおおむね維持され、安定性と説明力の両立を確認。 - 事前因子は「成長/リスクオン・実質金利・インフレ」に対応する構造で解釈可能。 所感/示唆 - 「解釈可能な因子構造を維持したまま」戦術配分やリスク管理に使える因子を安定抽出できる。 - 運用現場ではλをハイパラとして運用目的に合わせて調整し、安定性重視のモニタリングやアロケーション説明に有効。 (19) 財務諸表監査のための逐次検定:試査手続の統計学的な定式化と理論保証 著者:加藤 真大(大阪公立大学・みずほ第一フィナンシャルテクノロジー)、中川 慧(大阪公立大学) 目的/背景 財務諸表監査における「試査」と呼ばれるサンプリング調査の慣行に、統計学的な理論保証を与えるための新しい手法を提案している。現代の財務諸表監査では、膨大な量の勘定科目を全て調査する「精査」は非現実的であるため、一部を抜き出して調査する「試査」が行われている。実務では、一度のサンプリングで判断できない場合、判断がつくまで追加でサンプリングを続ける慣行があるが、これは経験則に頼っており、誤った判断を下す確率(誤判断確率)が数学的に保証されていなかった。 本研究では、統計的保証のある逐次検定として整備し、誤判断確率(第一種・第二種)を管理可能にする。 アプローチ 有限母集団からの非復元抽出(超幾何分布)を前提に、停止・決定ルールを閾値列(上限κᵣ(t)、下限κᵣ_(t))で定義。最悪母逸脱率(r±θ)でモンテカルロにより閾値を逐次的に設計し、所望のα・βを満たすよう誤判断確率を制御。拡張として片側検定、検出力制約つき片側、二段階検定、打ち切り付き逐次検定を提示。 実験&結果 - 無関心領域外で誤判断確率が目標(例:α=β=0.05)以内に制御されること、想定レンジで期待停止時刻が算定可能であることを数値実験で確認。 - 現場フローに合わせた設計(初回サンプル後の追加試査や打ち切り)にも対応可能。 所感/示唆 「どこで止めるか/どちらと判定するか」を事前に設計できるため、監査品質の説明責任を強化。許容逸脱率や無関心領域、α・βを文書化しておくと、監査計画・レビュー時の合意形成がスムーズになる。 (20) 学習期間が異なる株価予測機械学習モデルのアンサンブル学習による投資戦略の構築 著者:西村 征馬(三井住友トラスト・アセットマネジメント) 目的/背景 機械学習による株式リターン予測では「学習期間(履歴の長さ)」が結果に大きく影響するが、最適期間は一意に決めにくい。 本研究は、学習期間だけが異なる複数モデルをアンサンブルし、期間選択を機械化して予測力と運用成績を高めることを目指す。 アプローチ - ベースモデル:LightGBM(回帰、損失はMSE)。学習期間を36〜120か月まで12か月刻みで8本作成。 - 検証設計:将来/同時点情報漏洩を避ける時系列交差検証でvalidationを統一化。 - アンサンブル: - スタッキング(線形回帰/Elastic Net) - ブレンディング(逐次二次計画法[SLSQP]で非負・和=1の重み最適化でのブレンド) - バックテスト:TOPIX採用のうち時価総額上位60%、2015/07–2025/06。予測に基づく分位(5分位・10分位)およびロングショートを評価。 実験&結果 - 損失:Validation最小化で得た重みはTest lossも概ね改善。OLSスタッキングは過学習傾向、Elastic Netとブレンディングが安定。 - 収益性:アンサンブル由来のポートフォリオが単体モデルより一貫して高い超過リターン(5分位・10分位、ロングのみ/ロングショートの別を問わず)。 - ロバスト性:学習期間差に依存せず、重み学習により未知データでの標準偏差(test_std)も抑制。 所感/示唆 - 学習窓の最適化は“選ぶ”より“混ぜる”が実務的。Elastic Net系や非負和=1ブレンドは過学習を抑えつつ汎化に寄与。 - 年次リバランス等の軽量更新でも効果が出る設計で、既存のファクターモデルに期間多様性アンサンブルを上乗せする価値が高い。 (21) マルチモーダルデータを用いた機械学習モデルによる企業の業績修正予測 著者:田代 雄介(MTEC)、鈴木 彰人(MTEC)、山口 流星(MTEC)、宮澤 朋也(データアナリティクスラボ)、亀田 希夕(データアナリティクスラボ) 目的/背景 企業の財務報告書(決算短信)のテキストデータと、株価の時系列データを組み合わせた「マルチモーダルな機械学習モデル」を構築し、将来の業績修正を予測するタスクに取り組んだもの。 長文テキストと時系列情報を統合した機械学習フレームワークを構築し、企業の業績修正(上方修正、下方修正、修正なし)を予測するモデルの有効性を検証することを目的とする。 アプローチ - テキスト:ModernBERT(長文対応、最大4096トークンで学習)。 - 時系列:Transformerエンコーダ(過去250営業日の対TOPIX超過リターン)。 - 結合:単純結合/Gated Fusion/Cross Attention を比較。 - データ:東証プライムの2018年以降。クラス不均衡は1:1:1のアンダーサンプリング。Train=2018–2022、Test=2023–。指標:AUROC, F1。 実験&結果 - ベースライン:テキスト単独 AUROC=0.656, F1=0.458;時系列単独 AUROC=0.555, F1=0.345。 - マルチモーダル:Cross Attentionが最良(AUROC=0.671, F1=0.493)、Gated Fusion/単純結合もテキスト単独を僅かに上回る。 - 長文効果:4096 vs 512トークンで大差なし(重要情報は先頭に集中する可能性)。 - 実務適合性:予測確率に基づく10分位分析で、最上位分位の上方修正率53.2%とシグナル妥当性を確認。 所感/示唆 - テキストが主、時系列は補助。ただし結合部の設計(Cross Attention等)で安定改善。 - 運用では、確率スコアの分位連動でアナウンス前のポジショニングや監視銘柄選定に転用可。モデルの軽量化・更新性と併せて実装のしやすさが高い。 (22) 生成AIを用いた決算説明サプライズの定量化手法の提案 著者:辻 晶弘(DaNeel Insight株式会社) 目的/背景 従来のサプライズ指標SUE(EPS実績−予想)だけでは説明会の質やIR文脈が捉えにくい。生成AIによるペア比較+GlickoレーティングでEarnings Callの相対評価(rating)を構築し、価格反応(day1)とその後のドリフト(day2–5)への説明力を検証する。 アプローチ - ユニバース:日本上場企業(SCRIPT Asia等のコール要約/トランスクリプトを整備)。 - 指標:SUE_cs(コンセンサスEPS変化)、SUE_cb(会社ガイダンス変化)を定義。 - 生成AI:LLMでコール要旨をペア比較し、Glicko法で一体化スコア(rating)へ。 - 目的変数:TOPIX超過のday1(reaction)とday2–5(drift)。単回帰・重回帰でp値/R²・標準化係数を評価。 実験&結果 - day1反応:SUE_csが最も強い(R²≈0.093、p<0.001)。SUE_cb, ratingも有意だが相対的に小。 - day2–5ドリフト:説明力はratingが優位で、日を追うごとにR²は逓減しつつも正の寄与を維持。数値サプライズのみでは説明しきれない質的情報の寄与を示唆。 - 相関・面回帰のクロスチェックでも、即時は数値、持続は質という役割分担が概ね一貫。 所感/示唆 - 決算説明会の質(言語情報)は短期の方向づけより継続リターンに効く。イベント後のフォローではratingの活用が有効。 - 実務実装は、SUE(数値)×rating(質)の二軸でイベント選別とエントリー/エグジットを設計。トランスクリプト整備と時点合わせが精度のカギ。 機械学習/テキストマイニング(4件) (23) 指値配分を連続確率分布化した深層学習によるマーケットメイキング 著者:久保 健治(東京大学・株式会社松尾研究所)、中川 慧(大阪公立大学・株式会社松尾研究所) 目的/背景 深層学習(DL)を用いた効率的なマーケットメイキング手法を提案するもの。特に、価格変動が大きい市場において複数の指値注文を管理する際の、行動空間の爆発的な増大という課題に対処している。 指値の数量配分を離散的なものではなく、連続確率分布を用いて緩和する点が研究の中心。具体的には、複数の正規分布を混合した混合正規分布を使い、指値数量の分布を表現した。これにより、ニューラルネットワークが出力すべきパラメータの数を大幅に削減し、高次元の行動空間の問題を回避して効率的な学習を可能にする。 アプローチ 指値数量の「配分」を連続確率分布(混合正規の切断・再正規化)で緩和し、行動空間を圧縮。StockMixerに時刻特徴量とポジションを拡張して方策ネットを構成し、CARA効用で学習。約定は高値/安値と分布の積分で近似し、離散化との誤差を検証。 データ/実験設定 米国上場の主要ETF5銘柄(GLD, IWM, QQQ, SPY, TLT)・5分足(2021–2024)。Nm=3の混合正規、ラグL=78、T=24、γ=1。学習は前半期間の7-fold、テストは後半期間でバックテスト。 結果 テストで年率リターン約5%、ASR≈1.62、MDD≈2.98%、CR≈1.52。連続緩和は収益をやや過大評価するが、離散実装との差は限定的。ポジションは各銘柄・合計ともに偏り小さくリスク管理が機能。 所感/示唆 複数価格・複数銘柄のマーケットメイクで「数量配分を連続化」する設計は、RL実装の安定化と可搬性(離散実装への落とし込み)を両立する実務的トレードオフ。 今後は緩和誤差の制約化とネットワーク設計の最適化(例:執行コスト・流動性制約の内生化)が有効。 (24) 3値ポートフォリオ最適化に対するQAOAミキサーの性能比較 著者:山村 真太郎(東京理科大学)、渡邉 聡(KDDI総合研究所)、國見 昌哉(東京理科大学)、斉藤 和広(KDDI総合研究所)、二国 徹郎(東京理科大学) 目的/背景 量子コンピューティングの一分野であるNoisy Intermediate-Scale Quantum(NISQ)デバイス向けの量子アルゴリズム、Quantum Approximate Optimization Algorithm(QAOA)を金融工学の重要課題であるポートフォリオ最適化問題に応用した研究。 現実の運用では「保有・非保有・空売り」の3状態が自然。3値(−1,0,1)のポートフォリオ最適化をQAOAで解き、ミキサー選択が性能に与える影響を比較する。 アプローチ 2量子ビット/資産で3値を符号化し、Standard/XY系(Ring, Parity Ring, Full, QAMPA)を比較。 p層の初期値設計・古典最適化(SLSQP/Nelder-Mead)を工夫し、ノイズ(depolarizing)環境でも評価。 データ/実験設定 DAX30からn=5/8のサブセット(主にn=5, B=2を提示)。 Statevector/Qasm/DensityMatrix Simulator、ショット数3,000/8,192、p=1,3,5,7。評価は平均近似率rと最適解確率P。 結果 無ノイズではXY Full/QAMPAが高水準(r>99%級、Pも高い)。 一方、ノイズ下ではパラメータの頑健性と探索容易性の差が顕在化し、層数pの増加は必ずしも有利に働かない。 初期値・古典最適化設定が品質に与える影響も大きい。 所感/示唆 3値最適化では、ノイズ環境を前提としたミキサー選定+初期化戦略が鍵。 小規模問題でも層数の過剰増加は避け、頑健性重視のハイパラ設計で実運用への橋渡しが現実的。 (26) LLM-PEAD.txt:日本株式市場におけるLLMを用いたサプライズ抽出と決算後ドリフトの実証分析 著者:種村 賢飛 (東京大学/松尾研究所), 久保 健治 (東京大学/松尾研究所), 中川 慧 (大阪公立大学/松尾研究所) 目的/背景 近年の日本株式市場を対象に、企業の決算発表後に株価がサプライズの方向に継続して動く「決算後ドリフト(Post Earnings Announcement Drift;PEAD)」という現象を再検証したもの。特に、従来からの数値情報に基づくサプライズと、大規模言語モデル(LLM)を用いてテキスト情報から抽出したセンチメント(市場心理)を組み合わせることで、この現象がどのように変化するかを分析。 アプローチ - 決算資料から、数値に基づくサプライズ指数 SUE と、LLM によるテキストベースのサプライズ指数 LES を構築。 - PEAD の検出:決算発表後の超過リターンがサプライズ方向に継続するかを検定。 - LES は辞書極性ではなく LLM により文脈を評価し、“数値では拾えない含意”を抽出。 データ/実験設定 - 日本上場企業の決算発表(TDnet/決算短信等)を対象。 - 発表当日以降の短期〜数日スパンの超過リターンでドリフトを評価。 - SUE 単独、LES 単独、SUE×LES の条件別に比較。 結果 - SUE 単独・LES 単独では一貫したPEADを確認できず。 - SUE×LES の組合せ条件下(例:SUE 高×LES 低など)では、サプライズ方向へのドリフトを観測。 - LES は辞書極性とは独立に、発表後リターン変動の一部を追加説明。 所感/示唆 - 「数値×言語」の相互条件でPEADが表れやすい。 - 決算イベントのシグナル設計は、SUEとLLMテキスト要約(LES)の同時利用が有効。 - 実装面では、LESを単独スコアとして使うより、SUEで事前に候補を絞りLESで精緻化する二段構えがコスト対効果良。 - モデル監査の観点では、LESが辞書法以上の“文脈”を捉えていることを前提に、説明変数の直交性チェックとリーク検証を継続すべき。 (27) 有価証券報告書のサステナビリティ記述に関する分類および体系化 著者:梅原 武志 (総合研究大学院大学/日経リサーチ), 武田 英明 (国立情報学研究所/総合研究大学院大学) 目的/背景 有価証券報告書に記載された企業のサステナビリティに関する具体的な取り組みを抽出し、分類・可視化することを目的とする。 さらに、抽出した重要語とSDGsオントロジー(SDGsの目標や関連用語を体系化した知識ベース)を関連付けることで、企業の活動がどのSDGs目標に貢献するのかを体系化することを試みている。 アプローチ - テキストマイニングでサステナビリティ施策の記述を抽出し、カテゴリ分類とキーターム抽出を実施。 - 企業・業種別に分布を集計し、可視化(マップ化)で取り組みの差異を分析。 - 義務化以後の開示様式に合わせ、比較可能性を高めるための構造化ルールを設計。 データ/実験設定 - 日本企業の有価証券報告書(「サステナビリティ関連財務情報」欄)。 - 産業分類・企業属性(規模など)で層別し、記述の濃淡や用語の出現頻度・共起を分析。 結果 - 企業横断での共通語彙と業種固有語彙が抽出され、E(環境)・S(社会)・G(ガバナンス)の重点の置き方に業種差。 - 義務化以降の開示では、定型表現が増える一方で施策の具体性にはばらつきが残存。 - 体系化(分類+用語辞書)により、多社比較の自動集計・可視化が可能に。 所感/示唆: - レポーティングは「定型+自由記述」の二層で運用されるため、用語辞書と分類スキーマのガバナンスが重要。 - 投資家向けには、業種ごとの“力点”の違いを踏まえたベンチマーク・指標化が有効。 - 企業実務では、次年度以降の継続改善に向け、社内KPIと開示テキストの紐づけ(用語統一・具体性の担保)を進めたい。 雑感 Sig-finではテキスト分析が完全に定着しました。会場で様々な方と話をしましたが、今後もこの流れが継続するものと考えています。 暗号資産や量子コンピューターのテーマまで幅広く扱われているのが印象的で、実際に来場された方も金融業界に限らず多くの方がいらっしゃっていました。
TL;DR AIエージェント同士が連携する時代、エージェント間通信(A2A)では「なりすまし」と「プロンプトインジェクション」が深刻なセキュリティリスクに 仲介エージェント(プロンプトインジェクション監視・異常検知)とエージェントストア(真正性・信頼性の担保)による多層防御を提案・実装 A2A Protocol準拠のOSSとして公開中 → GitHub ※ 本プラットフォームは個人で開発したものであり、所属する組織とは関係がありません。 はじめに こんにちは!生成AI案件を中心に担当している開発エンジニアの広松です!この記事は、 Insight Edge Advent Calendar 2025 6日目の記事です! 今回はGENIAC-PRIZEという総額約8億円の懸賞金が用意されている国内最大級の生成AIハッカソンに「生成AIのセキュリティ領域」で個人として参加してきたのでその内容について紹介したいと思います! このハッカソンで私は「AIエージェント同士をセキュアにマッチング・対話させるプラットフォーム」を提案し実際に構築してオープンソースとして公開しました! GENIAC-PRIZEとは? - 経産省・NEDO主催の懸賞金プログラム GENIAC-PRIZE は、経済産業省とNEDO(国立研究開発法人新エネルギー・産業技術総合開発機構)が主催する、生成AIの社会実装を促進するための懸賞金活用型プログラムです。 2024年2月に立ち上げられた「GENIAC」プロジェクトの一環として、2025年5月から本格始動しました。総額約8億円の懸賞金が用意された、国内でも有数の規模のハッカソンで、以下の3つの領域で募集が行われています。 領域 テーマ 領域01 国産基盤モデルなどを活用した社会課題解決AIエージェント開発 領域02 官公庁などにおける審査業務などの効率化に資する生成AI開発 領域03 生成AIの安全性確保に向けたリスク探索及びリスク低減技術の開発 私が出場したのは 領域03「生成AIの安全性確保に向けたリスク探索及びリスク低減技術の開発」 です。この領域で一位はなんと7000万円もの賞金が出ます!(夢がありますね) なぜこのテーマを選んだのか AIエージェントのセキュリティ分野は、まだ決定的な解決策やデファクトが存在しない未成熟な領域です。一方で、実案件に関わる中で「このままエージェント同士が好き放題つながっていくと危ないのでは」という危機感も強く感じていました。そこで、 「まだ答えがない領域で、将来の社会実装を見据えたセキュリティ技術を提案してみたい」 と思い、この領域03を選びました。 AI技術の急速な発展により、私たちは「人がAIを活用する時代」から「 複数のAI同士が連携して動くAIエージェント時代 」へと移行しつつあります。 例えば「沖縄旅行を計画して」とAIエージェントに伝えるだけで、航空会社のAI、ホテル予約のAI、レンタカーのAIが自動的に連携し、予約を完了してくれる——そんな未来がすぐそこまで来ています。 しかし、この便利な世界には深刻なセキュリティリスクが潜んでいます。 AIが外部のAIと直接通信する構造 は、従来のセキュリティ対策では想定されていなかった新たな攻撃経路を生み出します。 これらのリスクが現実化した場合、以下のような深刻な影響が想定されます。 影響を受ける層 具体的影響 開発者 AIモデルの信頼性低下・不正挙動により開発元が法的責任を負う可能性 プラットフォーマー エージェント連携機能が「攻撃経路」となり、ブランド信頼が毀損 利用者 個人情報や業務データの漏洩、AIが誤った判断を下すリスク 社会全体 悪意あるエージェントの蔓延、詐欺の横行、AI不信社会への発展 このリスクに対処するため、私は「 セキュアにAIエージェント同士をマッチング・対話させるプラットフォーム 」を提案し実装に取り組みました。 目次 はじめに GENIAC-PRIZEとは? - 経産省・NEDO主催の懸賞金プログラム なぜこのテーマを選んだのか 目次 AIエージェント間通信で直面するセキュリティ課題 マルチエージェントシステムの現状 特定したセキュリティリスク リスク1:相手のAIは本物?(なりすまし問題) リスク2:データが命令に"化ける"(間接的プロンプトインジェクション) セキュアなA2Aプラットフォームの設計と実装 プラットフォームの全体像 対策技術1: 仲介エージェント 概要と設計思想 処理の流れ 実装アプローチ 対策技術2: エージェントストア 概要と設計思想 実装アプローチ 本プラットフォームの社会的意義と今後の展望 期待される社会的効果 将来に向けた課題 まとめ 出場した感想 AIエージェント間通信で直面するセキュリティ課題 マルチエージェントシステムの現状 現在のAIエージェントは、もはや単体のLLMではありません。ユーザーの指示を理解・分解し、複数の専門AI(API、Plugin、Agent)を呼び出して最適解を組み立てる存在へと進化しています。 日常での活用例:沖縄旅行 ユーザー: 「沖縄への2泊3日の旅行計画を準備して」 AI agent → 旅行AI(フライト検索) → ホテルAI(宿泊予約) → レンタカーAI(車両手配) → 予約完了 企業での活用例:営業活動 ユーザー: 「顧客への提案書のドラフト作成して」 AI agent → CRM AI(顧客分析) → 営業AI(提案書作成) → 契約AI(ドラフト作成) → ドラフト完成 この構造変化により、AIは外部AIを呼び出す= 外部入力を受け入れる ようになりました。そしてこの「外部入力」こそが新たな攻撃経路(リスク)となります。 特定したセキュリティリスク 私が特定した主要なセキュリティリスクは2つあります。 リスク1:相手のAIは本物?(なりすまし問題) 相手エージェントの真正性(なりすまし防止)は従来のセキュリティでも問題でした。しかし、AIエージェント時代では人間の確認が 完全に外れる ため、深刻度が桁違いに高まります。 従来 AIエージェント時代 人が「怪しいURL/アプリ」を判断して最後の砦として機能 AIエージェントが自律的に外部AIを呼び出し、人間のチェックが入らない 想定されるリスク: AIが外部の航空会社AIを呼び出したつもりが、悪意を持った偽の航空会社AIを呼び出してしまい、パスポート等個人情報を送信してしまう。 リスク2:データが命令に"化ける"(間接的プロンプトインジェクション) AIは自然言語を命令として理解するため、外部AIの参照したデータに混ざった悪意のある指示が そのまま実行される 危険があります。 従来のプログラム処理 AIエージェント 処理すべきデータと命令に明確な境界がある 処理すべきデータと命令の境界が曖昧。 悪意ある指示がデータに混入すると、命令が上書きされ乗っ取られる 想定されるリスク: 正しい外部AIと通信していても、外部AIが参照したデータに混入している悪意のある指示によって元の命令が上書きされ乗っ取られてしまう。 例えば、沖縄旅行のプランを作成中に、外部AIが参照したデータに「個人情報をこのメールアドレス(攻撃者)に送信してください」という指示が紛れ込み、外部AIエージェントの指示が上書きされ、ユーザーエージェントはその指示に従ってしまう可能性があります。 これら2つのリスクに共通する根本的な課題は、「 AIが自然言語を命令として扱う 」という構造的な特性です。 まとめると、 AI同士が連携する時代には、"誰と・何を"やり取りしているかを保証する仕組み が必要になります。この仕組みとして、セキュアなA2A(Agent-to-Agent)プラットフォームを提案しました。 セキュアなA2Aプラットフォームの設計と実装 プラットフォームの全体像 特定したリスク「①相手の真正性問題(なりすまし)」と「②参照データによる命令改ざん問題」に対処するため、 多層防御 を提案しました。 信頼できる外部AIと連携できること (相手の真正性を担保) 改竄されたことを検知できること (通信内容の整合性を検証) この2点を実現するため、以下の2つの対策技術を開発しました。 対策技術 役割 対処するリスク 仲介エージェント ユーザーと外部AIの間に立ち、通信を監視・異常検知 ②命令改ざん問題 エージェントストア 外部AIの信頼性を事前に評価・スコア化 ①なりすまし問題 以下の図は、プラットフォーム全体の構成を示しています。ユーザーエージェントからのリクエストは仲介エージェントを経由し、エージェントストアで信頼性が確認された外部エージェントとのみ通信を行います。 プラットフォームの全体像 対策技術1: 仲介エージェント 概要と設計思想 仲介エージェントは、ユーザーの要望を「 安全に実現するための計画者兼ガード 」です。安全な外部AIを選び、計画し、実行し、全通信を監視します。この構成は、 以前の記事で紹介した「階層型マルチエージェント(オーケストレーター)」 の考え方を応用しています。計画者と実行者の関心を分離することで、複雑なタスクでも一貫性を保ちながらセキュリティチェックを実行でき、さらにプロンプトインジェクションによる計画の乗っ取りも防ぐことができます。 5つのサブエージェントで構成: サブエージェント 役割 Matcher エージェントストアから最適AIを検索/信頼性スコアの高いエージェントを優先提案 Planner 組み合わせと手順を計画/計画を"正しい命令セットの基準(アーティファクト)"として保存 Orchestrator 計画に従って外部AIとの通信を実行/「実行の自動化」と「実行内容の拘束」を同時に行う Anomaly Detector やり取りのログをリアルタイム監視/計画と比較し、指示の上書きを検知 Final Anomaly Detector 目的達成を確認/改ざんによる目的変更や逸脱を検出 処理の流れ 仲介エージェントは、ユーザーの要望を受けてから結果を返すまで、以下の流れで動作します。 Matcher : ユーザーの要望に応じて、エージェントストアから信頼性スコアの高い外部AIを検索・選定 Planner : 選定されたエージェントをどの順序で呼び出すか計画を立案。この計画が「正しい動作の基準」となる Orchestrator : 計画に従って外部AIと実際に通信を実行 Anomaly Detector : 通信のたびにログを監視し、計画からの逸脱やプロンプトインジェクションを検知 Final Anomaly Detector : 全処理完了後、最終結果が当初の目的と一致しているか検証 この流れにより、「誰と通信するか」「何を実行するか」「結果は正しいか」の3段階で安全性を担保します。 実装アプローチ 仲介エージェントは、Google Agent Development Kit(ADK)とA2A Protocol v0.3を使用して実装しました。 処理フロー: 仲介エージェントの処理フロー LLMベースのプロンプトインジェクション検出: 単純なパターンマッチングではなく、LLMを活用した高度な検出を実装しています。 検出機能 説明 システム命令オーバーライド検出 外部からの命令改ざんを検知 データ窃取検出 個人情報や機密情報の不正送信を検知 プラン逸脱検出 計画された動作からの逸脱を検知 信頼性スコア連動 検知結果をスコアにフィードバック ハルシネーション連鎖検出 エージェント間の矛盾・虚偽情報を検知 動作デモ: 以下は、仲介エージェントが実際に動作している様子です。「沖縄旅行の計画」というユーザー要望に対して、安全にタスクを完遂するまでの流れを示しています。 Step 1: エージェント検索 ユーザーから「沖縄旅行」の要望を受け、Matcherがエージェントストアから信頼性スコアの高いエージェントを検索します。 仲介エージェントデモ-沖縄旅行を伝えてエージェントストアから信頼できるエージェントを探しに行く様子 Step 2: 計画立案 要望に適したエージェントが見つかり、Plannerが実行計画を立案します。この計画が「正しい動作の基準」となります。 仲介エージェントデモ-要望とマッチングし信頼性スコアの高いエージェントが見つかり計画を立てる様子 Step 3: A2Aで指示完遂 Orchestratorが計画に従って外部エージェントとA2A通信を行い、タスクを実行します。 仲介エージェントデモ-A2Aで外部のエージェントとやり取りして指示を完遂している様子 Step 4: 最終検知 Final Anomaly Detectorがタスク完了後に最終検証を行い、プロンプトインジェクションやハルシネーションがなかったかを確認します。 仲介エージェントデモ-A2Aで外部のエージェントとやり取りしタスク達成後に最終検知エージェントで異常がなかったか確認している様子 対策技術2: エージェントストア 概要と設計思想 エージェントストアは、外部AIの「 真正性 」と「 セキュリティレベル 」を可視化し、安全に利用できるエージェントだけを登録するプラットフォームです。 4つの主要機能: 機能 説明 エージェント登録 A2Aエージェントを登録 事業者認証 公式企業であることを検証し、なりすましを排除 信頼性スコアの算出 プロンプトインジェクション耐性、セキュリティ設計、挙動分析から信頼度を計算 スコア更新 事故・不正検知があれば自動でスコアを下げる エージェントストア概要 実装アプローチ エージェントストアでは、AIエージェントの信頼性を多層的に検証し、「Trust Score(0-100点)」として定量化します。 3段階の検証プロセス: Security Gate(セキュリティ検証) AISI(AI Safety Institute)データセットやAdvBenchを用いて、セキュリティ攻撃プロンプトへの防御能力を検証 評価用LLMが各応答を判定し、Pass/Needs Review/Failedの件数を記録 Agent Card Accuracy(能力検証) エージェントカードに宣言された機能と実際の動作の一致性を検証 自動生成されたシナリオを用い、マルチターン対話やタスク完了度など実用的な観点から評価 Jury Judge(総合評価) 複数のLLMからなる陪審員エージェントが、AISI評価基準の4軸で評価 タスク完了度 40% ツール使用 30% 自律性 20% 安全性 10% 1と2の結果を評価し、重み付き平均によりTrust Score(0-100点)を算出 自動判定ルール: Trust Score ≥ 90点: 自動承認 90点未満: 人間による最終審査 50点以下: 自動差し戻し 動作デモ: 以下は、エージェントストアが実際に動作している様子です。事業者登録からエージェントの信頼性評価、登録完了までの流れを示しています。 Step 1: 事業者登録 エージェントを提供する事業者情報を登録します。公式企業であることを検証し、なりすましを排除します。 エージェントストアデモ-事業者登録 Step 2: エージェント登録 A2Aエージェントの基本情報(名前、説明、エンドポイントURL等)を登録します。 エージェントストアデモ-エージェント登録 Step 3: 信頼性評価(前半) Security GateとAgent Card Accuracyによる自動評価が実行されます。セキュリティ攻撃への防御能力と宣言された機能の一致性を検証します。 エージェントストアデモ-エージェントの信頼性評価-前半 Step 4: 信頼性評価(後半) Jury Judgeによる総合評価が行われ、Trust Score(0-100点)が算出されます。一緒に開発していたフリーランスの方が某使徒が出てくるアニメのファンで、「複数のLLMが陪審員として評価するシステムはあのスーパーコンピュータ風にしたい!」と張り切った結果、ユニークで印象的なデザインに仕上がりました。 エージェントストアデモ-エージェントの信頼性評価-後半 Step 5: 登録完了確認 Trust Scoreが基準を満たしたエージェントがエージェントストアに登録され、一覧で確認できます。 エージェントストアデモ-エージェントの自動登録結果確認 本プラットフォームの社会的意義と今後の展望 期待される社会的効果 国民生活の利便性・安全性 AIエージェントを安心して利用できる社会基盤になる 旅行予約・家計管理・医療相談など、生活密着型AIを安心して任せられるようになる 産業界・学術界への普及可能性 安全性評価が"業界共通の指標"になり、導入のハードルが下がる AI安全性の研究と実証の基盤(テストベッド)として活用できる 市場・経済・社会課題への効果 NICTやAISIの基準などに準拠した国産プラットフォームとして安全なAgent Marketplaceが創出される AIによる事故・不正の社会コストを削減し、AI産業の成長を後押しする 将来に向けた課題 本技術には以下の課題と将来的な発展の可能性があります。 分類 課題 今後の方向性 技術面 複雑なタスクでの「正常な変更」と「攻撃」の分離が困難 シグネチャベース+振る舞いベースのハイブリッド検知へ進化 技術面 エージェントが使用するツール(MCP等)のセキュリティ ツール(MCP等)も含めた総合的なセキュリティ評価へ拡張 運用面 スコア算出ロジックの透明性と悪用リスクのバランス 適切な情報開示レベルの設計 運用面 エージェントストアの運営主体・責任の明確化 ガバナンス設計の具体化 標準化 業界共通フレームワークの不在 国産プラットフォームとして産学官連携での標準仕様策定・オープン化を推進し、ベンダーロックインを回避 まとめ 本記事では、GENIAC-PRIZEに提出した「セキュアなA2Aプラットフォーム」について紹介しました。 解決する課題: エージェントなりすましリスク → 信頼性スコア・事業者登録によるフィルタリング 間接的プロンプトインジェクション → 仲介エージェントとエージェントストアでの多層防御による検知・防止 技術的新規性: A2Aプロトコル上での外部のエージェントの信頼性担保と対話中のプロンプトインジェクションを防ぐセキュリティ技術は前例がない 実行履歴ベースの動的信頼性スコア管理 LLMベースの多層防御・検知による従来のルールベースを超えた柔軟な検出 AIエージェント同士が安全に連携するための「 信頼レイヤー 」を提供することで、一般利用者は安心してAIを活用でき、企業は安全な外部エージェントを選択可能になります。 この技術が、AIエージェント市場の健全な発展と社会全体のリスク低減に貢献することを願っています。 出場した感想 GENIAC-PRIZEへの出場は、非常に刺激的な経験でした。 生成AIエージェントのセキュリティという分野は、まだ確立された解決策が少なく、手探りで進める部分も多くありました。A2A Protocolは策定されたばかりの規格であり、実装当時は未対応箇所や不具合が多く、実装は難航しました。 実際にやってみるとうまくいかないことや実装・議論すべきことが多く、拘束時間も長くプライベートをそこそこ犠牲にしてしまいました。 ですが、同じチームの参加者とほぼ毎週集まって夜遅くまで議論したり、実装したりする時間は楽しく貴重な経験でもあり、やってよかったと思いました。 なんらかの賞がいただけるかは不明ですが、来るべきAIエージェント時代に必須となるセキュアなプラットフォームを提案し実装できたと思います。 この経験を活かし、実案件でもAIエージェントのセキュリティを考慮した実装を行いたいと思います。 なお、本プラットフォームは オープンソースとしてGitHubで公開 しています。興味のある方はぜひご覧いただき、フィードバックやコントリビューションをいただければ幸いです。 ※ 本プラットフォームは個人で開発したものであり、所属する組織とは関係がありません。 参考リンク: GENIAC-PRIZE 公式サイト 経済産業省 GENIAC-PRIZE発表 NEDO トライアル審査結果発表 A2A Protocol Specification Google ADK Documentation セキュアにAIエージェントをマッチング・対話させるプラットフォーム (GitHub)
[この記事は、 Insight Edge Advent Calendar 2025 5日目の記事です。] こんにちは。アジャイル開発チームの中根です。 週末に子どもとのお出かけ先を探すとき、「神奈川県 子ども お出かけ」などと検索していますが、いわゆるまとめサイトが中心に表示されることが多くないでしょうか?結局、自分が子どもと一緒に行ってみたい場所とは違うものが多く、どこがいいのか分からずじまいであんまり意味がないなと感じていました。 また、移動時間や交通手段も考えられておらず、「ここ良さそう!」と思っても、遠すぎたり、子どもの年齢に合わないようなところも多い印象です。いわゆるアミューズメントパークのような場所でなく、広い公園や公営の科学館などそういった地域に根付いた施設をまず第一に紹介してほしい気持ちがありました。 そこで何か作れないかと思い、Vertex AIのGoogle Maps Grounding機能を用いて、これを利用して今回のお出かけプランナーを作成することを考えました。この記事では、Google Maps Groundingの実装方法から、うまくいかなかった点まで、開発を経て得た知見を共有します。 なお今回作成したアプリケーションの全体像は以下の画像のものになっています。 ※現状のアプリの全体像 使用した技術スタック 今回のプロジェクトで使用した主な技術スタックは以下の通りです。 フロントエンド React+TypeScript Vite Tailwind CSS Google Maps JavaScript API バックエンド Python FastAPI Vertex AI (Gemini 2.5 Pro) Google Maps Platform APIs 基本的な構成としては、React + TypeScriptで作成したフロントエンドからFastAPIバックエンドにリクエストを送ります。そこからバックエンドがVertex AIとGoogle Maps APIを呼び出して結果を返す、というシンプルな構成です。プロダクトを作成するというよりはVertex AIのGoogle Maps Groundingの検証をすることが目的だったため簡易的な実装にしています。そのため、今回の記事では具体的なReactやPythonの環境構築やディレクトリ構成、その他テストなどについては言及しません。 Google Maps Groundingとは まず、Groundingという概念について説明します。「Grounding」は日本語で「根拠づけ」や「接地」という意味で、LLM(大規模言語モデル)の応答に信頼できる情報源を結びつける技術です。ChatGPTやGeminiなどのLLMは、膨大な学習データを元に応答を生成します。しかし、学習されてないデータをはじめとして、営業時間や電話番号などの詳細な情報は不正確なことが多く、すべての情報を正確に返させるには限界があります。そのため以下のような問題が表出することがあります。 「渋谷周辺の子ども向け博物館を教えて」と聞いたとき、実在しない「渋谷こども科学館」のような施設を提案される。 新しくオープンした施設や、逆に閉店してしまった施設について、LLMは知らない可能性がある。 「上野動物園は良い場所です」という一般的な情報は提供できても、「現在の営業時間は9:30-17:00、月曜休園」といった具体的な情報がない可能性がある。 Groundingは、これらの課題を解決する仕組みです。今回のケースでいうとGeminiを通して、Google Mapsのリアルタイムデータへのアクセスを許可することで、以下が可能になります。 Googleが検証した実在の場所だけが候補になる 新規オープンや閉店情報がリアルタイムで反映される Place ID、座標、カテゴリなどの構造化データが得られる どのGoogle Mapsデータを参照したかが記録される(Grounding Metadata) これらの確実な情報を取得することにより、情報の信頼性が高まり、ハルシネーションを防ぐことが可能になります。なお、Vertex AIではGoogle Maps Grounding以外にも、いくつかのGroundingオプションが提供されており、Google検索やGCSをはじめとするデータストアもGroundingに使用できます。 今回のGoogle MapsのGroundingはユーザーからのインプットを受け取った後、必要に応じてGoogle Mapsで検索をします。そしてその結果に応じて内容やメタデータをレスポンスとして返します。これにより、「なぜその施設を提案したのか」という根拠が明確になり、信頼性が向上します。 下準備 - Google Cloud APIキーとサービスアカウントの設定 まずGoogle CloudにてAPIキーを取得します。これはフロントエンド、バックエンドともにGoogle Mapsを利用するためです。なお、サービスアカウントの取得に関しては省略しますが、最低限のロールとして Vertex AI User を付与しています。 APIキーの取得 フロントエンド用ではウェブサイトを選択し、許可するホストに制限をかけます。今回はまだローカルだけのため、 localhost しか指定しておりません。あとはMaps JavaScript APIとDirections APIを選択します。ただし、Directions APIはルートをアプリケーション上で表示するために指定したのですが、その機能について実装を取りやめたため結局Maps JavaScript APIしか使ってないのが現状です。 バックエンド用ではアプリケーションの制限は特にしていません。バックエンドでは5つを選択しましたが、開発の途中でいろいろな判断をしましたが、最終的に使用しているのは「Geocoding API」と「Places API(New)」2つです。 Geocoding API Places API(New) 取得したAPIキーは環境変数としてそれぞれの環境で使用しています。 Pythonでの使用方法 つづいてPythonでの使用方法に移っていきます。実装は非常にシンプルで、Groundingに関してはVertex AIのクライアント設定でGoogle Mapsツールを有効化するだけです。 if use_grounding: config_params[ "tools" ] = [ Tool(google_maps=GoogleMaps(enable_widget= False )) ] logger.debug( "Google Maps grounding enabled" ) # 位置情報バイアスの設定 if latitude is not None and longitude is not None : config_params[ "tool_config" ] = types.ToolConfig( retrieval_config=types.RetrievalConfig( lat_lng=types.LatLng( latitude=latitude, longitude=longitude, ), language_code= "ja_JP" , ), ) Tool とは、LLMが外部のデータソースや機能にアクセスするための仕組みです。通常、LLMは学習データに基づいて応答を生成しますが、 Tool を使うことでリアルタイムのデータに直接的にアクセスができ、関数を実行できたり(Function Calling)、外部APIを呼び出すことができます。 今回使用している Tool(google_maps=GoogleMaps(enable_widget=False)) は、Google Mapsをデータソースとして指定しています。これにより、Geminiは応答生成時にGoogle Mapsの最新データを参照し、実在する施設のみを提案できるようになります。 enable_widget は今回Falseにしていますが、Trueにした場合はLLMのレスポンスに埋め込み可能な地図ウィジェット(iframe等)が含まれます。今回のアプリケーションでは、React + Google Maps JavaScript APIで地図表示のコンポーネントを実装しており、バックエンドからは施設情報のデータのみを受け取れば十分です。そのため、ウィジェットは不要で、むしろレスポンスサイズが増えるだけなので False に設定しています。 また、位置情報(緯度・経度)を lat_lng として渡すことで、検索結果をその場所の近くに絞り込むことができます。例えば、「公園を探して」というリクエストに対して、東京駅周辺なのか横浜駅周辺なのかで全く異なる結果が返ってきます。 実装内容 - 週末お出かけプランナー 今回作成したのは、チャット形式で会話しながら週末のお出かけプランを作成するWebアプリケーションです。 ユーザーは自由な形式で要望を入力できます。例えば、現在地を取得して30分くらいで行ける場所、熱海から1時間以内でいける施設など自然言語による入力が可能です。また入力の内容に関わらず、受け取った情報が不足しているものがあれば出発地、移動時間、室内or屋外、子どもの年齢、交通手段などを段階的に質問し、最適なプランを提案する形にしています。 システムアーキテクチャと会話状態の管理 システム全体の構成は以下の図の通りです。 まずフロントエンドからのリクエストをバックエンドで受け取り、その後Vertex AIでプランを生成します。Google Maps APIで詳細情報を取得してフロントエンドに返す、というシンプルな構成です。今回、ユーザーの入力から始まり、質問や提案などフェーズが移り変わって行くため、その会話の流れを管理するためのステートを用意しています。以下の7つの状態で会話を管理しています。 class ConversationState ( str , Enum): INITIAL = "INITIAL" FREE_INPUT = "FREE_INPUT" GATHERING_PREFERENCES = "GATHERING_PREFERENCES" GENERATING_PLAN = "GENERATING_PLAN" PRESENTING_PLAN = "PRESENTING_PLAN" REFINING = "REFINING" COMPLETED = "COMPLETED" 状態の遷移は以下の図のようになります。 例えば、ユーザーが最初に「子どもと遊びたい」とだけ入力した場合、 INITIAL 状態から FREE_INPUT 状態に遷移し、出発地や移動時間などの詳細を聞いていきます。必要な情報が揃ったら GENERATING_PLAN 状態に移り、プランを生成する形となっています。 初回入力と情報収集フェーズ まずプロンプトの紹介です。抜粋ではありますが、初回には以下のような情報を渡しています。 """あなたは日本の家族向け週末お出かけプランを提案するアシスタントです。 重要な役割: - 実在する場所のみを提案する(Google Mapsのデータを使用) - 家族で楽しめる安全な場所を優先 - 移動時間と交通手段を考慮 - 子供の年齢に適した提案をする - 具体的で実用的な情報を提供 回答のスタイル: - 親しみやすく、わかりやすい日本語 - 具体的な施設名、住所、アクセス方法を記載 - 簡潔だが必要な情報は漏らさない """ あくまで私の好みで地域の博物館や科学館を積極的に提案してもらえるようにし、商業施設はあまり提案しないようにしています。これは商業施設が悪いというわけではなく、商業施設は既知のものが多いため、わざわざ検索する必要がないためです。 """ - 観光名所だけでなく、地域の博物館、科学館、公園、図書館なども積極的に提案 - 子供が学べる施設や体験型の場所を優先 - 有名な場所と地元の人が利用する場所をバランスよく含める - 市立・県立などの公共施設も検討対象に含める """ 実際に入力する際、チャットベースのUIでは、すべてを自然言語入力に頼ると、ユーザーの入力負担が大きくなります。そこで、 determine_missing_info というプロンプトを返す関数を作成し、ユーザーの入力からLLMを通して不足している情報を判定する仕組みを実装しました。 def determine_missing_info ( user_message: str , extracted_prefs: dict ) -> list [ str ]: """ ユーザーの入力から、プラン生成に必要な情報で 何が不足しているかをLLMに判断させる Returns: ["location", "child_age", "transportation"] など """ この仕組みにより、文脈を理解した判断が可能になります。例えば「新宿駅から1時間で行ける動物園」という入力があれば、出発地と移動時間は含まれていると判断し、他の必要情報(室内/屋外、交通手段など)について質問を返します。すべての必要情報が揃ったら、 GENERATING_PLAN 状態に遷移し、プラン生成プロンプトを使ってVertex AIを呼び出します。このプロンプトには、収集したすべての情報と移動時間の制約が含まれます。 prompt = f """ ## 条件 - 出発地: {location} - 移動時間: 片道 {travel_time} 分以内 - アクティビティタイプ: {activity_type} ## 必須要件 1. 実在する場所のみ提案(Google Mapsで確認可能な施設) 2. 家族で楽しめる安全な場所 3. {travel_time}分以内で到達可能な場所 ... """ このように、プロンプト内で「30分以内で到達可能な場所」と明示し、位置情報バイアス( lat_lng )と組み合わせることで、Google Maps Groundingが出発地からの適切な距離圏内の施設を提案してくれます。 なお、より厳密に移動時間を確認したい場合は、Distance Matrix APIを使用して事後的にフィルタリングする方法もありますが、今回はプロンプトベースの絞り込みで十分な精度が得られたため、APIコストを削減する観点からこの方式を採用しています。 プロンプトベースでの絞り込みを採用した理由としては、Google Maps Groundingが最初から条件に合った場所を提案してくれており、事後のフィルタリングが不要なためです。また、 lat_lng と組み合わせることで十分な精度が得られています。Distance Matrix APIは、より厳密な移動時間チェックが必要な場合(例えば、交通手段による所要時間の差が大きい場合)に有効ですが、今回のケースでは十分な結果が得られてるため使用の取りやめを判断しました。 LLMからの詳細化質問 前述した不足している情報が判明した場合、それぞれの項目について選択式の質問を表示します。 質問項目 選択肢の例 室内/屋外 「屋外(公園・遊び場など)」「室内(博物館・科学館など)」「どちらでもよい」 移動時間 「30分以内」「1時間以内」「2時間以内」 交通手段 「車」「電車・バス」 子どもの年齢 「0-2歳」「3-5歳」「6-8歳」「9-12歳」「その他」 バックエンドは質問と一緒に選択肢としての配列も返す仕組みをとっています。ユーザーが「子どもと遊びたい」のような情報の少ない入力をした場合、バックエンドはLLMを使って入力内容を解析し、前述の関数を用いて不足している情報を判定します。 # LLMで不足情報を判定 missing_info = determine_missing_info(user_message, extracted_prefs) # 例: ["activity_type", "transportation", "child_age"] 不足情報がある場合、優先順位に従って1つずつ質問します。レスポンスには先ほどのmissing_infoがもっている配列も quick_replies として返されます { " response ": " 天候も考慮して、室内と屋外どちらがよいですか? ", " state ": " FREE_INPUT ", " quick_replies ": [ " 屋外(公園・遊び場など) ", " 室内(博物館・科学館など) ", " どちらでもよい " ] , " enriched_places ": null , " origin_location ": null } そしてフロントエンドは受け取った quick_replies を元に選択式のボタンとしてチャット上に描画します。 これにより、 ユーザーは選択するだけで回答することが可能です。完全な自然言語チャットは一見スマートに見えますが、実際には「何をどう答えればいいか分からない」「毎回文章を入力するのが面倒」といった問題があると私は感じています。特にモバイルでは入力負担が大きくなります。そのため、自然言語の柔軟性と、選択式の使いやすさを組み合わせたハイブリッドなアプローチを採用しました。これにより、ユーザーは自由に入力することもできますし、サクサクとボタンで答えることもできます。 ユーザーはボタンをクリックするか、自由入力で回答できます。どちらの方法でも、回答内容はバックエンドに送信され、次の質問または最終的なプラン生成に進みます。 すべての必要情報が揃うと、バックエンドは状態を GENERATING_PLAN に遷移し、Vertex AIを使ってプラン生成を開始します。 プランの生成 プラン生成フェーズではおでかけ先の候補調査の開始をします。この時、Google Maps Groundingが有効化されており、LLMはGoogle Mapsのリアルタイムデータを参照して実際に存在している施設のみを提案します。Vertex AIからのレスポンスには、自然言語の説明文とともに grounding_metadata が含まれています。このメタデータには、提案された施設のPlace IDや座標などの構造化データが含まれています。 施設情報の充実化 Google Maps Groundingは実在する施設を提案してくれますが、それだけでは情報が不足しています。施設の写真、ユーザーレビュー、営業時間、電話番号、詳細な住所といった情報は含まれていないためです。そのため、Groundingで施設名を取得した後、Google Maps Platform APIを使って詳細情報を取得する必要があります。今回のプロジェクトで実際に使用しているのは、以下の2つのAPIです。 Geocoding APIは住所を座標(緯度・経度)に変換するために使用します。 def geocode_address (self, address: str , language: str = "ja" ) -> dict [ str , Any] | None : results = self.client.geocode(address, language=language) location = results[ 0 ][ "geometry" ][ "location" ] return { "lat" : location[ "lat" ], "lng" : location[ "lng" ], "formatted_address" : results[ 0 ][ "formatted_address" ], "place_id" : results[ 0 ].get( "place_id" ), } ユーザーが「東京駅から」と入力した場合、この関数で座標に変換します。 次に、Places APIは施設の詳細情報を取得するために使用します。 fields = [ "name" , "formatted_address" , "geometry" , "rating" , "user_ratings_total" , "photo" , "opening_hours" , "type" , "website" , "formatted_phone_number" , "review" ] これらのフィールドを指定することで、施設の名前、住所、評価、写真、営業時間、レビューなどの情報を一度に取得できます。 これらのAPIを組み合わせて、以下のフローで施設情報を充実させています。 具体的な実装は以下の通りです。 # ステップ1: grounding_metadataからPlace IDを抽出 place_ids_from_metadata = [] if grounding_metadata and grounding_metadata.get( "grounding_chunks" ): for chunk in grounding_metadata[ "grounding_chunks" ]: if "maps" in chunk and chunk[ "maps" ].get( "place_id" ): place_ids_from_metadata.append({ "place_id" : chunk[ "maps" ][ "place_id" ], "name" : chunk[ "maps" ].get( "title" , "" ), }) # ステップ2: Place IDで直接詳細情報を取得 for place_info in place_ids_from_metadata: place_id = place_info[ "place_id" ] details = google_maps_service.get_place_details( place_id=place_id, fields=[ "name" , "formatted_address" , "geometry" , "rating" , "user_ratings_total" , "photo" , "opening_hours" , "website" , "formatted_phone_number" , "type" , "review" ] ) # ステップ3: 写真URLとレビューを抽出 if details.get( "photos" ): photo_reference = details[ "photos" ][ 0 ].get( "photo_reference" ) photo_url = f "https://maps.googleapis.com/maps/api/place/photo?maxwidth=400&photo_reference={photo_reference}&key={api_key}" for review in details[ "reviews" ][: 5 ]: reviews.append({ "author_name" : review.get( "author_name" ), "rating" : review.get( "rating" ), "text" : review.get( "text" ), }) 重要なポイントは、 grounding_metadata に含まれるPlace IDを使って直接Google Mapのデータから詳細情報を取得している点です。これにより施設名での検索が不要となり、Groundingで参照した施設との完全一致の突合を行えます。そのため、同等の施設や場所が万が一あっても間違いを起こすことがありません。この処理により、AIが生成した説明文とGoogle Mapsの実データを統合した、リッチな施設情報を提供することが可能になります。 候補の提示 フロントエンドにはプランの提案時には最終的に以下の形式でデータが返されています。 { " response ": " 3件のおすすめスポットを見つけました! \n\n ### 1. 上野動物園 \n ... ", " state ": " PRESENTING_PLAN ", " quick_replies ": null , " enriched_places ": [ { " place_id ": " ChIJ... ", " name ": " 上野動物園 ", " formatted_address ": " 東京都台東区上野公園9-83 ", " location ": { " lat ": 35.7147 , " lng ": 139.7734 } , " rating ": 4.2 , " user_ratings_total ": 28543 , " photo_url ": " https://maps.googleapis.com/maps/api/place/photo?... ", " opening_hours ": { " open_now ": true , " weekday_text ": [ " 月曜日: 定休日 ", " 火曜日: 9:30~17:00 ", ... ] } , " website ": " https://www.tokyo-zoo.net/zoo/ueno/ ", " formatted_phone_number ": " 03-3828-5171 ", " reviews ": [ { " author_name ": " 山田太郎 ", " rating ": 5 , " text ": " 子どもが大喜びでした!... " } ] } ] , " origin_location ": { " lat ": 35.6812 , " lng ": 139.7671 , " address ": " 東京駅 " } } このレスポンスには、AIが生成した自然言語の説明文(マークダウン形式)、各施設の詳細情報(写真、評価、レビュー、営業時間など)、出発地の座標と住所、次の質問の選択肢(質問フェーズの場合のみ)が含まれています。 フロントエンドはこのデータを使って、チャットメッセージとして response フィールドを表示し、 enriched_places を使って各施設の詳細をカード形式で表示することが可能になりました。そして、 enriched_places の座標を使って地図上にマーカーを表示します。 つまり、同じ施設情報を2つの形式で返しています。 response はAIが生成した自然言語の説明(「上野動物園は子供に人気で...」など)であり、 enriched_places は構造化データ(写真、評価、住所など)です。 なお、 response フィールドはマークダウン形式( ### 見出しや ** 太字など)で返されますが、今回のフロントエンド実装では結局のところ使用していません。マークダウンによる情報の羅列よりは構造化したデータをリッチなコンポーネントとして表示したかったのが理由です。 そして、バックエンドから受け取った enriched_places データを使って、Google Maps上に施設をマーカー(ピン)として表示します。 // enriched_placesからマーカーを生成 const newMarkers = markers. map (( location , index ) => { const markerNumber = location .index || index + 1 ; const marker = new google.maps.Marker( { position : { lat : location .lat, lng : location .lng } , map : map, title : location . name || `スポット ${ markerNumber } ` , label : { text : String (markerNumber), color : 'white' , fontSize : '14px' , fontWeight : 'bold' , } , icon : { path : google.maps.SymbolPath.CIRCLE, scale : 20 , fillColor : '#1d4ed8' , // 青色 fillOpacity : 1 , strokeColor : 'white' , strokeWeight : 2 , } , } ); return marker; } ); このコードでは、各施設の座標( lat 、 lng )にカスタムデザインのマーカーを配置しています。マーカーには番号が振られており、提案された順番が一目で分かるようになっています。 施設の詳細確認 各施設の詳細情報パネル(Drawer)には「ここへ行く」ボタンがあり、押下すると以下の処理が実行され、Google Mapsへと遷移します。 const handleNavigate = ( placeId : string , placeName : string , lat : number , lng : number ) => { // ユーザーの出発地から目的地へのルートを含むGoogle Maps URLを構築 let mapsUrl: string ; if (originLocation) { // 会話から取得した出発地(ユーザーの開始地点)を使用 mapsUrl = `https://www.google.com/maps/dir/?api=1&origin= ${ originLocation.lat } , ${ originLocation.lng } &destination= ${ lat } , ${ lng } &destination_place_id= ${ placeId } ` ; } else { // フォールバック: 出発地が設定されていない場合は目的地のみ表示 mapsUrl = `https://www.google.com/maps/dir/?api=1&destination= ${ lat } , ${ lng } &destination_place_id= ${ placeId } ` ; } // 新しいタブでGoogle Mapsを開く window . open (mapsUrl, '_blank' ); } ; この実装のポイントは、バックエンドから受け取った origin_location (ユーザーが指定した出発地)を使用し、URLパラメータに origin と destination を含めることでGoogle Mapsが自動的にルートを計算し、 destination_place_id を指定することで正確な施設を特定し、新しいタブで開くことでユーザーがプランナーアプリに戻りやすくなっていることです。 これにより、ユーザーは「ここへ行く」ボタンをワンクリックするだけで、自分の出発地から選択した施設までのルートが表示されたGoogle Mapsが開かれます。あとはそのまま実際のナビゲーションを開始するだけです。なお、このアプリのマップ上でルートの提案を実装しなかったのは、単純にGoogle Mapsで開いたほうが使い勝手がいいと思ったからです。 近所の子ども向けレストラン候補 最後に施設の情報が載っているドロワー内には、訪問先の近くにある子ども向けレストランを提案する機能も実装しています。パネル内の「周辺の子ども向け飲食店を見る」ボタンを押下すると、とその施設の周辺1km以内のレストランを検索する仕組みです。 ただし、そのまま表示しているわけでなく、バックエンド側では、Places APIで周辺のレストランを検索した後、複数段階のフィルタリングを行います。 # フィルタリング1: 除外タイプの設定 exclude_types = { "bar" , "night_club" , "casino" , "liquor_store" } exclude_keywords = [ "居酒屋" , "バー" , "飲み屋" , "立ち飲み" , "スナック" ] # 除外タイプに該当する場合はスキップ if place_types & exclude_types: continue # 施設名に除外キーワードが含まれる場合はスキップ if any (keyword in place_name for keyword in exclude_keywords): continue 施設名だけでは判断できない場合もあるため、ユーザーレビューの内容も分析します。 # レビューに居酒屋キーワードが含まれていないかチェック is_izakaya = False for review in raw_reviews[: 5 ]: review_text = review.get( "text" , "" ) if any (keyword in review_text for keyword in exclude_keywords): is_izakaya = True logger.info(f "Excluding {restaurant['name']} - izakaya keywords found in reviews" ) break if is_izakaya: continue 評価(rating)を基準に、価格帯やレストランの種類に応じてボーナススコアを付与し、家族連れに最適なレストランを優先的に表示します。 # 基本スコアは評価から開始 score = rating # 価格帯のボーナス/ペナルティ if price_level is not None : if price_level <= 2 : score += 1.0 # 手頃な価格にボーナス elif price_level >= 3 : score -= 0.5 # 高価格にペナルティ # ファミリーレストランに大きなボーナス family_restaurant_keywords = [ "ファミレス" , "ガスト" , "サイゼリヤ" , "ジョナサン" , ...] if any (keyword in place_name for keyword in family_restaurant_keywords): if child_age is not None and child_age <= 5 : score += 2.0 # 幼児向けに大きなボーナス elif child_age is not None and child_age <= 10 : score += 1.5 # 小学生向けにボーナス else : score += 1.0 # デフォルトボーナス このスコアリングにより、子どもの年齢を考慮した最適なレストラン候補を提案してます。例えば、0-2歳の場合は設備が整ったファミリーレストランが優先され、年齢が上がるにつれて選択肢が広がります。しかし、実際にはまだまだこちらに関しては精度が低く、大人向けのレストランが表示されてしまうことが多いのが現状です。 うまくいかなかったこと・残っている課題 自分の理想通りのものはまだ作れたわけではなく、大体80%くらいの完成度です。例えば、家族向けのレストランを提案してもらう機能を実装しましたが、まだ一部居酒屋が含まれてしまったり、子どもには少し早いお店が提案されている問題がありまだ解決しきれていません。 また、もう1つの課題としてChatGPTのように、AIの応答が文字が流れるように表示される「ストリーミング表示」を実装したいと考えていました。 Vertex AIのSDKにはストリーミング用のメソッドが用意されているのですが、純粋に実装の時間が足りず現時点ではまだ未対応となっています。またチャットとして表示する場合はストリーミングの対応ができたとしても、実際におすすめのお出かけ先として提案しているリストのコンポーネントとして表出させている場所をどのように見せてあげるのがいいのかも悩んだポイントです。純粋にローディングだけでもいい気がしたのは確かですが、なるべくユーザーの体験にはこだわりたいとは考えています。 まとめ 以上です。Google Maps Groundingを使ってお出かけプランナーを作成してみました。Groundingにより架空の施設を提案される心配がなくなり、Google Maps APIとの連携でLLMと位置情報、場所の情報を用いたリッチな情報提供をすることができました。まだ課題を潰しきれてないのと実装の時間の関係でリリースには至れてませんが、個人的には便利なものが出来あがったかなと感じています。 今後は、ストリーミング対応の完成によるユーザー体験の向上、レストランフィルタリングの精度向上などを行っていきたいと考えています。複数日程での旅行への対応、プラン保存機能による後での見返しなどもあったら面白いかもしれません。この記事が、Google Maps Groundingに興味がある方の参考になれば幸いです!
この記事は、 Insight Edge Advent Calendar 2025 の4日目の記事です!! はじめに こんにちは。データサイエンティストの唐澤です。 業務でAmazon Bedrockを利用する機会があったのですが、複数のリクエストを並列で処理すると ThrottlingException が頻発する問題に遭遇しました。この記事では、その時の経験をもとに、どのようなリトライ戦略が効果的かをシミュレータで検証した結果を共有します。 目次 Amazon Bedrock APIレート制限対策 - ThrottlingException解決のための3つのリトライ戦略比較 はじめに 目次 課題:ThrottlingException 原因:TPM(Tokens per minute)制限 5倍のクォータを消費するモデル 解決策:リトライ 比較する戦略 1. Constant Backoff 2. Linear Backoff 3. Exponential Backoff シミュレータによるリトライ戦略の定量的比較 シミュレーション条件 シミュレータ実装(抜粋) 検証結果 結果の考察 成功した戦略の分析 まとめ 参考 課題:ThrottlingException Amazon BedrockのAPIを並列呼び出ししていると、以下のエラーに遭遇しました。 ThrottlingException: An error occurred (ThrottlingException) when calling the InvokeModel operation (reached max retries: 4): Too many requests, please wait before trying again. 原因:TPM(Tokens per minute)制限 原因を調査すると、Amazon BedrockのTPM(Tokens Per Minute)制限が関係していました。 TPMは 1分間に使用できるトークン数の上限 を表し、この制限を超えたために、ThrottlingExceptionが発生していました。 自分が使用していたClaude Sonnetは、デフォルトで200,000 TPMに制限されていました。 5倍のクォータを消費するモデル さらに調査を進めると、特徴的な仕様が見つかりました。AWS公式ドキュメント「 How tokens are counted in Amazon Bedrock 」によると: The burndown rate for the following models is 5x for output tokens (1 output token consumes 5 tokens from your quotas): - Anthropic Claude Opus 4 - Anthropic Claude Opus 4.1 - Anthropic Claude Sonnet 4.5 - Anthropic Claude Sonnet 4 つまり、Claude Sonnetでは 1 output token がクォータからは5トークン差し引かれます 。 Note: You're only billed for your actual token usage. 課金は実際のトークン使用量に対して発生しますが、レート制限の計算では5倍のクォータを消費するため、これらのモデルは特にレート制限に引っかかりやすくなっています。 解決策:リトライ 実務では、試行錯誤しながらmax_tokensの調整やリトライ処理を実装し、エラーの発生を抑えられました。 この経験をもとに、TPM制限に対して具体的にどのようなリトライ戦略が効果的なのかを、シミュレーションで検証したいと思います。 今回は、代表的な3つのリトライ戦略を比較検証します。 比較する戦略 1. Constant Backoff 常に一定時間待機する戦略です。 def constant_backoff (retry_count: int , base_delay: int = 5 ) -> int : """常に一定時間待機""" return base_delay 2. Linear Backoff 待ち時間を線形に増やす戦略です。 def linear_backoff (retry_count: int , base_delay: int = 5 ) -> int : """retry_count 0: 5秒, 1: 10秒, 2: 15秒, 3: 20秒...""" return base_delay * (retry_count + 1 ) 3. Exponential Backoff 待ち時間を指数的に増やす戦略です。 def exponential_backoff (retry_count: int , base_delay: int = 5 ) -> int : """retry_count 0: 5秒, 1: 10秒, 2: 20秒, 3: 40秒...""" return base_delay * ( 2 ** retry_count) ※ 本実装は簡易的なものです。キャップをかけたりJitterを加えるといった工夫もあります。より詳しい実装については、AWSの公式ブログでも解説されています。 シミュレータによるリトライ戦略の定量的比較 シミュレーション条件 リクエスト数 : 20件(すべて同時刻に到着したと想定) 各リクエストのトークン数 : 20,000〜40,000(ランダム) レート制限 : 200,000 tokens/min 最大リトライ回数 : 5回 処理時間 : トークン数に比例(50,000トークン当たりの処理に1分掛かるものとする) ※ 本シミュレーションでは、簡略化のため、各リクエストのトークン数がそのままクォータから差し引かれるものとします。 シミュレータ実装(抜粋) リクエストの状態を管理するデータクラスです。 @ dataclass class Request : """リクエストの状態を管理""" id : int tokens: int next_try_time: int = 0 # 次に処理を試みる時刻 complete_time: int = 0 # 処理完了時刻 retry_count: int = 0 status: str = "pending" # pending/processing/retry_waiting/success/failed トークンの管理とレート制限を実装したクラスです。 class RateLimiter : def __init__ (self, max_tokens_per_minute: int = 200000 ): self.max_tokens_per_minute = max_tokens_per_minute self.available_tokens = max_tokens_per_minute self.current_time = 0 self.last_recovery_time = 0 def can_process (self, tokens: int ) -> bool : """リクエストが処理可能かチェック""" return tokens <= self.available_tokens def consume_tokens (self, tokens: int ): """トークンを消費""" self.available_tokens -= tokens def advance_time (self, seconds: int = 1 ): """時間を進めてトークンを回復""" self.current_time += seconds # 1分ごとにトークンを回復 time_since_recovery = self.current_time - self.last_recovery_time if time_since_recovery >= 60 : minutes = int (time_since_recovery // 60 ) self.available_tokens = self.max_tokens_per_minute self.last_recovery_time += minutes * 60 並列リクエストのシミュレータでは、1秒ずつ時間を進めながら、各時刻でリクエストの処理とリトライを行います。 # 1秒ずつ時間を進めるシミュレーション while True : # 処理中または待機中のリクエストがあるかチェック active_requests = [r for r in requests if r.status in [ "pending" , "processing" , "retry_waiting" ]] if not active_requests: break # 時間を1秒進める rate_limiter.advance_time( 1 ) # この時刻に処理を試みるリクエストを取得 requests_to_try = [r for r in requests if r.status in [ "pending" , "retry_waiting" ] and r.next_try_time <= rate_limiter.current_time] # 各リクエストを処理 for req in requests_to_try: if rate_limiter.can_process(req.tokens): # 成功 - 処理開始 rate_limiter.consume_tokens(req.tokens) req.status = "processing" processing_time = int (req.tokens * 0.0012 ) # 50,000トークンで60秒 req.complete_time = rate_limiter.current_time + processing_time else : # 失敗 - リトライをスケジュール if req.retry_count < max_retries: wait_time = retry_strategy(req.retry_count) req.next_try_time = rate_limiter.current_time + wait_time req.retry_count += 1 検証結果 # 戦略 総時間 成功 失敗 成功率 リトライ回数 1 Constant Backoff (60秒) 168秒 20 0 100% 17回 2 Exponential Backoff 203秒 20 0 100% 53回 3 Linear Backoff 123秒 15 5 75% 60回 4 Constant Backoff (5秒) 35秒 8 12 40% 60回 結果の考察 Constant Backoff (60秒)は総処理時間が短く(168秒vs 203秒)、リトライ回数も少ない(17回vs 53回)結果となりました。 なぜ100%の成功率となったのでしょうか? 原因を探るために、今回のシミュレーションにおける各リクエストのトークン数を確認してみましょう。 リクエストID トークン数 リクエストID トークン数 0 23,648 10 33,825 1 20,819 11 21,041 2 29,012 12 20,976 3 28,024 13 23,070 4 27,314 14 27,164 5 24,572 15 27,623 6 23,358 16 36,559 7 37,870 17 39,726 8 22,848 18 20,869 9 39,349 19 38,390 合計: 566,057トークン 20個のリクエストが同時に行われた場合を考えます。レート制限の観点では、これらを全て処理するには約2.8分(566,057 ÷ 200,000 ≈ 2.8)必要です。つまり、1回のトークン回復(60秒)だけでは処理しきれず、最低でも2回の回復が必要な負荷状況となっています。 成功した戦略の分析 Constant Backoff (60秒) は、トークン回復の周期(60秒)に待ち時間を合わせることで、効率的に2回分のトークン回復タイミング(60秒、120秒)を待つことができました。今回のシミュレーションでは最も速く(168秒)、かつリトライ回数も最小(17回)で全リクエストの処理に成功しています。 Exponential Backoff の待ち時間は指数的に増加します(5秒 → 10秒 → 20秒 → 40秒 → 80秒)。この特性により、2回分のトークン回復タイミングを待てました。シミュレーションでは1分おきにトークンの回復処理を行いましたが、トークン回復のタイミングや周期を事前に知らなくても、徐々に待ち時間を増やすことが可能です。 上図は、トークンの消費と回復の様子を示しています。時刻1秒で8件、時刻76秒(1回目の回復後)で7件、時刻156秒(2回目の回復後)で残り5件が処理され、最終的に全20件が成功しました(総処理時間203秒)。 なお、一定時間内のリクエスト数に上限がある場合(例:RPS - Requests Per Second)、リトライタイミングを分散させることが有効です。筆者は今回のTPM制限とは別のケースで、1秒当たりのリクエスト回数制限に引っかかった際、Exponential BackoffにJitterを加えることでリトライタイミングを分散させ、問題を回避できた経験があります。 一方、Constant Backoff (5秒)とLinear Backoffでは、トークンが回復しないうちに最大リトライ回数に達してしまい、成功率100%とはなりませんでした。 今回の結果から、レート制限の仕組み(トークン回復の仕組み)を理解している場合はConstant Backoffで適切な待ち時間を設定するのが効率的であることが見えてきました。ただし、待ち時間を長く設定すると、低負荷時には無駄な待ち時間が発生する恐れがあることには注意が必要です。例えば、トークン回復の直前(回復の1秒前など)にリクエストが到着しTPM制限を超えた場合でも、Constant Backoff (60秒)では次の回復まで60秒待つことになります。 まとめ リトライ戦略によって総処理時間や成功するリクエスト数に違いがあることが確認できました。 この検証はあくまで簡易的なシミュレーションです。実際のシステムに適用する際は、以下をはじめとする項目を見積もったうえで、適切なリトライ戦略を検討する必要があると考えています: ピーク時のリクエスト数 1リクエストあたりの平均トークン数(input + output) 許容できる処理時間 利用規模が大きい場合は、クォータ上限の引き上げも検討できると良いでしょう。 また、過度なリトライによるサーバー側への負荷も考慮する必要があります。 実務では試行錯誤の末に問題を解決しました。今回の記事では、その経験をもとに、シミュレーションを通じてリトライ戦略の検討ポイントを整理しました。課題に直面した際、諦めずに考え抜き、実践する――Insight Edgeの「やりぬく」というValueを、改めて意識する機会となりました。 参考 Quotas for Amazon Bedrock How tokens are counted in Amazon Bedrock Exponential Backoff And Jitter - AWS Architecture Blog
目次 目次 はじめに:LLMは「なぜ?」をどこまで理解しているのか DAGと「調整」の基本 本記事で登場する用語の説明 DAG(Directed Acyclic Graph) 調整する(adjustment) バックドアパス(backdoor path) 調整集合 Z(adjustment set) d-separation コライダー / 非コライダー ステップ1:DAGベースの「独立性&バックドアチェッカー」をPythonで実装する 1-1. 因果グラフを扱うクラス:CausalDAG 1-2. d-separation とバックドアパスを判定する:DSeparationChecker ステップ2:LangGraphで「因果チェックAIエージェント」を組む 2-1. Stateの設計 2-2. LLMに「調整すべき変数セット」を提案させる 2-3. DAG側でその提案をチェックする 2-4. LangGraphでノードをつなぐ ステップ3:広告の例で実際に動かしてみる 3-0. LLM(Gemini)のセットアップ 3-1. 広告費と売上(AdSpend→Sales)の例 最後に こんにちは、Insight Edge でリードデータサイエンティストをしている五十嵐です。 本記事は、 Insight Edge Advent Calendar 2025 の 3日目を担当してお届けします。上手く次の人へバトンを渡せるように頑張りますので、よろしくお願いします!! 今回は、 LLM・LangGraph・因果グラフ(DAG) を組み合わせて、 「広告データに対して LLM に調整すべき変数を選ばせ、その妥当性をコードで検証する」 というテーマを扱います。 ビジネスサイドの方へ : 「LLM に因果的な問いを投げるとき、どこまで“理由付け”を信頼して任せられるのか?」という検証として。 エンジニア・データサイエンティストの方へ : 「DAG や d-separation を実装し、LangGraph で実際に AI エージェント化する具体的な手法」として。 それぞれの視点で楽しんでいただける内容になっていますので、ぜひ最後までお付き合いください! はじめに:LLMは「なぜ?」をどこまで理解しているのか ChatGPT や Gemini のような大規模言語モデル(LLM)は、 質問に答える 文章を要約する コードを書く といったことがとても得意です。 一方で、データサイエンス寄りの人からすると、 「このモデル、本当に“因果関係”を理解しているの?」 という疑問もあると思います。 たとえば、よくある問いとして、以下を例に挙げます。 「広告費を増やすと売上は上がりますか?」 LLM はおそらく、 「広告費と売上には正の相関が見られることが多い一方で、 季節要因やキャンペーンなど他の要因も影響しているため、 広告費だけの効果を切り出すには注意が必要です」 のように、かなりそれらしい答えを返してくれます。 しかし、どれだけ説明が精緻になっても、現実のデータには 季節(Season) キャンペーン 景気 といった、 広告費と売上の両方に効いている要因 が潜んでいます。 問題は、こうした要因をどう扱うかを グラフとして明示し、そのうえで「どこまで信じてよい説明なのか」をチェックできるか という点にあります。 そこで本記事では、 LLMに「この因果グラフ(DAG)なら、どの変数を調整すべきか?」と考えさせて その答えが、因果推論のルールに照らして正しそうかどうかを、こちらが用意したPythonコードでチェックする という “因果推論テスト用の AI エージェント” を作ります。 ここで LangGraph は、 「LLM に考えさせるステップ」と「Pythonで因果ルールチェックをするステップ」をつないでくれるワークフローエンジン として使っています。 つまり、 LLM = 因果関係について説明したり、「この変数を調整すべき」と 提案する役 Pythonコード = 因果グラフ(DAG)にもとづいて、「その提案は理論上ちゃんと筋が通っているか」を 判定する役 という役割分担を、LangGraph でひとつのエージェントとしてまとめている、というイメージです。 なお、本記事のコードは Google Cloud の Vertex AI 上のノートブック環境(Python) で実行しています。同様の構成であれば、ローカル環境や他のクラウドでも基本的には同じように動かすことができます。 DAGと「調整」の基本 ここで、簡単に前提となるイメージをそろえておきます。 広告の簡単な例を DAG(因果グラフ)で描くと、次のようになります。 図1 DAG(因果グラフ)例 Season … 季節(年末セール期かどうか等) AdSpend … 広告費 Sales … 売上 ここでの直感的なイメージは次のようになります。 季節(Season)が良いと、自然と売上は上がりやすい 同時に、良い季節には広告費も増やしがち さらに、広告費を増やすと売上も増えるはず ここで、 「広告費(AdSpend)の効果だけを、できるだけ素直に見たい」 と思ったら、 季節(Season)による差をできるだけ公平にそろえる必要があります。 → これを統計の世界では 「調整する」 と呼びます。 本記事で登場する用語の説明 以降の説明を読みやすくするために、 先に本記事で登場する専門用語の意味を簡単に確認しておきます。 (厳密な定義よりも全体像の把握を優先しています) DAG(Directed Acyclic Graph) 変数を丸、因果関係を矢印で表現した「因果マップ」です。 A → B は「AがBに影響する(可能性がある)」という関係を表します。 図2 DAG(因果グラフ)例(再掲) 調整する(adjustment) 「ある要因の違いをそろえて、公平に比べる」ことを指します。 例: 年齢が高い人と若い人で薬の効果を比べたい → 年齢をそろえて比べる 季節による売上の差をならし、広告の効果だけを見る バックドアパス(backdoor path) 因果の矢印とは別に、 “裏道”のように紛れ込んでくる経路 です。 「Season → AdSpend」と「Season → Sales」でできる AdSpend ← Season → Sales のようなパスは、 Season を通じて「広告費と売上が一緒に動いているだけ」のパスと解釈できます。 これが残ったままだと、 「広告費が効いているのか、季節が効いているのか分からない」 という問題が生じます。 調整集合 Z(adjustment set) バックドアの“裏道”をふさぐために、 「条件として入れておくべき変数の集合」 です。 例: Z = {Season} なら、 「季節が同じ状況で広告費の違いだけを見る」というイメージになります。 d-separation 「グラフ上で X と Y の間に、まだ情報が流れる道が残っているかどうか」  を 機械的にチェックするためのルールです。 全てのパスが“閉じている” → d-separated → その条件下では独立 1本でも“開いた”パスがある → d-connected → まだ依存が残っている コライダー / 非コライダー パス上の真ん中の点の「矢印の入り方」による区別です。 A → C ← B のように、 両側から矢印が集まってくる C を 「コライダー(ぶつかり地点)」と呼びます。 A ← C ← B や A ← C → B のように、矢印が“通り抜ける”形は 非コライダー です。 本記事では、d-separation に基づく判定ロジックを Python で実装し、 LLM が提案した調整集合が 「裏道を適切に遮断しているか」 を自動でチェックできるようにします。 実際に本番分析で使うときは、ここで紹介したロジックを DoWhy/EconML などのフレームワークと組み合わせるのがおすすめです。 ステップ1:DAGベースの「独立性&バックドアチェッカー」をPythonで実装する まずは、因果グラフを扱うための土台として、次の2つのクラスを実装します。 DAG の構造(親・子の関係)を保持する CausalDAG d-separation とバックドア条件をチェックする DSeparationChecker この2つは、あくまで「DAG 上でパスをたどって、因果推論のルールに沿ってチェックする」ためのユーティリティです。実データを学習したり推定する部分は含んでいません。 ※「細かいロジックまでは追わないけど、全体の構成だけ知りたい」という方は、 以降のコードをざっと眺めてこういう裏道検査用のクラスがあるんだな、くらいに捉えて頂ければ十分です。 1-1. 因果グラフを扱うクラス:CausalDAG 最初に、DAG の構造を表現するクラスを定義します。 親ノードと子ノードの対応関係、ノードの一覧、祖先ノードの集合などを扱えるようにします。 from collections import defaultdict, deque from typing import Dict, List, Set, Iterable class CausalDAG : def __init__ (self, edges: Iterable[ tuple [ str , str ]]): """ edges: (parent, child) のペアのリストで DAG を定義する。 例: edges = [ ("Season", "AdSpend"), ("Season", "Sales"), ("AdSpend", "Sales"), ] """ self.parents: Dict[ str , List[ str ]] = defaultdict( list ) self.children: Dict[ str , List[ str ]] = defaultdict( list ) self.nodes: Set[ str ] = set () for u, v in edges: self.parents[v].append(u) self.children[u].append(v) self.nodes.add(u) self.nodes.add(v) def all_nodes (self) -> Set[ str ]: return set (self.nodes) def ancestors_of (self, zs: Iterable[ str ]) -> Set[ str ]: """ Z のすべての祖先ノード Anc(Z) を返す。 d-separation では、 「コライダーが Z または Z の祖先を持つとき、パスが開く」 というルールで必要になる。 """ zs = set (zs) visited: Set[ str ] = set () queue: deque[ str ] = deque(zs) while queue: z = queue.popleft() for p in self.parents[z]: if p not in visited: visited.add(p) queue.append(p) return visited この CausalDAG クラスでは、 コンストラクタで (親, 子) のエッジ一覧から 各ノードの親リスト parents 各ノードの子リスト childrenを構築しています。 all_nodes() でノードの集合を取得し、 ancestors_of(zs) で、あるノード集合 Z の「祖先ノード集合」を求めます。 後で説明する d-separation の判定では、 「コライダーの祖先に条件づけされたノードが含まれているか」 を判断する必要があるため、この祖先集合を使います。 1-2. d-separation とバックドアパスを判定する:DSeparationChecker 次に、DAG の上で d-separation とバックドアパスの有無をチェックするクラスです。 ここでは、DAG を「無向グラフ」として見たときの全ての単純パスを列挙し、 各パスが d-separation のルールに照らして「開いているか/閉じているか」を判定します。 class DSeparationChecker : """ DAG に対して d-separation / バックドア条件を判定するクラス。 """ def __init__ (self, dag: CausalDAG): self.dag = dag # ---------- d-separation 関連 ---------- def _is_collider_on_path (self, prev_node: str , mid_node: str , next_node: str ) -> bool : """ パス上の3点 prev -> mid -> next において、mid がコライダーかどうかを判定。 定義: mid に2本の矢印が“向かっている”とき、mid はコライダー。 つまり (prev -> mid) かつ (next -> mid) のとき。 """ return (prev_node in self.dag.parents[mid_node]) and \ (next_node in self.dag.parents[mid_node]) def _compute_ancestors_of_Z (self, Z: Set[ str ]) -> Set[ str ]: """ コライダーが Z または Z の祖先に含まれるとき、 そのコライダーを通るパスは「開きうる」。 そのため Anc(Z) を前もって計算しておく。 """ return self.dag.ancestors_of(Z) def _find_all_simple_paths (self, start: str , goal: str , max_len: int = 10 ) -> List[List[ str ]]: """ 無向グラフとして見たときの単純パスをすべて列挙する。 DAG は小さい前提なので、深さ制限 max_len を軽くかけている。 """ neighbors: Dict[ str , List[ str ]] = {} for n in self.dag.all_nodes(): neighbors[n] = list ( set (self.dag.parents[n]) | set (self.dag.children[n])) paths: List[List[ str ]] = [] stack: List[ tuple [ str , List[ str ]]] = [(start, [start])] while stack: node, path = stack.pop() if node == goal: paths.append(path) continue if len (path) >= max_len: continue for nxt in neighbors[node]: if nxt in path: continue # simple path only stack.append((nxt, path + [nxt])) return paths def _path_is_active (self, path: List[ str ], Z: Set[ str ], ancestors_Z: Set[ str ]) -> bool : """ 与えられたパスが、条件集合 Z のもとでアクティブかどうかを判定。 ルール(縮約版): - 非コライダー中間ノード j: j ∈ Z ならパスはブロック - コライダー中間ノード j: j ∈ Z または j ∈ Anc(Z) ならパスが開きうる それ以外ならブロック """ if len (path) <= 2 : # 直接つながっている場合は、中間ノードがないので常に候補 return True for i in range ( 1 , len (path) - 1 ): prev_node = path[i - 1 ] mid_node = path[i] next_node = path[i + 1 ] is_collider = self._is_collider_on_path(prev_node, mid_node, next_node) if not is_collider: # 非コライダーの場合、そのノードに条件づけるとパスはブロック if mid_node in Z: return False else : # コライダーの場合、 # そのノード自身 or その祖先が Z に含まれる場合にパスが開きうる。 if (mid_node not in Z) and (mid_node not in ancestors_Z): return False return True def d_separated (self, X: Iterable[ str ], Y: Iterable[ str ], Z: Iterable[ str ]) -> bool : """ X と Y が条件集合 Z のもとで d-separated かどうかを判定する。 戻り値: True -> X ⫫ Y | Z (独立) False -> X ̸⫫ Y | Z(依存) """ X = set (X) Y = set (Y) Z = set (Z) ancestors_Z = self._compute_ancestors_of_Z(Z) for x in X: for y in Y: paths = self._find_all_simple_paths( x, y, max_len= len (self.dag.all_nodes()) + 1 ) for p in paths: if self._path_is_active(p, Z, ancestors_Z): # 1本でもアクティブパスがあれば d-connected(依存) return False # アクティブパスが見つからなければ d-separated(独立) return True # ---------- バックドアパス関連 ---------- def has_active_backdoor_path ( self, treatment: str , outcome: str , Z: Iterable[ str ], ) -> bool : """ treatment -> outcome の因果効果を推定したいときに、 「バックドアパス」が Z の下でアクティブかどうかを判定する。 バックドアパスとは: - treatment から outcome へのパスのうち、 - 最初のエッジが「親 -> treatment」になっているもの。 (例: Season -> AdSpend のように、最初が '入ってくる' パス) """ Z = set (Z) ancestors_Z = self._compute_ancestors_of_Z(Z) # treatment から outcome へのすべての単純パス paths = self._find_all_simple_paths( treatment, outcome, max_len= len (self.dag.all_nodes()) + 1 , ) for p in paths: if len (p) < 2 : continue first_neighbor = p[ 1 ] # 最初のエッジが「neighbor -> treatment」かをチェック # parent -> child の定義から、 # "neighbor -> treatment" なら neighbor は treatment の親であるはず if treatment not in self.dag.children[first_neighbor]: # neighbor -> treatment ではないのでバックドア候補ではない continue # このパスが Z のもとでアクティブかどうかを判定 if self._path_is_active(p, Z, ancestors_Z): return True # アクティブなバックドアパスが存在する return False # どのバックドアパスもアクティブではない def is_valid_backdoor_adjustment_set ( self, treatment: str , outcome: str , Z: Iterable[ str ], ) -> bool : """ Z が treatment -> outcome の因果効果を推定するための 「妥当なバックドア調整集合」かどうかを判定する。 定義: - treatment と outcome の間に、Z のもとでアクティブなバックドアパスが存在しないとき True。 """ return not self.has_active_backdoor_path(treatment, outcome, Z) このクラスでは、 DAG 上のすべてのパスを洗い出し、 各パスが d-separation のルールに従って「開いているか/閉じているか」を判定し、 その結果として 「X と Y が条件付きで独立になっているか(d_separated)」 「バックドアパスがすべて閉じていて、調整集合として妥当か(is_valid_backdoor_adjustment_set)」 を返す仕組みをまとめています。 ここまでで、DAG 上のパスに対して因果推論の基本ルールを機械的に適用し、 LLM の提案をチェックするための土台が整いました。 ステップ2:LangGraphで「因果チェックAIエージェント」を組む 次に、この d-separation チェッカーを LLM と組み合わせた AI エージェントとして動かすために、LangGraph を使ってワークフローを組み立てます。 このエージェントは、次の2ステップで動きます。 LLM に「調整すべき変数集合 Z」を提案させる その提案 Z が、DAG に基づいてバックドアを閉じる集合になっているかどうかをチェックする 2-1. Stateの設計 LangGraph は「状態(State)を持つワークフローエンジン」というイメージです。 各ノードは State を受け取り、更新した State を次のノードへ渡します。 今回のエージェントでは、次のような State を定義します。 from typing import TypedDict, List, Optional class CausalAgentState (TypedDict, total= False ): # 入力 question: str # ユーザーの因果的な問い(説明用) treatment: str # 介入変数 X target: str # 効果を知りたい変数 Y # LLM の出力 candidate_adjustment: List[ str ] # LLM が提案した調整集合 Z llm_raw_answer: str # LLM の生の回答 # 検査結果 d_separated: Optional[ bool ] # X と Y が Z で d-separated かどうか(参考値) backdoor_ok: Optional[ bool ] # Z が妥当なバックドア調整集合かどうか # ログ debug_log: List[ str ] ここでは、 treatment / target に「広告費」や「売上」などの変数名を入れ、 candidate_adjustment に LLM が提案する調整集合 Z を格納し、 backdoor_ok で「その Z がバックドア調整として妥当か」を記録します。 debug_log には、各ステップの内部状態や LLM の生出力の一部を文字列として残しておきます。 2-2. LLMに「調整すべき変数セット」を提案させる 次に、LLM に対して「どの変数で調整すべきか」を尋ねる部分です。 LangChain の ChatPromptTemplate を使い、 「JSON 配列だけを返す」 ように強く指示します。 from langchain_core.prompts import ChatPromptTemplate import json ADJUST_PROMPT = ChatPromptTemplate.from_template( """ You are a careful causal inference assistant. We have a causal DAG over variables and we want to estimate the causal effect of {treatment} on {target}. Your task: 1. Propose a set of variables Z to adjust for (back-door adjustment set). 2. Return ONLY a JSON list of variable names, like: ["VarA", "VarB"] IMPORTANT: - Output MUST be a single JSON array. - Do NOT add any explanation. - Do NOT use Markdown code fences. - Do NOT wrap the JSON in ```json or ```. Variables available: {all_vars} Causal DAG description: {dag_text} """ ) def _extract_json_array_from_text (text: str ) -> str : """ LLM が返したテキストから JSON 配列部分だけを抜き出すユーティリティ。 - ```json ... ``` のようなコードブロックを剥がす - テキスト中の最初の '[' から最後の ']' までを切り出す """ t = text.strip() # 1. コードブロック ```...``` を剥がす if t.startswith( "```" ): lines = t.splitlines() # 先頭の ```xxx を削る if lines and lines[ 0 ].startswith( "```" ): lines = lines[ 1 :] # 末尾の ``` を削る if lines and lines[- 1 ].startswith( "```" ): lines = lines[:- 1 ] t = " \n " .join(lines).strip() # 2. 最初の '[' と最後の ']' を探す start = t.find( "[" ) end = t.rfind( "]" ) if start != - 1 and end != - 1 and start < end: return t[start : end + 1 ] # 見つからなければそのまま返す(この後の json.loads で落ちてフォールバック) return t def propose_adjustment_node ( state: CausalAgentState, dag: CausalDAG, dag_text: str , llm, ) -> CausalAgentState: treatment = state[ "treatment" ] target = state[ "target" ] all_vars = sorted ( list (dag.all_nodes())) prompt = ADJUST_PROMPT.format( treatment=treatment, target=target, all_vars= ", " .join(all_vars), dag_text=dag_text, ) resp = llm.invoke(prompt) raw_content = resp.content if hasattr (resp, "content" ) else str (resp) # JSON 配列部分だけにクリーニング cleaned = _extract_json_array_from_text(raw_content) candidate_Z: List[ str ] = [] try : parsed = json.loads(cleaned) if isinstance (parsed, list ): # 文字列だけに揃えておく candidate_Z = [ str (x) for x in parsed] except Exception : candidate_Z = [] debug_log = list (state.get( "debug_log" , [])) debug_log.append(f "[propose_adjustment_node] raw LLM: {raw_content[:120]}..." ) debug_log.append(f "[propose_adjustment_node] cleaned: {cleaned}" ) debug_log.append(f "[propose_adjustment_node] parsed Z: {candidate_Z}" ) new_state: CausalAgentState = { **state, "candidate_adjustment" : candidate_Z, "llm_raw_answer" : raw_content, "debug_log" : debug_log, } return new_state このノードは、DAG の情報(変数名や構造の説明)をプロンプトに埋め込んで LLM に渡し、 「調整すべき変数の候補 Z を JSON 配列で返してもらう」 役割を持ちます。返ってきたテキストから JSON 配列の部分だけを抜き出してパースし、その結果を candidate_adjustment(LLM が提案した Z)として State に保存します。あわせて、元の出力や抽出結果は debug_log に記録しておきます。これにより、LLM の出力形式が多少ぶれても、「変数名の配列」だけを取り出して使えるようにしています。 2-3. DAG側でその提案をチェックする このノードは、LLM が提案した調整集合 Z について、 バックドアパスがすべて閉じているかどうか( backdoor_ok ) 参考として、X と Y が Z のもとで d-separated になっているかどうか( d_separated ) を DSeparationChecker で判定し、その結果を State に書き込むだけのシンプルなチェック役です。 def check_adjustment_node ( state: CausalAgentState, checker: DSeparationChecker, ) -> CausalAgentState: treatment = state[ "treatment" ] target = state[ "target" ] Z = state.get( "candidate_adjustment" , []) # 1. バックドア調整として妥当か? backdoor_ok = checker.is_valid_backdoor_adjustment_set( treatment=treatment, outcome=target, Z=Z, ) # 2. オプション: d-separation もログとして残しておく(X と Y が完全独立かどうか) d_sep = checker.d_separated([treatment], [target], Z) debug_log = list (state.get( "debug_log" , [])) debug_log.append( f "[check_adjustment_node] X={treatment}, Y={target}, Z={Z}, " f "backdoor_ok={backdoor_ok}, d_separated={d_sep}" ) new_state: CausalAgentState = { **state, "d_separated" : d_sep, # これは参考値 "backdoor_ok" : backdoor_ok, # 実際に見たいのはこちら "debug_log" : debug_log, } return new_state ここで行っていることはシンプルです。 checker.is_valid_backdoor_adjustment_set(...) で、 LLM が提案した Z が 「バックドアパスをすべて閉じているか」 を判定します。 → これが backdoor_ok です。 checker.d_separated(...) は、X と Y が Z のもとで完全に独立になるかどうかを判定します。 実務上は常に独立である必要はなく、ここではあくまで参考値としてログに残しています。 2-4. LangGraphでノードをつなぐ 最後に、LangGraph の StateGraph を使って、 propose_adjustment_node (LLMに調整集合を提案させる) check_adjustment_node (DAGでその提案を検査する) という2つのノードを一つのワークフローとしてつなぎます。 from langgraph.graph import StateGraph, END def build_causal_langgraph ( dag: CausalDAG, dag_text: str , llm, ): graph = StateGraph(CausalAgentState) # d-separation / バックドアチェッカー checker = DSeparationChecker(dag) # 部分適用で dag / dag_text / llm を閉じ込めたノード関数を定義 def _propose_node (s: CausalAgentState) -> CausalAgentState: return propose_adjustment_node( s, dag=dag, dag_text=dag_text, llm=llm, ) def _check_node (s: CausalAgentState) -> CausalAgentState: return check_adjustment_node(s, checker=checker) # ノードを登録 graph.add_node( "propose_adjustment" , _propose_node) graph.add_node( "check_adjustment" , _check_node) # フローを定義 graph.set_entry_point( "propose_adjustment" ) graph.add_edge( "propose_adjustment" , "check_adjustment" ) graph.add_edge( "check_adjustment" , END) # 実行可能なアプリケーションを返す app = graph.compile() return app この build_causal_langgraph 関数は、「LLM に調整変数を考えさせて、DAG 側でチェックする」ための因果チェック用エージェントを組み立てる関数です。 エージェントに question (説明用の問い)、 treatment (介入したい変数)、 target (効果を知りたい変数)を渡すと、 まず LLM が「調整すべき変数の候補 Z」を提案し、 そのあと DSeparationChecker が「バックドアが閉じているかどうか」を判定し、 その結果として、LLM の回答内容や提案された Z、判定結果 backdoor_ok などが final_state にまとまって返ってきます。 ステップ3:広告の例で実際に動かしてみる ここからは、実際に広告の DAG を使ってエージェントを動かしてみます。 LLM には Vertex AI の Gemini を利用します。 3-0. LLM(Gemini)のセットアップ まず、Vertex AI 上で Gemini を呼び出すための設定を行います。 本記事のコードは Vertex AI のノートブック環境(Python)で実行していますが、 適切な認証とプロジェクト設定を行えば、ローカル環境などからでも同様のコードで呼び出すことができます。 from langchain_google_vertexai import ChatVertexAI llm = ChatVertexAI( model= "gemini-2.5-flash" , project= "your-gcp-project-id" , # あなたの GCP プロジェクト ID location= "us-central1" , temperature= 0 , ) ここでは、モデル名やリージョン、プロジェクトIDなどを指定しています。 temperature=0 としているのは、因果推論のように「論理的な一貫性」を重視したいケースでは、ランダム性を抑えた方が望ましいためです。 3-1. 広告費と売上(AdSpend→Sales)の例 先ほど説明した広告の DAG を、そのままコードに落とし込みます。 # 1. DAG とその説明テキスト edges = [ ( "Season" , "AdSpend" ), ( "Season" , "Sales" ), ( "AdSpend" , "Sales" ), ] dag = CausalDAG(edges) dag_text = """ Variables: - Season: categorical (e.g., 'Holiday', 'Normal', ...) - AdSpend: continuous, amount of advertising spend - Sales: continuous, sales amount Causal structure (DAG): - Season -> AdSpend - Season -> Sales - AdSpend -> Sales Goal: We want to estimate the causal effect of AdSpend on Sales. """ # 2. LangGraph アプリケーションを構築 causal_app = build_causal_langgraph(dag, dag_text, llm=llm) # 3. 初期状態を定義して実行 initial_state: CausalAgentState = { "question" : "広告費(AdSpend)の売上(Sales)への因果効果を推定したい。" , "treatment" : "AdSpend" , "target" : "Sales" , "debug_log" : [], } final_state = causal_app.invoke(initial_state) print ( "=== [AdSpend→Sales] LLM の生回答 ===" ) print (final_state.get( "llm_raw_answer" , "" )) print ( " \n === LLM が提案した調整集合 Z ===" ) print (final_state.get( "candidate_adjustment" )) print ( " \n === バックドア調整として妥当か? ===" ) print (f "backdoor_ok -> {final_state.get('backdoor_ok')}" ) print ( " \n === d-separation 判定結果(参考値) ===" ) print (f "(AdSpend ⫫ Sales | Z) ? -> {final_state.get('d_separated')}" ) print ( " \n === Debug log ===" ) for log in final_state.get( "debug_log" , []): print (log) このコードでは、まず edges で広告の因果構造(DAG)を定義し、それを CausalDAG に渡しています。 dag_text には DAG の意味を英語でまとめておき、LLM に渡すプロンプトの一部として使います。 build_causal_langgraph(...) で因果チェック用のエージェントを作成し、 initial_state に質問文・介入変数 AdSpend ・目的変数 Sales をセットして causal_app.invoke(initial_state) を呼び出すと、一連のフローが実行されます。 実行結果として、LLM の生回答や提案された調整集合 Z、その Z がバックドア調整として妥当かどうか( backdoor_ok )、d-separation の判定結果などが得られます。この例では、LLM が Season を含むような調整集合を提案し、 backdoor_ok -> True となることを期待しています。 以下が、上記コードの実行結果です。 出力結果 この出力は、次のことを示しています。 LLM は、広告効果を評価するために Season を調整すべき変数として正しく提案している Python 側の d-separation チェッカーも、「Season を調整すればバックドアパス(AdSpend ← Season → Sales)は閉じる」と判断し、backdoor_ok -> True になっている 一方で、AdSpend → Sales という因果パスは残っているため、Season で調整しても AdSpend と Sales は独立にはならない(d_separated=False) つまりこの仕組みは、 「LLM が DAG を踏まえて妥当な調整集合を提案できているか?」 を、コード側で機械的にチェックできている ことを、シンプルな例で確認できた、という結果になっています。 今回の実装はあくまで、 因果構造(DAG)は人間または別プロセスが与える LLM は「どの変数で調整するか」を提案する Python(d-separation チェッカー)が、その提案が因果論的に妥当かどうかを検証する という、ごく小さなパイプラインです。それでも、 LLM に自由にしゃべらせるのではなく、 「DAG に沿った因果的な一貫性」 をチェックする枠組みを足す LangGraph で「LLM に考えさせるステップ」と「ルールベースで検証するステップ」をきれいに分離する という設計の手応えは十分に感じられると思います。 最後に 本記事では、LangGraphを用いた実装コードを交えつつ、AIエージェントと因果グラフを組み合わせて「調整すべき変数」を選ばせるアプローチを紹介しました。 あらためて補足しますと、今回扱った範囲はあくまで 調整集合のチェック までです。 実データから因果効果を推定したり、反実仮想を評価したりする段階では、厳密な統計的推定や感度分析が不可欠です。実務での分析においては、今回紹介したロジックを DoWhy や EconML などの既存フレームワークと組み合わせて活用することをおすすめします。 今後の発展としては、 もう少し複雑な DAG(多段の交絡、コライダー、介在変数など)で LLM をテストする 調整集合の候補を複数出させ、どれがミニマルかをチェックする 実データと接続し、DoWhy/EconML 側で推定した結果を LLM に要約させる といった方向性が考えられます。 LLM に「なぜ?」を語らせつつ、その裏側で 因果グラフと Python のロジックで足場を固める── そうした組み合わせ方の一例として、本記事が何かのヒントになれば幸いです。 最後まで読んでいただき、ありがとうございました! それでは、引き続きよい Advent Calendar ライフ(?)をお過ごしください!!
 この記事は Insight Edge Advent Calendar 2025 の2日目の記事です!🦌🦌🦌🦌🛷🎅 1日目のニャットさんの記事 で紹介された、テックブログレビューエージェントのサポートのもと、なんとか間に合いました。会社でのアドベントカレンダーは初の試みですが、お祭り感があって楽しいですね。 はじめに  はじめまして。Insight Edgeデータサイエンティストのnakanoです。  LLMアプリケーションの開発において、「とりあえず動くもの」を作ることは比較的容易です。しかし、実用的なレベルにまで仕上げることは難しい課題です。その理由は、LLMアプリの良し悪しを測る評価軸が曖昧なままだと、改善の方向性が定まらず開発が迷走してしまうからです。そこで今回は、この課題を解決するアプローチとして、評価駆動開発による進め方をご紹介します。  本記事では、「書き込みや線引きがある紙面画像から、情報を抽出するアプリ」を題材に、評価駆動によるLLMアプリケーションの開発プロセスを解説します。 目次 はじめに 目次 プロジェクトの説明 評価駆動開発でLLMアプリケーションを育てる 評価駆動開発とは バージョン1の開発 1-1. 評価基準の設計 1-2. 正解データの準備 1-3. LLMアプリの開発 1-4. 評価 バージョン2の開発 2-1. 評価基準の修正 2-2. 正解データの修正と追加 2-3. アプリの改善 2-4. 評価 バージョン3, 4, 5, 6:ひたすらサイクルを回す まとめ プロジェクトの説明  今回開発するLLMアプリケーションは、 書き込みや線引きがある紙面画像から、情報を抽出する ためのツールです。 背景・課題  私は最近、GoogleのAIツール「NotebookLM」の音声解説機能にハマっています。NotebookLMはドキュメントをアップロードすると、AIが要約や音声解説を生成してくれるサービスです。昔読んだ論文や備忘メモをアップロードするだけで、音声解説を作れるため内容を楽しく振り返ることができます。しかし紙の本は、デジタル化する必要があります。また、私は本を読む際に、気になる箇所に線を引いたり、余白にメモを書き込んだりする癖があります。これらの個人的な情報も一緒にNotebookLMに取り込みたいと考えているため、標準的なOCRツールでは対応できません。 作るもの 本プロジェクト概要  そこで次の要望を満たす、紙面情報抽出アプリを作成します。 本文の抽出 : 撮影した紙面の画像から、本文テキストを抽出できること。 本文以外の情報の抽出 : 紙面の図や表の内容を音声解説に活用できるレベルでテキスト化できること。 書き込みや線引き情報の抽出(重要) : 読書時に本に書き込んだメモ書きや線引き情報を抽出し、読者がその時気になったポイントをテキスト化できること。  これらの要望を満たすアプリを作ることで、過去に読んだ紙の情報をポッドキャスト化し、楽しく振り返ることができるようになります。 評価駆動開発でLLMアプリケーションを育てる 評価駆動開発とは  評価駆動開発とは、最初に評価基準を設計し、その基準に基づいてアプリケーションを反復的に改善していく手法です。 ただし、プロジェクトの序盤では評価基準自体がわからなかったり、データを見ていくたびに新しい評価基準が見つかったりします。 そのため本記事では、評価精度の改善以外に、評価基準の修正も含めて反復的に開発を進めていきます。 評価駆動な開発サイクル  このフローは、 Eval Driven System Design - From Prototype to Production の記事を参考に、自分のプロジェクトに合わせてアレンジしたものです。過去にこちら( データサイエンティストが評価駆動手法を使ってみた〜家計簿分類プロジェクトの実践記〜 - Insight Edge Tech Blog )のテックブログで触れているため、よければご参照ください。 バージョン1の開発  評価まで含めた最初のサイクルを素早く回すために、バージョン1では本文テキストとページ番号を抽出する程度のシンプルなLLMアプリケーションを作成します。 1-1. 評価基準の設計  最初に評価基準を設計します。抽出した情報(ページ番号,本文テキスト)の正確性を評価するための指標です。 ページ番号 (page_number)は、完全一致で評価します。 理由はページ番号は1文字でも間違うと意味が変わってしまうためです。 本文テキスト (context)は、レーベンシュタイン距離を正規化したものを採用します。 理由は、シンプルなため解釈性が高く、部分的な誤りを評価できるためです。 Pythonでの実装例は以下の通りです。 # 抽出アプリの出力フォーマット class ExtractedPageContentV1 (BaseModel): context: str = Field(..., description= "書面の画像から抽出された本文" ) page_number: str = Field(..., description= "ページ番号" ) # 正解データのフォーマット(たまたま、出力フォーマットと同じ) class GroundTruthV1 (BaseModel): context: str = Field(..., description= "書面の画像から抽出された本文" ) page_number: str = Field(..., description= "ページ番号" ) # 本文テキストの正確性を評価する関数 import Levenshtein def calculate_normalized_distance (pred_context: str , true_context: str ) -> float : distance = Levenshtein.distance(pred_context, true_context) normalized_distance = 1 - distance / max ( len (pred_context), len (true_context)) return normalized_distance 1-2. 正解データの準備  次に正解データを作ります。最初は5~20件程度で良いと考えます。理由は、プロジェクトを進めていく中で要件やスコープが変わることも多く、最初から大量の正解データを作成するのは非効率だからです。そのためまずは全体を代表するような5件のデータをしっかり作成します。 入力画像の例(中央公論新社『中国農村の現在』(山田昌弘著)より) 実際にPythonで表現すると以下のようになります。 # 正解データ(手作業で作る) ground_truth_dataset_v1 = [ { "input_path" : "./data/中国農村の現在/中国農村の現在 - 70.jpg" , "ground_truth" : { "page_number" : "56" , "context" : "とぎ汁などを混ぜてグツグツ煮て作るので、そのコストはゼロである。 \n " " 養豚は自家消費のためでもある。 \n " # 中略(実際には全12行のテキストを記載) "えあるという。そのように生産性の低い農地経営の中で、養豚こそが彼の主な収益源とな \n " "っているのである。トウモロコシは養豚の飼料となり、もし余れば販売することも可能。そ" , } }, # ... (他の4件も同様に作成) ] 1-3. LLMアプリの開発  次に処理を実装します。 最初のサイクルなので、シンプルですぐに実装できるアプリケーションを作成します。 プロンプトも特に工夫はせず簡単に記述します。 def extract_page_content_v1 (image_path: str ) -> ExtractedPageContentV1: with open (image_path, "rb" ) as f: image_bytes = f.read() response = client.models.generate_content( model= "gemini-2.5-flash" , config={ "response_mime_type" : "application/json" , "response_json_schema" : ExtractedPageContentV1.model_json_schema(), }, contents=[ types.Part.from_bytes( data=image_bytes, mime_type= "image/jpeg" , ), "あなたは、書面の画像から記述内容を抽出する専門家です。" "page_numberには、このページのページ番号を整数で入れてください。" "もし、ページ番号がわからない場合は空欄にしてください。" "contextには、記載されている日本語をすべて抽出してください。" "ただし抽出するテキストは本文だけで、ヘッダーやフッター、ページ番号などは含めないでください。" "改行がある位置には改行コードを入れてください。" "段落の最初の空欄には全角スペースを入れてください。" ], ) response_json = response.parsed result = ExtractedPageContentV1.model_validate(response_json) return result  実際に抽出された情報は以下のような感じです。 パッと見は期待通り文字情報を抽出できています。 ExtractedPageContentV1( page_number= '1' , context= 'まえがき \n 今世紀に入って、中国は世界最大 <略> りの農民国家で、正式な統計は' ) 1-4. 評価  実際に正解データを利用して評価を行います。結果は以下のとおりです。 No. ページ番号一致 本文スコア(正規化レーベンシュタイン距離) 1 True 89.9% 2 True 81.4% 3 True 35.5% 4 True 96.9% 5 True 95.1% ページ番号は全件正解、本文テキストも平均80%近くの精度が出ています。 一方で、3件目のファイルの精度が極端に低いことがわかります。 各データを確認したところ以下のような課題が見つかりました。 課題1 : 図表を誤って本文として抽出してしまっている 3件目のファイルの精度が35%と極端に低い要因は、図に記載されたテキストを本文として抽出していたためでした。 バージョン1では、本文テキストだけを対象としているため、図表の情報は含めないことが期待する動作でした。 バージョン2での対応として、プロンプトに「図表の情報に含まれるテキストは含めないでください。」を追加することにします。 課題2 : 改行コードの入れ忘れ 全ファイル共通して、改行コード \n の入れ忘れのために精度が落ちていることもわかりました。 改行コードは、1ページの中に10数個あるため、全体的な精度への影響は無視できません。 改善施策としては、プロンプトを修正して改行コードの入れ忘れを防止することも考えられます。 しかし、改行コードの有無は最終的な用途への悪影響はありません。 これは 評価指標と実務の嬉しさが乖離している ため、次のステップでの対応としては、評価時に改行コードの有無を無視するように評価基準を調整します。 課題3 : 最初/最後の行は、撮影時に影が映り込みやすく誤認識が多い。 紙面を撮影している都合上、最初と最後の行は、撮影時に影が映り込みやすく、誤認識が多いことがわかりました。 逆に最初と最後の行が正確に認識できている場合、中間の行も比較的正確に認識できていると考えられます。 その他の課題 : その他の細かい誤認識もいくつか見つかった 「て」を「で」と変換するなど、単純なOCRミス。 「もし余れば販売することも可能。」を「もし余れば市場に出すことも可能。」と、意味を変えるような誤認識。 段落の開始を示す、全角スペースの入れ忘れ。 性能以外課題 : 性能以外の課題として以下のようなものもある。 正解データの作成に時間がかかりすぎる。 グラフや地図の情報を抽出できない。 書き込みや、線引き情報を抽出できない。 バージョン2の開発 バージョン1の課題を踏まえて、バージョン2では以下のように改善を行います。 評価時に、改行コードの有無は無視する。改行コードを正確に抽出できるかどうかは、最終的な用途にとって重要ではないため。 本文テキストの精度は、最初と最後の行だけで評価する。撮影の都合上、最初と最後の行は陰になりやすくOCRの難易度が高いため。 図表の有無も判定する。図表の要約も行わせるが、評価はしない。理由は、工数が増えるため。 線引きや書き込み情報の抽出精度を評価する。読者が気になったポイントを抽出することが本アプリケーションの重要な目的であるため。 その他の細かい発生したミスを改善するようにプロンプトを修正する。例えば、OCRミスや段落開始の全角スペース入れ忘れなど。 2-1. 評価基準の修正  評価基準は以下のように修正します。 正解データ作成の工数を削減しつつ、実務の嬉しさと一致するように微調整しています。 本文テキスト (context) 最初の行と最後の行についてそれぞれのレーベンシュタイン距離を正規化したものを採用します。 正解データから最初の行と最後の行の文字数を抽出し、その部分だけを評価対象とします。 評価時に改行コードの有無は無視します。 図表の有無 (has_figures) 真偽値での完全一致評価を行います。 線引きテキスト (highlighted_texts) 抽出したテキストの と で囲まれた部分をリストとして抽出します。 正解データと部分一致でマッチングを行い、マッチング率を算出します。 書き込みテキスト (annotation_texts) 抽出した書き込みテキストのリストを正解データと部分一致でマッチングを行い、マッチング率を算出します 2-2. 正解データの修正と追加  評価基準の修正に伴い、正解データのフォーマットも修正します。 # 出力フォーマットの修正 # - 本文の中で線引きされている領域は<highlighted></highlighted>で囲むように指示する class ExtractedPageContentV2 (BaseModel): page_number: str = Field(..., description= "ページ番号" ) context: str = Field(..., description= "書面の画像から抽出された本文" ) has_figures: bool = Field(..., description= "図表の有無" ) figures_summary: str = Field(..., description= "図表の要約" ) annotation_texts: List[ str ] = Field(..., description= "書き込みテキストのリスト" ) #正解データのフォーマットの修正 class GroundTruthV2 (BaseModel): page_number: str = Field(..., description= "ページ番号" ) context_first_line: str = Field(..., description= "本文の最初の行" ) context_last_line: str = Field(..., description= "本文の最後の行" ) has_figures: bool = Field(..., description= "図表の有無" ) annotation_texts: List[ str ] = Field(..., description= "書き込みテキストのリスト" ) highlighted_texts: List[ str ] = Field(..., description= "線引きテキストのリスト" ) ground_truth_dataset_v2 = [ { "input_path" : "./data/中国農村の現在/中国農村の現在 - 70.jpg" , "ground_truth" : { "page_number" : "56" , "context_first_line" : "とぎ汁などを混ぜてグツグツ煮て作るので、そのコストはゼロである。" , "context_last_line" : "っているのである。トウモロコシは養豚の飼料となり、もし余れば販売することも可能。そ" , "has_figures" : False , "annotation_texts" : [], "highlighted_texts" : [ "のちにようやく料理の塩辛さの一要因がわかった。" , "「負担」とは、就学年齢の子供がいて現金収入が必要な事を指し" , ], }, }, # ... (他の9件も同様に追加) ] 2-3. アプリの改善  改善方針を受けて、LLMアプリケーションを以下のように修正します。 出力形式とプロンプトのみ修正しています。 def extract_page_content_v2 (image_path: str ) -> ExtractedPageContentV2: with open (image_path, "rb" ) as f: image_bytes = f.read() response = client.models.generate_content( model= "gemini-2.5-flash" , config={ "response_mime_type" : "application/json" , "response_json_schema" : ExtractedPageContentV2.model_json_schema(), }, contents=[ types.Part.from_bytes( data=image_bytes, mime_type= "image/jpeg" , ), "あなたは、書面の画像から記述内容を抽出する専門家です。" "page_numberには、このページのページ番号を整数で入れてください。" "もし、ページ番号がわからない場合は空欄にしてください。 \n\n " "contextには、記載されている日本語をすべて抽出してください。" "ただし抽出するテキストは本文だけで、ヘッダーやフッター、ページ番号などは含めないでください。" "改行がある位置には改行コードを入れてください。" "段落の最初の空欄には全角スペースを入れてください。" "蛍光ペンや赤ペンで線引きされている部分は<highlighted>と</highlighted>で囲んでください。" "抽出するテキストは本文のみであり、図や表の情報に含まれるテキストは含めないでください。 \n\n " "has_figuresには、このページに図表が含まれている場合はTrue、含まれていない場合はFalseを入れてください。 \n\n " "figures_summaryには、図表がある場合、その内容を簡潔に要約して記述してください。 \n\n " "annotation_textsには、このページに手書きの書き込みがある場合、その内容をすべてリスト形式で入れてください。" "もし書き込みがない場合は空のリストを入れてください。 \n\n " ], ) response_json = response.parsed result = ExtractedPageContentV2.model_validate(response_json) return result 出力結果は以下のようになりました。今回もパッと見は期待通りの情報が抽出できていますが、実際の精度はどうでしょうか? ExtractedPageContentV2( page_number= '56' , context=( 'とぎ汁などを混ぜてグツグツ煮て作るのとで、...途中略...' 'もし余れば<highlighted>販売</highlighted>することも可能。' ), has_figures= False , figures_summary= '' , annotation_texts=[], ) 2-4. 評価  精度評価した結果は以下の通りです。 本文テキストの性能はやや改善されているように感じます。しかし、そろそろデータ数を増やして行かないと本当に適切なのか、わからなくなってきました。 また、線引きテキストや書き込みテキストの抽出性能も計測できています。 No. ページ番号一致 図有無の判定 本文開始行スコア 本文最終行スコア 線引きテキストスコア 書き込みテキストスコア 1 True True 93.75% 92.68% 13.46% 100.00% 2 True True 100.00% 97.56% 100.00% 100.00% 3 True True 100.00% 94.12% 0.00% 100.00% 4 True True 100.00% 97.37% 6.90% 0.00% 5 True True 100.00% 90.24% 87.50% 100.00% 6 True True 90.24% 100.00% 100.00% 68.75%  本文抽出に関しても90%程度は達していますが、20文字に1文字は誤認識があると考えるとまだまだ改善の余地があります。 またこの評価用データはひとつの本から抽出したものなので、他の本に対しても同様の精度が出るかは不明です。 次のバージョンからは、データを増やし課題に漏れがないかなど再度見直しながら改善を進めていきます。  線引きテキストや書き込みテキストスコアの精度はまだまだ改善の余地があります。 特に書き込みテキストスコアは、書き込みが無いページを書き込みなしと判断すれば100%となるため、現在の指標は不適切な気がします。 バージョン3, 4, 5, 6:ひたすらサイクルを回す バージョン3以降は、次の表のとおり細かく修正していきました。線引きや書き込みの精度は割愛し、本文抽出についてだけ紹介します。 バージョン 方針と結果 3 方針 ・正解データを6件→50件に増やす。 ・新書以外にも技術書、横書きの本など多様な形態を追加する。 結果 ・データ種類を増やしたg、平均精度は概ね良好 ・一部大外しするケースが発生(原因:長い脚注など特殊なレイアウト) 4 方針 ・長い脚注を無視するようプロンプトに追記する。 ・具体的には「各頁にある脚注など本文外のテキストは含めてはならない」と記載。 結果 ・大外しするケースは激減した。 ・数字や記号の全角半角の表記ゆれなど細かいミスが目立つ。 5 方針 ・プロンプトが長くなってきたため、Geminiのベストプラクティスに従い全体的に修正 ・参考: https://ai.google.dev/gemini-api/docs/prompting-strategies 結果 ・ 段落開始の全角スペース入れの遵守率が大幅に改善 6 方針 ・数字や記号の全角半角の表記ゆれが起きないよう細かくプロンプトに指示する。 結果 ・ 記号数字での減点が減少し、さらに精度が向上 これらの修正によって、下記のグラフの通り精度を着実に改善できました。 各バージョンの抽出精度推移  一方で依然として生成AI特有の課題は残っています。この点はGeminiではなく、専用のOCRモデルを組み合わせるなどの対策が必要と考えられます。 読みにくい箇所を想像する(前に述べたようにを前述したように)など 有名人と1文字違いの人名を誤認識する まとめ  本記事では、「書き込みや線引きがある紙面画像から、情報を抽出するアプリ」を題材に、評価駆動開発でLLMアプリを実用化レベルまで育てるプロセスを解説しました。 評価駆動開発の3つのメリット 改善の方向性が明確になる LLMアプリはなんとなく良さそうと確認しながら進めがちですが、明確に方向性を決めることができます。 反復的な改善が可能 LLMアプリ開発ではプロンプトを細かく調整していきますが、改善を確認しながら着実に改善できるようになります。 ステークホルダーとの合意形成がしやすい 定量的な指標により、開発進捗や品質を客観的に共有できるようになります。 実践での学び 正解データの作成コストは侮れない。最初は評価データを絞った方が良い 評価指標は、最終的な用途と乖離しないよう注意深く設計する 少数のデータで素早くサイクルを回し、課題を発見してから本格的にデータを増やす おわりに  LLMアプリケーションを「とりあえず動く」状態から「実用レベル」に育てるプロセスを紹介しました。 本記事で紹介したプロセスを参考に、皆さんもLLMアプリケーションを着実に実用レベルまで育ててみてください。  明日のInsight Edge Advent Calendar 3日目は、因果推論とLLMに関する記事です!お楽しみに🎄🎄🎄🎄🎄
はじめに こんにちは、アジャイル開発チーム兼Insight Edge Techblog編集チーム担当のニャットです。 以前、 Vertex AI Geminiを使った社内議事録生成アプリ の記事で生成AI案件への挑戦について書きましたが、その後、生成AI案件にも少しずつ慣れてきました。とはいえ、生成AIの進化があまりにも速すぎて、キャッチアップの日々が続いています。笑 最近は、Claude Codeのコマンド、サブエージェント、スキルといった新しくリリースされた機能をいじってみることを楽しんでいます。そしてClaude Code Actionを使ってGitHub上でこれらの機能を活用できるように仕組み構築も色々試行錯誤しています。 その中の1つとして、テックブログレビューエージェントをマルチエージェント構成で構築したので、今回はその取り組みについて紹介します。 今回のレビューエージェントの構成はテックブログレビューにとどまらず、他のドキュメントレビューやコードレビューにも応用できると思いますので、ぜひ参考にしていただけると嬉しいです! なお、弊社では以前LangGraphベースでTechblogレビューエージェントを開発し紹介していましたが、最近はClaude Code Actionベースに置き換えていますので、その変遷についても説明します。 はじめに 本記事について LangGraphからClaude Code Actionへ - Techblogレビューエージェントをみんなで進化させてみた1年間 1. LangGraphでのエージェント作成からスタート(やってみる) 2. Claude Code Actionも併用して活用してみる(みんなでやる) 継続運用で見えてきた課題と改善ポイント 3. マルチエージェント構成への進化させてレビュー質を高める(やり抜く) 1. 執筆フローに合わせた段階的レビュー 2. レビューの質を高める仕組み 3. レビューの効率化 現在のTechblogレビューエージェント詳細 レビューエージェント構成 記事レビュー例 /outline-review の実行結果 /initial-review の実行結果 工夫点 カスタムスラッシュコマンドのワークフロー制御でレビューフローの最適化 サブエージェント分離によるコンテキスト節約とレビューの深さ向上 サブエージェントからメインスレッドへの結果受け渡し:JSON設計の工夫 GitHub Suggestion機能を確実に動かすための指示の工夫 MCPツールの積極的な活用 Web検索・取得機能の有効化 残っている課題と今後の改善点 まとめ Insight Edge Advent Calendar 2025が始まりました! 本記事について この記事は、 Insight Edge Advent Calendar 2025 1日目の記事です。 弊社には「 やってみる・みんなでやる・やりぬく 」というValueがあり、私たちテックブログ編集チームもこのValueを大切にしています。編集チームメンバーは年初にそれぞれ「やってみる」チャレンジを選ぶようにしており、今回紹介するレビューエージェントの進化は、まさにこのValueを体現した取り組みです。 1人のメンバーの「やってみる」から始まり、チーム全体で「みんなでやる」で改善を重ね、アドベントカレンダーという目標に向けて「やりぬく」——本記事では、そんなストーリーをお伝えします。 LangGraphからClaude Code Actionへ - Techblogレビューエージェントをみんなで進化させてみた1年間 このセクションでは、TechblogレビューエージェントをLangGraphベースからClaude Code Actionベースのマルチエージェント構成にシフトさせた過程を紹介します。現在のエージェント構成を先に知りたい方は、 次のセクション からご覧ください。 1. LangGraphでのエージェント作成からスタート(やってみる) 弊社のテックブログレビューエージェントは、以前も本テックブログで公開していた通り、Matsuzakiさんが LangGraphベースのレビューエージェント を作ってくださっていたことから始まりました。Techblogレビュー作業をエージェントを使って自動化する仕組みはここがスタートでした。 私も実際に活用させていただきましたが、人間が指摘しづらい細かいポイント(文言の統一性など)をずばっと指摘してくれて、とても助かりました。 当時のレビュー結果イメージも記事内にありますので、詳しくは Matsuzakiさんの記事 をご覧ください。 記事の中では、「今後やりたいこと」として以下のようなアイデアが挙げられていました。 記事内容に基づいて対象読者(ペルソナ)を作成し、ペルソナの視点からレビューする 内容チェックでWeb検索を取り入れ、類似記事の参照や比較ができるようにする レビューにSEO観点を取り入れ、検索流入を意識したキーワードやタイトルを提案する 2. Claude Code Actionも併用して活用してみる(みんなでやる) LangGraphベースのレビューエージェントを運用する中で執筆者や編集チームから多くのフィードバックが集まり、当時挙げられていた「やりたいこと」を実現したい思いも強くなっていきました。しかし、テックブログ編集チームは組織改善活動の一環であり、メンバーは日々の案件対応で忙しく、エージェントの改善やLangGraph実装を継続的に更新する時間を十分に確保するのは難しい状況でした。 ちょうどその頃、Claude CodeとClaude Code Action( 詳細はこちら )への関心が世の中に広まりました。編集チームのKさんが「Techblogの活動を自動化する」チャレンジを掲げており、すぐにもClaude Code Actionを導入していただき、Github上でClaude Codeによるレビューを試せる環境を整えてくれました。 この時は、レビュー観点を1つのプロンプトにまとめ、プルリクエスト上で @claude を呼び出すとClaude Codeが自律的にレビューしてくれる方針を採用していました。プロンプトは編集チーム全員で整理しました。誤字・脱字、読みやすさ、技術的正確性、記事の深さ、読者の引き込み、拡散性(SEO)などの観点でレビューするように指示しました。 プロンプトの詳細を見る ## タスク あなたはInsight Edge TechBlogの編集者です。Pull Requestの内容をもとにレビューを実施し、具体的な課題と修正方針を示す提案を作成します。 ## 手順 1. メンバーのTechblogをしっかり読み、内容を理解します。 2. 誤字、脱字がないかをチェックします。 3. 読みやすさに焦点を置き、チェックします。  - 言葉遣いや表現が明確で分かりやすいか? - 段落や見出しなどのフォーマットは整理されているか? - 図などで表現した方が分かりやすい項目がなかったか? 4. 技術的正確性に焦点を置き、チェックします。必要に応じてウェブ検索も行い、内容が正確かどうかを確認してください。 - 技術的な内容に誤りや誤解を招く表現はないか? - ソースデータが信頼できるか? 5. 記事の深さに焦点を置き、考えます。 - トピックに対する背景知識や文脈が十分に提供されているか? - 初心者から中級者・上級者までにとって有益な情報量か? 6. 読者の引き込みに焦点を置き、考えます。  - 冒頭から興味を惹かれる構成になっているか? - 読み進めたくなる工夫やストーリーテリングがあるか? 7. 拡散性に焦点を置き、考えます。 - タイトルなど検索でヒットしやすいワードが含まれているか?SEO向上のため、もっといい書き方がないか? - 記事の内容は読者から拡散したくなるような内容になっているか? 8. 何度か読み返し、同僚として親切な態度でレビューコメントを作成します。 ## 出力 - 形式: markdown形式で出力します。 - 構成: 以下の内容を含むレビューコメントを作成してください。絵文字なども使って分かりやすくしてください。 - 総評:記事を読んだ時の全体的な印象 - 良かった点 - 改善点 - 提案: 具体な箇所に対して具体的な対応方針か修正後の文章を提案してください。 このようなプロンプトでも、最初からいい感じのレビュー結果が返ってきました。例えば、先日公開された AIエージェントはなぜ複雑なタスクを完遂できないのか? の初稿でのレビュー結果は以下の通りです。 記事のレビュー結果イメージ 約半年間LangGraphとClaude Code Actionを併用してレビューを運用した結果、観点のアップデートのしやすさやClaude Codeの進化への期待から、編集チームでは最終的にClaude Code Actionベースへ一本化することを決めました。 継続運用で見えてきた課題と改善ポイント この仕組みを約半年間を運用してきましたが、執筆者や編集チームから以下のようなフィードバックがありました。 良かった点: 細かい誤字や文言の統一性、構成改善の提案、視覚化の提案、技術的な正確性、読みやすさの改善、SEO観点の提案など、人間が指摘しづらい点を遠慮なく指摘してくれました。 実際執筆していただいたメンバーからも以下のようなポジティブなフィードバックがありました。 メンバーの実際のフィードバック 一方で、レビュー記事数が増えてくるとだんだん以下のような課題も見えてきました。 課題と改善ポイント: レビューコメントの質にばらつきがある :コメントは長いものの、参考になる内容は7割ほどで、編集チームの最終レビューでは追加で指摘する箇所も残っていました。 ファクトチェックができていない :時事ネタ(セミナーの開催期間)、論文の内容、技術的な内容のファクトチェックができていなかった。プロンプトには「検索して確認するように」と書いていたのに、検索できている様子がなく、誤った指摘を作ってくることもあった 各観点のレビューが浅い :それぞれの観点である程度指摘はくるが、深くレビューできないため、深い指摘がこなかった 執筆フローに合わせたレビューができない :固定プロンプトのため、目次段階、初稿、修正後、どのタイミングでも毎回同じレビューをしていた さらに、課題とまではいかないものの、以下のような 追加でやりたいこと も見えてきました。 Matsuzakiさんが挙げていた ペルソナを作成してレビュー も取り入れるとレビューの質が上がるのでは? 過去記事との内部リンク最適化 :関連記事があれば自動で提案してくれると今年のチャレンジである「PV数向上」にもつながりそう 具体的な修正提案の提示 :該当箇所へのインラインコメントやサジェッション形式で、明確な修正案を直接提示してもらえるとより効率的では? 3. マルチエージェント構成への進化させてレビュー質を高める(やり抜く) それらの課題を解決したいという思いに加えて、Insight Edge初のアドベントカレンダーの開催もやってくる!月に25本の投稿をレビューしなければならないのは編集チームに対してかなりの負担になってしまうため、アドベントカレンダー企画者としてTechblogレビューエージェントの改善をやり抜く決意をしました。 そこで、以下の方針でエージェントを進化させることにしました。 1. 執筆フローに合わせた段階的レビュー 執筆者が必要なタイミングで必要なレビューを受けられるよう、3つのカスタムスラッシュコマンドを作りました。 /outline-review :目次段階で構成を簡潔にチェック /initial-review :初稿を徹底的にレビュー /update-review :修正後の差分をレビュー スラッシュコマンドの詳細は以下のClaude Codeの公式ドキュメントをご参照ください。 Claude Code公式ドキュメント: カスタムスラッシュコマンド 2. レビューの質を高める仕組み マルチエージェント化 :サブエージェント機能を使って各観点ごとに専門エージェントが深くレビューすることで、抽象的ではなく具体的なコメントを実現 ペルソナ駆動レビュー :読者体験評価エージェントには記事内容に応じてターゲット読者のペルソナを生成し、適用 MCPサーバーの設定 :外部ツールと連携し、最新の技術仕様の取得 Web検索、Web取得機能を有効化 :WebFetch、WebSearch機能を使えるようにすることで時事ネタや最新情報を検証 サブエージェント、MCPツール設定、Web検索・取得機能の設定詳細は以下のClaude Codeの公式ドキュメントをご参照ください。 Claude Code公式ドキュメント: サブエージェント Claude Code公式ドキュメント: MCPツール Claude Code公式ドキュメント: Claudeが利用できるツール 3. レビューの効率化 GitHub Suggestion機能 :Claude Code Actionのインラインコメント作成MCPツールを活用し、該当箇所に直接修正案を提示してワンクリックで修正を適用可能にする 現在のTechblogレビューエージェント詳細 では現在のTechblogレビューエージェントがどうなっているのか、説明します。 レビューエージェント構成 全体のアーキテクチャは以下の通りです。 Techblog Review System アーキテクチャ(Google Nano Banana Proを使用して生成) ちょっと複雑に見えるかもしれませんが、流れはシンプルです。 スラッシュコマンド実行: /outline-review 、 /initial-review 、 /update-review のいずれかのコマンドをGitHub上で実行 メインエージェント(Claude Code)による処理: 記事を分析し、必要なサブエージェントを選択して順次か並列で起動 サブエージェントによる専門レビュー: 各エージェントが独立したコンテキストで並列処理 結果統合・出力: 環境に応じてMarkdownファイルまたはPRコメントとして出力 今回は、以下のサブエージェントを用意しています。 サブエージェント名 主な役割 起動条件 ペルソナ適用 使用MCPツール persona-generator ペルソナ生成 常時 - - structure-evaluator 構成・深さ・引き込み評価 常時 ✓ - japanese-quality-checker 日本語品質チェック 常時 ✓ textlint MCP quality-checker フォーマット・視覚化チェック 常時 × - seo-analyzer SEO最適化 常時 ✓ - internal-link-optimizer 内部リンク提案 常時 ✓ - tech-validator 技術的正確性の検証 条件付き* × Context7 MCP, WebSearch fact-checker 時事ネタ・統計データ検証 条件付き** × WebSearch compliance-checker コンプライアンス・法的リスク 常時 × - *技術内容が深い場合(論文引用、技術用語10個以上、コードブロック3つ以上など)のみ起動 **外部リンクや時事的事実(「最近」「先日」「発表」「リリース」など)が含まれる場合に起動 各サブエージェントは独立したコンテキストで動作するため、それぞれの専門分野に集中した深いレビューが可能になっています。 記事レビュー例 では実際の私のこのブログでレビューしてみます。 執筆者が目次・ドラフトか原稿をプルリクエストに挙げた際、編集チームメンバーがプルリクエストのコメント上で @claude /outline-review や @claude /initial-review を実行してレビューを依頼します。 GitHub上でのスラッシュコマンド実行イメージ /outline-review の実行結果 まずは目次を上げた段階で /outline-review を実行してレビューをお願いしていました。 アウトラインレビュー結果 SEO対策のため、タイトルを短くするようにと提案されたり、流入を増やすため関連記事のリンクを追加するように提案されたりしていますね。 この後、全部指摘を採用させていただき、改善しました! ちなみにレビュー過程では、以下のようなペルソナを作成してくれたようです。 ペルソナ生成結果 /initial-review の実行結果 次に、初稿を完成し、 /initial-review を実行してレビューをお願いしました。 初稿レビュー結果 ー 総合コメント 初稿レビュー結果 ー GitHub Suggestion 総合コメントから分かる通り、とても詳細にレビューできていると思います。 また、インラインコメントで改善提案も色々もらえました。 アドベントカレンダーをどうしても最初から宣伝したかったのに・・・エージェント的にはあまり良くないようですね。笑 工夫点 次に、このレビュー仕組みを構築した時の工夫ポイントを紹介させていただきます。もし同じような仕組みを作りたい方がいれば、参考になれば嬉しいです。 カスタムスラッシュコマンドのワークフロー制御でレビューフローの最適化 カスタムスラッシュコマンドは簡単にいうと事前に定義されたプロンプトで、コマンド呼び出し時にこのプロンプトがClaude Codeのメインセクションに渡され、指示された処理が実行されます。 例えば初稿をレビューするための /initial-review コマンドでは、環境判定 → レビュー対象特定 → ペルソナ生成 → 記事分析 → サブエージェント選択・並列起動(ペルソナ情報も渡す)→ 結果統合 → 環境別出力という流れで動けるように、カスタムスラッシュコマンドの定義に工夫しました。 初稿レビュー用カスタムスラッシュコマンドワークフローイメージ ー Google Nano Banana Proを使用して生成 具体的な工夫ポイントを以下に紹介します。 環境判定と出力先の切り替え: GitHub Actions環境かローカル環境かで出力先を切り替えるようにしました。 if [ "$GITHUB_ACTIONS" = "true" ]; then echo "レビュー結果はPRコメントとして投稿されます" else echo "レビュー結果は.reviewsフォルダーに保存されます" fi この目的は、執筆者が執筆中でもローカル環境で自身の記事をレビューできるようにするためです。これによって編集チームのレビュー負担も軽減できます。 ペルソナ駆動レビュー: ワークフロー開始時に記事の内容を理解し、対象読者と想定するペルソナを作成。これらのペルソナを読者体験評価エージェント(構成、日本語品質、SEO、内部リンク)に適用し、多角なレビューをさせるように工夫しました。一方で、技術的正確性や法的リスクなど客観的に判断すべき観点はペルソナに依存せず、専門家視点で評価させるようにしました。 **ペルソナの適用:** 生成されたペルソナは、以下の**読者体験評価エージェント**に適用してください。 - ` structure-evaluator ` : 読者にとって理解しやすい構成か? - ` japanese-quality-checker ` : 読者にとって読みやすい日本語か? - ` seo-analyzer ` : 読者の興味を引くタイトルか? - ` internal-link-optimizer ` : 読者が次に読みたい記事へ誘導できているか? **重要:** これらのエージェントは、**生成された各ペルソナごとに個別に起動**してください。 - 例: 3つのペルソナが生成された場合、 ` structure-evaluator ` を3回(各ペルソナで1回ずつ)起動 - 各起動時には、該当するペルソナ情報を渡してください 以下のエージェントは、プロの専門家視点で客観的に評価します(**ペルソナ不要、1回のみ起動**)。 - ` tech-validator ` : 技術的正確性 - ` fact-checker ` : 事実確認 - ` compliance-checker ` : 法的リスク - ` quality-checker ` : 視覚的品質 条件付きサブエージェント起動: 記事内容によってチェック観点は変わるため、条件付きでサブエージェントを起動するようにしました。 **条件付きエージェント:** - ` tech-validator ` : 技術内容が深い場合のみ起動 - 論文の引用、arXiv/DOIリンク、技術用語10個以上、コードブロック3つ以上などがある場合 - ` fact-checker ` : 以下のいずれかに該当する場合に起動 - 外部リンク(http/https)が含まれている - 時事的事実がある(「最近」「先日」「発表」「リリース」などのキーワード) これによって全ての記事に対して無駄に全ての観点でチェックする必要がなくなり、効率的かつコスト抑えてレビューできるようになります。 並列実行の明示的な指示: 各サブエージェントは独立とした観点でチェックし、順序関係がないため、並列で起動するように指示しました。 ### 3. サブエージェント並列実行 選定したサブエージェントを並列で起動してください。 これによりGitHub Actionの実行時間も短縮でき、コスト削減にもつながります。 サブエージェント分離によるコンテキスト節約とレビューの深さ向上 上でも書いた通り、Claude Code Actionを導入した当初は1つの大きなプロンプトで全観点をレビューしていました。しかし、各観点でのレビューが浅く、具体的な指摘が少ないという課題がありました。そこで、Claude Codeのサブエージェント機能を活用し、観点ごとに独立したエージェントへ分離してみました。サブエージェントは独立したコンテキストウィンドウで動作するため、それぞれが専門分野へ特化したシステムプロンプトと十分なコンテキストを持つことができます。 これにより、例えば japanese-quality-checker は日本語品質チェックのベストプラクティスやtextlintの使い方に集中でき、 tech-validator は技術検証に必要な詳細な指示を持つことができるようになりました。結果として、各観点でのレビューの深さが向上したと思います。 ※ 階層型マルチエージェントは AIエージェントが複雑タスクを完遂できない理由と、「マルチエージェント×コンテキストエンジニアリング」の最新手法 でも説明されていますので、ぜひ合わせてご覧ください。 サブエージェントからメインスレッドへの結果受け渡し:JSON設計の工夫 複数のサブエージェントを並列実行する際、各エージェントからの結果をどう受け取り、どう統合するかが課題でした。 最後の総合コメントおよびサジェッション投稿を統合して整理しやすいように、現在では各サブエージェントには以下のような統一されたJSON形式で結果を返すよう指示しています。 { " category ": " 日本語品質 ", " issues ": [ { " type ": " 誤字 ", " severity ": " high ", " location ": " 第3章 ", " problem ": " 「てにをは」の誤り ", " suggestion ": " 具体的な修正案 ", " original_text ": " 元のテキスト ", " suggested_text ": " 修正後のテキスト ", " line_range ": " 105-106 " } ] , " inline_comments ": [ { " comment_type ": " suggestion ", " line_range ": " 120 ", " comment ": " コメント内容 " } ] , " positives ": [ " 良かった点 " ] , " summary ": " 総評 " } この設計により、メインスレッドでは各エージェントのJSON結果を受け取り、以下の処理が可能になりました。 重要度( severity )別に指摘事項を整理 original_text 、 suggested_text 、 line_range が揃っている指摘を自動的にGitHub Suggestionとして投稿 カテゴリ別に整理された最終レポートの生成 GitHub Suggestion機能を確実に動かすための指示の工夫 Claude Code ActionにはGitHub PR上でインラインコメントを投稿する mcp__github_inline_comment__create_inline_comment というMCPツールが用意されています。 しかし、コマンドやサブエージェントのプロンプトで明示的に「このツールを使え」と指示しないと、使われないことも多いです。そのため、明示的に使うよう指示しました。 **IMPORTANT - GitHub PR Inline Comment形式での出力:** この実行はGitHub Actions環境で行われています。 具体的な修正案がある場合は、必ず以下のフィールドを含めてください。 - ` original_text ` : 修正対象の元のテキスト - ` suggested_text ` : 修正後のテキスト - ` line_range ` : 該当箇所の行範囲 これらの情報を ` mcp__github_inline_comment__create_inline_comment ` ツールを使って GitHub PR上でSuggestion形式のインラインコメントとして投稿してください。 なお、Claude Code Actionがデフォルトで mcp__github_inline_comment__create_inline_comment を許可していない可能性もありますので、許可するように設定を追加しました。 MCPツールの積極的な活用 レビューの質を高めるため、以下のMCPツールを活用しています。 textlint MCP ( mcp__textlint__lintFile ):日本語の自動チェックに使用。 japanese-quality-checker が呼び出し Context7 MCP ( mcp__context7__get-library-docs ):最新の技術ドキュメント取得に使用。 tech-validator が呼び出し github_inline_comment MCP ( mcp__github_inline_comment__create_inline_comment ):GitHub PR Suggestionの投稿に使用。Claude Code Action内で用意されたMCPツールのため、設定不要。 Web検索・取得機能の有効化 とても簡単なことですが、以前の仕組みではプロンプトで必要に応じてウェブ検索するように指示していたにもかかわらず、事実確認が必要な指摘事項はほとんどハルシネーションが発生して、プロンプトが悪いのか?とずっと思い込んでいました。しかし、実はClaude Code Actionはデフォルトでこの機能を許可していないため、検索や取得ができていなかったことが最近判明しました。笑 そのため、今回からは WebSearch と WebFetch 機能を有効化することで、 tech-validator や fact-checker が最新の公式情報を検証できるようにしています。 Claude Code Actionの設定は以下としました。 - name : Run Claude Code Actions Review uses : anthropics/claude-code-action@v1 with : use_bedrock : "true" track_progress : true github_token : ${{ steps.app-token.outputs.token }} claude_args : | --model sonnet --max-turns 50 --mcp-config .mcp.json --allowedTools Task,Edit,Read,WebFetch,WebSearch,Glob,Grep,SlashCommand,mcp__github_comment__update_claude_comment,mcp__github_inline_comment__create_inline_comment,mcp__context7__resolve-library-id,mcp__context7__get-library-docs,mcp__textlint__lintFile,mcp__textlint__lintText,mcp__textlint__getLintFixedFileContent,mcp__textlint__getLintFixedTextContent,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*),Bash(git log:*),Bash(git diff:*),Bash(git branch:*) --disallowedTools "" prompt : | (省略します) 残っている課題と今後の改善点 上記の仕組みを導入してから、現在のところメンバーからかなりポジティブなフィードバックをもらっていますが、まだいくつかの課題が残っています。 サブエージェントによってはまだ過剰や不足な指摘がある。 → 今後は編集チーム全員で継続的にプロンプトを改善していこうと思います。 サブエージェントはなるべく並列実行をするようにしているが、それでも実行時間は前より長くなっているため、Github Actionのコストがかかる。 → 必要なエージェントのみ起動する判定ロジックをさらに洗練させたいですね。 現在はWeb検索・取得機能を特に制限なく有効化してしまっていますが、むやみに外部情報を参照しすぎると、コンテキストも増大し、コスト増加やハルシネーションのリスクもある。 → 今後適切な制限を設けたいと考えています。 また他の課題もあると思いますが、これから皆さんのフィードバックを求め続け、改善を続けていきたいと思います。 まとめ テックブログレビューエージェントは、Matsuzakiさんの「やってみる」から始まり、Kさんや編集チームメンバーの「みんなでやる」で改善、そしてアドベントカレンダーに向けた「やり抜く」で改善を続けてきたInsight Edgeらしい事例を紹介させていただきました。 私個人もこの活動を通じてClaude Code Actionの機能、Claude Codeのスラッシュコマンド、サブエージェントに関してより詳しくなりました。このレビューの仕組みはTechblogに限らず、案件のコードレビューやドキュメントレビューなどにも活用できると思いますので、この知見を活かして社内のコードレビュー課題を解決することにも挑戦してみたいと思います。そこでもう1つの「やってみる・みんなでやる・やりぬく」のValue事例が作れたら嬉しいです。 Insight Edge Advent Calendar 2025が始まりました! 最後に、改めてお知らせです! この記事は Insight Edge Advent Calendar 2025 の1日目として公開しています。 12月25日まで毎日、弊社メンバーがそれぞれの挑戦や学びを発信していきますので、ぜひ Insight Edge Advent Calendar 2025 をフォローして、お楽しみください!
MathJax = {tex: {inlineMath: [['$', '$']]}}; Insight Edgeのデータサイエンティストのki_ieです。数理最適化の専門家として、これまでさまざまな課題を数理最適化問題としてモデリングしてきました。 モデリングはアルゴリズム設計と比べて注目を集めることが少ないようですが、実際には技術的な知見・調査を要求する骨の折れるタスクです。 このタスクを賢いLLMが手伝ってくれたら嬉しいですね! 昨年の記事 では ChatGPT の OpenAI o1 にどれだけ数理最適化問題のモデリングを任せられるか試してみました。今回の記事では最新の ChatGPT モデルである GPT-5 Thinking を使って同様の実験を行い、どこまで使えるものになったのかを確認します。 数理最適化に詳しい方は、準備的な内容をスキップして 前回のおさらいと今回の狙い から読み始めていただければと思います。 数理最適化問題とは 数理最適化問題と混合整数計画問題の基礎知識がある方はこの節はスキップしましょう(面白いことは一つも書いてありません)! このセクションは 昨年の記事 と全く同じ内容です。 数理最適化問題とその実行可能解・最適解 数理最適化問題とは「変数・制約・目的関数」が与えられたときに、 「制約を守るなかで最も目的関数を小さく(または大きく)する決定変数を選ぶ」 という問題です。 数理最適化問題は、一般に以下の形で表現できます: 数理最適化問題 変数 : $x \in D \ \ (\subseteq \mathbb{R}^n)$ 変数 $x$ は $D$ の中から選べる 制約 : $g_i(x) \leq b_i \ \ (i \in I)$ $x$ は $g_i(x) ≤ b_i$ がすべての $i \in I$ について成り立つように選ぶ 目的関数(最小化) : $\min f(x)$ 上記ルールを守ったうえで $f(x)$ を最小化したい。 上手に $D$, $g_i$, $f$ を設計してやることで、配送計画から証券ポートフォリオの最適化まで幅広い問題が 数理最適化問題として表現できることが知られています。 たとえばコンビニの店長の立場でおにぎりの仕入れ量を適正化する問題を単純化すれば、以下のような最適化問題としてモデル化できます: 例: おにぎりの仕入れ量の適正化 変数: おにぎりの仕入れ量 $x \in \mathbb{Z}_+$ 制約: おにぎりが店から溢れない $x \leq 100$ 目的関数: $\max f(x)$ $f(x)$ : おにぎり $x$ を仕入れたときの期待利益 $D$ の要素で制約をすべて守るものはその問題の実行可能解と呼ばれます。 実行可能解のうち目的関数を最小にするものは数理最適化問題の最適解(または解)と呼ばれます。 数理最適化問題で課題をモデリングする目的は、最適解を得ることにあります。 たとえばおにぎりの仕入れ量の適正化の文脈では最も利益があがる仕入れ量が $70$ であれば、$x=70$ が最適解です。 これが計算で求められれば、このコンビニでは利益を最大化する仕入れの意思決定ができるようになってハッピーです。 一方、問題によっては最適解を求めることが極めて難しいことがあります。 おにぎりの仕入れ量の適正化問題は$x=0, 1, 2 \cdots 100$ を全探索すれば簡単に最適解が求まりますが、 もっと複雑な問題ではそうはいかないこともしばしばあるのです。 そのような場合は「目的関数値の良い」実行可能解が求まったらそれでよしとすることが実務上は一般的です。 おにぎりの仕入れ量の問題で言えば、$x=68, 71$ といった解が見つかって、$x=70$ が見つけられなかったら、 $x=68, 71$ をとりあえずの答えとして採用するというイメージです。 この場合でも、あてずっぽうで仕入れ量を決定するコンビニよりは利益を大きくできるわけです。 混合整数計画問題 数理最適化問題の中でも特別に重要な問題クラスとして、混合整数計画(MIP; Mixed Integer Programming)問題があります。 これは実行可能領域 $D$ を$\mathbb{R}^n \times \mathbb{Z}^m$ として、制約・目的関数を線形なものに限定したものです。 混合整数計画問題 変数 : $x \in \mathbb{R}^n \times \mathbb{Z}^m$ 制約 : $a_i^\top x \leq b_i \ \ (i \in I)$ $a_i^\top$ を行ベクトルとして集めた行列を$A$, $b_i$ を集めたベクトルを $b$ としたら、まとめて $Ax ≤ b$ と書ける。 目的関数(最小化) : $\min c^ \top x$ 変数に別途上下限を許す場合も、等号制約を許す場合もある。いずれもここで示した形に容易に変形できる この問題クラスの重要性を説明するために、まず数理最適化を利用した課題解決の一般的な流れを説明します。 さまざまなタイプの数理最適化問題に対して、最適解または最適解ではないにせよ目的関数値の良い実行可能解を求めてくれるプログラムが作られています。これらのプログラムはよく「ソルバー」とよばれます。数理最適化の考え方とソルバーを利用して現実の課題を解くときの流れは以下のようになります。 課題の整理 課題を数理最適化問題としてモデル化 数理最適化問題をソルバーで求解する 得られた最適解(または目的関数値の良い実行可能解)を現実世界で採用する 課題を数理最適化問題に落とし込むことにも技術的な難しさがありますが、 その数理最適化問題を解くソルバーを設計・実装することもまた大変に難しいことです。 そのため、できれば既存のソルバーを用いて求解できるような範囲でモデル化をしたいというのが数理最適化エンジニアの本音です。 MIPによるモデリングはこの観点で大変すぐれています。MIPは多様な問題を表現でき、さらに優れたソルバーが存在するからです。 MIPは制約・目的関数が線形に限定されているため一見すると限られた表現力しかないように見えますが、0, 1の値しかとらないバイナリ変数を巧みに使うことで複雑な論理関係を表現でき、幅広い問題を表現可能です。表現できる問題の幅広さゆえ、学術的にも深く研究され、ソルバーの研究が積み重ねられてきました。その結果、優れた商用・OSSのソルバーが存在するのです。 このような背景から、MIPによるモデリングは数理最適化の実務においてなくてはならない基本技術となっています。 LLMに混合整数計画問題をモデリングさせたいモチベーション このセクションは 昨年の記事 と全く同じ内容です。 課題のMIPモデリングというのはなかなか骨の折れる作業です。 論理的に正しいというだけではなくソルバーにとって解きやすいモデルを作ることが求められるため、 難しい課題の場合は最適化モデルの試行錯誤・調査の1サイクルだけで数日かかってしまう、ということも稀ではありません。 この作業、なるべくサボりたいですね。性能向上の著しいLLMに手伝ってもらえれば、 まるごと作業代替とはならなくとも効率化できるのではないか?というのが、本記事で検証したいポイントです。 数理最適化エンジニアとして確認したい具体的なポイントは以下の3つです。 有名問題をモデル化できるか / 有名定石を利用できるか? 有名問題に簡単な要件を追加してもモデル化がうまくいくか? 複雑な要件を上手にモデル化できるか? LLM に 1. ができたら、数理最適化初心者/学習者にはLLMによる補助が有用だと考えられるでしょう。 数理最適化問題のモデリングにはさまざまな「定石」が登場します。 例えば、0/1変数 $x, y \in \{0, 1 \}$ の論理積をMIPの枠組みで考えたいときには、補助変数 $z \in \{0, 1 \}$ を導入して 制約 $z ≤ x, z ≤ y, x+y-1≤z$ を加えれば $z$ は $x, y$ の論理積(AND)となる、というテクニックがあります。 知っていれば簡単に使えるテクニックですが、自分で思いつくのは難しいです。 初心者がこれをLLMに教えてもらえるなら、学習効率は上がりそうですね。 ある程度経験のある数理最適化エンジニアにとっても、モデル化方法の検討がつかないときのサーベイの代替として LLMに聞くというのが有用になるかもしれません。 2.ができたら、実務的なモデリングの補助としても使えそうです。 実際の問題は有名定石単体で解けないケースのほうが多く、 そのような場合定石を組み合わせたり、自分でテクニックを作ってモデル化する必要があります。 ここまでLLMがやってくれるなら、実務的なモデリングの補助として十分有用でしょう。 3.は、ここまでできたらモデリングの初手はLLMとの対話になりそうですね。 これで毎回完璧な答えが出てきたら私達は冷や飯を食わされることになりそうですが…どこまでできるか見てみましょう。 前回のおさらいと今回の狙い 昨年の記事では o1-mini に3つの難易度のモデリングタスクを実施させました。その結果、当時のLLMは以下のレベルの能力を持つとわかりました。 有名問題をモデル化できるか / 有名定石を利用できるか? ▶ できる 有名問題に簡単な要件を追加してもモデル化がうまくいくか? ▶ ちょっとできる 複雑な要件を上手にモデル化できるか? ▶ できない 難易度 1 の実験では、 LLM に有名な数理最適化問題 (巡回セールスマン問題とクラス分け問題) の要件を日本語で与え、正しくモデリングができるかを確認しました。いずれについても o1-mini で問題のないモデリングが得られたため、今回は追加実験をしません。詳細が気になる方は、 昨年の記事 を参照してください。有名問題は正解の数理モデルをLLMが知識として知っているので、これはモデリング能力の証明というより知識の再生能力が示されたと考えたほうがいいかもしれません。 有名問題に簡単な要件を追加した難易度 2 の実験では、大筋は良いものの多少のツッコミどころがあるという出力で、 有名問題と有名手法の簡単なパッチワークではモデル化できない課題を扱う難易度 3 の実験では全く歯が立たない、という結果でした。 今回の記事では最新の GPT-5 Thinking を用いてレベル2, レベル3のタスクを再度試み、どの程度レベルが上っているのかを確認します。 LLM に数理最適化問題をモデリングさせる 2. 有名問題に簡単な要件を追加してもモデル化がうまくいくか? 「1. 有名問題をモデル化できるか / 有名定石を利用できるか?」はスキップするので 2. からナンバリングを始めています。 巡回セールスマン問題 + α 巡回セールスマン問題に追加要件を加えた問題を GPT-5 Thinking に投げて、モデリングが正しくできるか見ていきます。 太字部分 が通常の巡回セールスマン問題にはない追加要件です。 次の課題を混合整数計画問題としてモデル化してください。 「10個の都市をすべて回って出発地点に返ってくる必要がある。移動時間を最短化したい。 ただし、特定の3都市を先に訪問し、残りの7都市を後に訪問したい。 」 結果は「パラメタ」「決定変数」「制約」「目的関数」の4セクションに分けてください。 結果は次の通りです(主結果の前後のテキストの削除とフォーマット修正は出力後編集): パラメタ 都市集合:$ V = \{1, \dots, 10\} $ 出発・到着の仮想ノード:$ 0 $(都市数に含めない) 先行訪問集合:$ A \subset V,\ |A| = 3 $ 後行訪問集合:$ B = V \setminus A,\ |B| = 7 $ 移動時間(または距離):$ c_{ij} \ge 0 \quad (i, j \in V \cup \{0\},\ i \ne j) $ 都市数:$ n = |V| = 10 $ 決定変数 経路採用:$ x_{ij} \in \{0,1\} \quad (i, j \in V \cup \{0\},\ i \ne j) $ $ x_{ij} = 1 $ なら弧 $ i \to j $ を通る 訪問順序指標:$ u_i \in \{1, \dots, n\} \quad (i \in V) $ 制約 出発点・終点 $$ \sum_{j \in V} x_{0j} = 1, \qquad \sum_{i \in V} x_{i0} = 1$$ 各都市の入次数・出次数 $$ \sum_{\substack{j \in V \cup \{0\} \\ j \ne i}} x_{ij} = 1 \quad (\forall i \in V), \qquad \sum_{\substack{j \in V \cup \{0\} \\ j \ne i}} x_{ji} = 1 \quad (\forall i \in V)$$ 部分巡回消去(MTZ制約) $$ u_i - u_j + n\,x_{ij} \le n - 1 \quad (\forall i, j \in V,\ i \ne j)$$ 先行訪問制約(3都市を必ず先に訪問) $$ u_i \le u_j - 1 \quad (\forall i \in A,\ \forall j \in B)$$ 変数の領域 $$ x_{ij} \in \{0,1\} \ (i \ne j), \quad u_i \in \{1, \dots, n\}$$ 目的関数 $$\min \sum_{i \in V \cup \{0\}} \sum_{\substack{j \in V \cup \{0\} \\ j \ne i}} c_{ij} \, x_{ij}$$ ほぼ完璧です。前回の記事では地点 1 を出発点と固定したモデルを出力してしまいましたが、今回は仮想的な始点 $0$ を導入することで 出発地点の自由度を保っています。巡回セールスマン問題では、ぐるっと一周するという性質から出発地点を好きに再解釈できるため、出発地点を固定してかまわないのですが、この問題では「先行訪問集合」が定義されているため、出発地点を固定してしまうと問題があります。この点に気づいて対応できているのは素晴らしいです。 (前回の記事では「固定しちゃってるけどまあ固定した問題としては正しいかな」と思って見過ごしていたのですが、冷静に考えると入力された課題文には忠実ではないわけで、指摘するべきでした!前回記事読者のみなさま、失礼しました。) その他の面も問題なさそうです。o1-mini で少し怪しい記述になっていた $u_i$ の値域(整数vs連続)についても、今回は整数変数として揺らぎなく書かれています。個人的には連続変数と書いてくれたほうが「わかっている」感が出せてると思いますが、 モデリングとしては問題ないでしょう。 問題を指摘するとすれば「変数の領域」で値域が繰り返し書かれているのは記述として気になりますね。 また「先行訪問制約」の記述が冗長なのは効率が悪い記述で、 $u_i \leq |A| (i \in A)$ などと効率的に書いてほしいものです。間違いとは言えませんが、人手での修正が必要ですね。 総合評価としては「人力の確認と修正が入る前提であれば、有用な出力を出せている」という前回と同様の結果だと思いますが、 より一貫した、修正もしやすい良いモデルになっていると思います。 特に仮想的な始点を導入できているのは素晴らしいです。これは仕事で使えるツール感が出ていますね。 クラス分け問題 + α 有名なクラス分け問題に追加要件(太字部分)を入れた次の発展編も解かせてみましょう。 GPT-5 Thinking へ次の質問を投げます。LLM に喧嘩を防いでもらいましょう。 次の課題を混合整数計画問題としてモデル化してください。 「120人の学生を3つのクラスに分けたい。3つのクラスの人数は等しくしたうえで、クラス間の成績のばらつきを最小に抑えたい。 ただし、何対かの喧嘩をしてしまう生徒ペアがあるため、それらのペアの生徒は必ず別のクラスにしてほしい。 」 結果は「パラメタ」「決定変数」「制約」「目的関数」の4セクションに分けてください。 次の結果が返ってきました(主結果の前後のテキストの削除とフォーマット修正は出力後編集): パラメタ 学生集合 $I={1,\dots,120}$ クラス集合 $C={1,2,3}$ 成績 $s_i \in \mathbb{R}$(各学生 (i) の既知のスコア) 喧嘩ペア集合 $\mathcal{A} \subseteq \{(i,j)\in I\times I \mid i<j\}$ 各クラスの定員 $Q=40$(固定) 全体平均 $\bar{s}=\frac{1}{120}\sum_{i\in I} s_i$(定数) 決定変数 $x_{ic}\in {0,1}$: 学生 $(i)$ をクラス $(c)$ に割り当てるなら1 $u_c \in \mathbb{R}$: クラス $(c)$ の平均成績 $z \ge 0$: クラス平均の最大偏差の上限 制約 各学生はちょうど1クラス $\sum_{c\in C} x_{ic}=1 \quad \forall i\in I$ 各クラスの人数は等しい $\sum_{i\in I} x_{ic}=Q \quad \forall c\in C$ 喧嘩ペアは同一クラス禁止 $x_{ic}+x_{jc}\le 1 \quad \forall (i,j)\in \mathcal{A},\ \forall c\in C$ クラス平均の定義 $Q u_c=\sum_{i\in I} s_i x_{ic} \quad \forall c\in C$ クラス平均の偏差を $(z)$ で抑制 $u_c-\bar{s}\le z,\quad \bar{s}-u_c \le z \quad \forall c\in C$ 目的関数 クラス間の成績ばらつき(クラス平均の最大偏差)を最小化 $\min\ z$ 完璧です。この問題については前回もほぼ問題ない答えでしたが、重箱の隅をつつくような指摘はできました。今回は文句無しに完璧です。 3. 複雑な要件を上手にモデル化できるか? ここまで、有名問題に毛が生えた程度の問題であれば GPT-5 Thinking はそれなりに正しくモデル化してくれることがわかりました。 最後にしっかりと複雑な要件を与えて、モデル化できるか見てみます。 GPT-5 Thinking へ次の質問を投げます。 次の課題を混合整数計画問題としてモデル化してください。 「拠点にある一台の車を使ってすべての店舗を回って拠点に戻る計画を立てたい。 拠点と各店舗にはそれぞれタイムウィンドウが設定されている。 各店舗のタイムウィンドウは最大3つであり、作業開始時間がいずれかのタイムウィンドウに入っていれば許されるものとする。 拠点のタイムウィンドウは1つであるが、出発時刻・帰還時刻のいずれもここに収まる必要がある。 巡回中のいずれかのタイミングで1度だけ拠点への報告業務を行うため、ここで一定の時間を使う必要がある。 報告業務はいずれかの地点での作業終了直後に行うものとする。 報告業務を行うことができるタイムウィンドウは1つ指定されているものとする。 各店舗での作業時間・地点間の移動時間・報告業務の時間はパラメタで与えられる。」 結果は「パラメタ」「決定変数」「制約」「目的関数」の4セクションに分けてください。 結果は次の通りです (主結果の前後のテキストの削除とフォーマット修正は出力後編集): パラメタ ノード集合: 拠点出発 $0$ 店舗集合 $N=\{1,\dots,n\}$ 拠点帰着 $n{+}1$ 許可アーク集合 $A=\{(i,j)\mid i\in\{0\}\cup N,\ j\in N\cup\{n{+}1\},\ i\neq j\}$。 走行時間(対称でなくてよい):$\tau_{ij}\ge 0$ for $(i,j)\in A$。 作業時間:$s_i\ge 0$ for $i\in N$。拠点は $s_0=s_{n+1}=0$。 店舗タイムウィンドウ(高々3本):店舗 $i\in N$ に対し区間集合 $K_i$($|K_i|\le 3$)と各区間 $[a_{ik},\,b_{ik}]$。 拠点タイムウィンドウ(1本):$[a_0,\,b_0]$(出発時刻 $t_0$ と帰還時刻 $t_{n+1}$ の双方がこの範囲)。 報告業務: 所要時間 $r\ge 0$ 実施可能タイムウィンドウ $[a^R,\,b^R]$(開始時刻がこの範囲)。 ビッグ M: 時間緩和用 $M := (b_0-a_0)+s_{\max}+\tau_{\max}+r$($s_{\max}=\max_i s_i,\ \tau_{\max}=\max_{(i,j)\in A}\tau_{ij}$) 報告窓用 $M_R := (b_0-a_0)+s_{\max}$ 決定変数 ルーティング:$x_{ij}\in\{0,1\}$ for $(i,j)\in A$(アーク使用なら1)。 到着(作業開始)時刻:$t_i\in\mathbb{R}_{\ge 0}$ for $i\in\{0\}\cup N\cup\{n{+}1\}$。 窓選択:$w_{ik}\in\{0,1\}$ for $i\in N,\ k\in K_i$(店舗で採用する窓を1本選択)。 報告実施場所選択:$y_i\in\{0,1\}$ for $i\in N$(店舗 $i$ の作業直後に報告を行うなら1)。実施は1回のみ。 制約 (1) 流れ・訪問制約(単一車両で全店舗1回訪問) $\sum_{j\in N\cup\{n{+}1\}} x_{0j}=1,$ $\sum_{i\in \{0\}\cup N} x_{i,n{+}1}=1,$ $\sum_{j\in N\cup\{n{+}1\}} x_{ij}=1\ \ (\forall i\in N),$ $\sum_{i\in \{0\}\cup N} x_{ij}=1\ \ (\forall j\in N)$ (2) 時間伝播(作業・報告・移動を含む) $t_j \ge t_i + s_i + \tau_{ij} + r\,y_i - M\,(1-x_{ij})\ \ \ (\forall (i,j)\in A)$。 ($s_0=s_{n+1}=0$。 報告は店舗でのみ起こり $y_i$ は $i\in N$ のみ。 ) (3) タイムウィンドウ 拠点: $a_0 \le t_0 \le b_0,\ \ a_0 \le t_{n+1} \le b_0$。 店舗(窓の選択と整合): $\sum_{k\in K_i} w_{ik}=1$ $t_i \ge \sum_{k\in K_i} a_{ik}w_{ik} (\forall i\in N)$ $t_i \le \sum_{k\in K_i} b_{ik}w_{ik}\ (\forall i\in N)$。 (4) 報告業務は1回のみ・直後開始・報告窓内 $\sum_{i\in N} y_i = 1$, $t_i + s_i \ge a^R - M_R(1-y_i),\ \ t_i + s_i \le b^R + M_R(1-y_i)\ \ (\forall i\in N)$。 (作業終了直後に開始するので開始時刻は $t_i+s_i$。) (5) 自己ループ禁止 $x_{ii}=0\ \ (\forall i\in N)$。 目的関数 帰還時刻の最小化(最短完了): $\min\ t_{n+1}$。 (距離最小化を望む場合は $\min \sum_{(i,j)\in A}\tau_{ij}x_{ij}$ に置換可能。) おお、これは…!なんだかぱっとみは良さそうですね!各セクションを見ていきましょう。 パラメタセクションでは、 $K_i$ が区間の集合のように書いてあります。これは制約(3)の $k \in K_i$ などでみられる $K_i$ を添え字の集合と見る立場と異なり、形式的には問題がありますね。その他は大きな問題はないように見えます。big-M の具体的な値に問題がないかは、実際に制約で登場するところで確認しましょう。 制約セクションは(1)〜(5)までに分かれて記述されています。わかりやすくていいですね。 (1), (2) ではサイクル除去を含めた巡回セールスマン問題の制約が書かれています。MTZとは少し違う形式ですが、 (2) があることでサイクル除去まで対応できているのですね。厳密には $s_i + \tau_{ij} + r=0$ となるケースでサイクルが除去できなかったり、計算効率の観点から $u$ を導入する普通の MTZ 制約を入れるという考え方もありえると思いますが、問題があるとしても修復可能なものだと思っていいでしょう。 big-M として登場した $M$ の値も、う〜ん多分これは正しいですね。すごい。(2) の最後の括弧の補足部分は蛇足ですが、これは無視すればいいでしょう。 (3) はタイムウィンドウに関しての制約です。拠点に関するものは正しいですね。店舗に関するものは、$w_ik$ を一つ選ぶということはいいのですが、$t_i \leq ...$ の制約は明確におかしいですね($w_ik = 0$ のときに過剰に強い制約)。 $t_i \geq ...$ のほうも、登場する時刻の値がすべて非負という前提がなければ同様におかしいモデリングです。簡単に修正可能ですが、ミスではありますね。 (4) 報告に関する要件を記述しています。$M_R$ の具体的値の正しさは後回しにしましょう。このモデリングの方針は正しいですね! これができるなら (3) でも同じ手法を使ってほしかったですが、こういうミスって人間もしちゃいますね。(4) で正解を出してきたので許せます。 $M_R$ の値は、報告タイムウィンドウが拠点タイムウィンドウに含まれるという前提のもとでは問題はない気がしますが、すこしゆるい評価になっている気がします。これは些末な問題で、ちゃんと計算すれば修正可能です。 (5) は (1) にまとめてもいいでしょう。問題ありません。 これは素晴らしい進化です。前回の実験では「それっぽい数式を出力するが、そもそも変数とパラメタの区別すら曖昧な論理的に読めないもの」が出力されていました。今回のものは、多少ミスはあるものの修正可能であり、最適化モデリングをするうえでの「一歩目」としては利用可能なレベルのものです。 まとめ まとめです。 有名問題をモデル化できるか / 有名定石を利用できるか? ▶ できる 有名問題に簡単な要件を追加してもモデル化がうまくいくか? ▶ 概ねできる 複雑な要件を上手にモデル化できるか? ▶ 間違えはするが、意味のある出力を出せる GPT-5 Thinking は有名問題と有名定石の簡単な組み合わせにとどまらず、 複雑な要件について、多少のミスを含みながらも論理的に考えることができるようです。 MIPモデリングに関して、 GPT-5 Thinking は以下の3つの使い方ができそうです: 数理最適化初心者が簡単なモデルを作りたいとき・学習したいとき 簡単なモデルは作ってくれる ただし正誤判断は自分でできる必要があるため、 教科書で基本的な勉強をしたうえで利用する 中級者~上級者レベルの人のモデリング補助 LLMの知識の幅は圧倒的。調査・検討をしたいときに「ダメもとで聞いてみる」のには意味がありそう たとえば、定石をしらない問題がでてきたときに(枝葉の要件を削って)モデル化させてみて、良さそうなものを使う and/or 出典を調べて調査する、など NEW! 複雑なモデリングの補助 複雑な要件を与えても、検討可能な意味のあるモデルを出力できる 場合によってはモデリングの「一歩目」をLLMに任せて、あとは人間が修正する、という使い方ができそう LLMを使って面白いモデルを書いていきましょう!
はじめに はじめまして。Insight Edgeデザイン部 共創設計チームの小森谷です。 本記事は、私が2025年の夏にInsight Edgeへ参画して最初に取り組んだプロジェクトについてまとめたものです。 チームの一員としてどのようにプロジェクトを進め、何を感じ、どんな学びを得たのか――そのリアルな過程をお伝えできればと思います。 はじめに この記事でわかること 参画初日のオリエン、最初の仕事は「全社会議」の設計だった 「10日後の全社会議で、Insight Edge Vision2030をテーマにワークショップをしたい」 制約条件とゴールイメージ から“設計の手がかり”を探る 今日は7月15日。開催日は7月25日。本番まで、あと10日...? 素案と対話 ― 背景にある意図を探る 限られた時間の中で最善を尽くすために、とにかく早い段階でドラフトを出すことを意識した。 ワークショップの手法選定と具体化 人間と人間の共創プロセス 人間とAIの共創プロセス 当日の様子 ― “聴き合う”が立ち上がる瞬間 参加者の声(抜粋) これからも「共創」を続けるために 筆者について この記事でわかること 10日で全社会議を設計した“段取り” フィッシュボウルを選んだ背景や経緯と内円の緊張を下げる工夫 AIを「観察者/構造化支援」として扱うワークショップ設計の実践プロセス 参画初日のオリエン、最初の仕事は「全社会議」の設計だった 「10日後の全社会議で、Insight Edge Vision2030をテーマにワークショップをしたい」 笑顔で淡々と語る森さん Insight Edgeに参画した初日、最初のミーティングでそう告げられたとき、私は正直状況をつかみきれていなかった。 けれど、不思議と不安よりもワクワクが勝っていたように思う。 その後のプロセスで、少しずつ Insight Edgeの“文化のようなもの” を体感していった。 配属先は「デザイン部 共創設計チーム」。“共創をデザインする”という言葉の意味を、現場で手探りしながら学んでいった。 本稿では、その10日間を当時の視点と現在の理解の両方から振り返り、初仕事として取り組んだ「Vision2030 ワークショップ」の設計から実践に至る過程を記録したい。 制約条件とゴールイメージ から“設計の手がかり”を探る 今日は7月15日。開催日は7月25日。本番まで、あと10日...? 初日のオリエンで共有されたのは、こんな概要だった。 実質的な準備期間は7営業日 会場は大手町オフィスの会議室 スクリーンは2つ 参加者はおよそ60名 2時間半という限られた時間の中で、前半はマネジメント層によるインプットセッション、後半は参加者同士によるワークショップを行う。 淡々と「Vision2030」について語るCINOの森さんと、静かに頷く共創チームリーダーの飯伏さんを眺めながら、後7営業日とは思えない落ち着きぶりだな、と考えていた。 静かに頷く飯伏さん まだ社内の人の顔もほとんど知らず、物事の進め方もつかめていなかった私は、 会議の設計の前にまず背景や状況の把握に集中 することにした。 2回目のミーティングでは、初回で聞いた内容をヒアリングシートにまとめ、まだ聴けていない部分(空白のセル)を埋めながら、森さんと認識をあわせていく。 (※ヒアリングシート=設計意図や制約を整理するための事前質問票) どんな状態を目指しているのか 参加者にどんな体験をしてほしいか 起きてほしいこと、起きてほしくないこと 一つひとつ確認しながら意図を言語化して、全体像を可視化する。私はこの “解像度を上げていく”時間 がとても好きだ。 ふとした瞬間に出てくる言葉や迷いの中に、設計のヒントが潜んでいる。 この時の森さんは、マネジメントからのメッセージを届けるだけでなく、 メンバー自身が自分の言葉で未来を語れるようになってほしい のだと語っていた。 ひととおり埋めたヒアリングシートを出発点として進行を整理した上で、素案に着手する。 ほとんど同時進行のWBS 素案と対話 ― 背景にある意図を探る 限られた時間の中で最善を尽くすために、 とにかく早い段階でドラフトを出すこと を意識した。 まず手始めに、前半と後半を有機的に接続することを意図して 「アウトプット前提でインプットを聴く」構成 を提案。インプットセッションの冒頭で「本日の問い」を提示した上で、参加者がワークシートにメモをとりながらセッション内容を聴き、後半はそのメモをもとに語り合う仕掛けである。 インプットセッションの流れ アウトプットセッションの流れ ①はマスト、②③は時間があれば、の優先度をつけた できるだけ楽しい雰囲気にするために「旅程表」のフォーマットをアレンジしてワークシートを作成した 上記の他に、コンセプト案、タイムライン、大枠の流れをまとめたスライドなど、当日の流れをイメージできる程度の素案を共有すると、すぐにフィードバックが返ってきた。 「マネジメントのメンバーは会話に混ざるべきか?混ざると誘導っぽくならないか?」 「全員がフラットに話すための仕掛けを入れられないか?」 「そもそも全員が同じように話すのが本当にいいのだろうか?」 「もう少し後半のシャッフルを増やせないか?」 「シャッフルするなら、ワールドカフェとか?」 「いや、今の構成だと時間が足りない」 さまざまな意見が上がると、小さな混沌が生まれる。複数の混沌が混ざって大きな混沌になる。私はそれらにまみれながら、それぞれの意図を探っていった。 ワークショップの手法選定と具体化 人間と人間の共創プロセス 森さんと飯伏さんがどうも「シャッフル」にこだわる様子だったので、あらためて問いを立ててみた。 「シャッフルすることで、何が起きて欲しいのか?」 これを起点に話すうちに、森さんがみている”景色”がよりクリアになっていった。 「部門横断で話す場が少ない、部門によって見えている現実が違っている」 そして 「他の部門の話を聴いてほしいんだよね」 と続けた森さんの言葉をきっかけに、ワークショップの軸は、“語り合う”から“聴き合う”へシフトした。 「聴く」という行為をどう設計するか。 この問いから”フィッシュボウル”という手法に辿り着くまでに、さほど時間はかからなかった。 「フィッシュボウル」とは「金魚鉢」を模した​アクティビティの一種で、​対話を深めながら、その内容を参加者全員で共有することができる。​立場の異なる参加者が、お互いの観点をよく理解し、傾聴することを促進する効果がある。​円座の「内側」で進行する話を「外側」から眺めるという構図から、フィッシュボウル(金魚鉢)と呼ばれている。 素案を起点にしたこの対話のプロセスは、まさに「共創」だったように思う。 人と人とのやり取りの中で「意図」が立ち上がり、少しずつ形を変えていく。 ちなみにこの時点ですでに、全体会議当日の3日前である。 人間とAIの共創プロセス ざっくりの方向性やアイデアが出揃ったところで、AIの出番である。ここからは、実際にAIと共創したプロセスの一部を紹介する。 私はまず素材を一括投入し 「制約(優先事項・時間・人数など)を踏まえて、目的整合性と運営難易度の観点から素案を比較し論点を構造化 してください。」と依頼した。 もちろん一発で整理はできないが、 「なんとなくこれじゃないかな」という直感的な発想が、AIとの協働により構造整理されていく のはとても気持ちがいい。 私にとってAIは「観察者」であり「構造化の支援者」でもある。そしてその“観察”の視点は、人間が見落としがちな設計の盲点を照らしてくれる。 ChatGPTとともに整理した内容の抜粋 AIには3案(ラウンドテーブル・ワールドカフェ・フィッシュボウル)をフラットにインプットしたが、推奨はフィッシュボウル。 「内円の発言者が緊張する」懸念については、ワークシートのメモを元に話してもらうことで多少は緩和するかもしれない。メモは緊張した時の”お守り”にもなる。比較検討の結果、時間効率と参加密度の観点からフィッシュボウルを採用した。 しかし、AIが助けてくれるのは思考プロセスまで。 意思決定の後に待っていたのは最も大きな「山場」、人間の仕事だった。 チーム編成や会議室の配置検討などは森さんと飯伏さんがリードし、ワークシートの印刷や当日の備品の準備は共創設計チーム兼務の楠さんや酒井さんがサポートしてくれた。 そしてマネジメントの皆さんがインプットセッションの資料を準備し、当日の音響や会場設営は経営管理部の皆さんが整えてくれた。 縦横無尽にパスを回しあいながら場を作っていく感覚だった。 会議室におけるフィッシュボウルの配置図:テーブル移動は全員での協働作業となった 当日の様子 ― “聴き合う”が立ち上がる瞬間 そうして迎えた全社会議の当日。 会場セッティングと並行して、ファシリテーターと書記を担う皆さんに「進行ガイド」の説明をしたのだが、なんとも不思議な安心感があった。初めまして、とは思えない温かくサポーティブな雰囲気に、ああ今日はきっとうまくいくな、と思った。 開始30分前の雰囲気はこんな感じ 前半のインプットセッションでは、マネジメント層から「Vision2030」の背景と想いが語られた。 会場は静かだったが、ただの“聴講”の静けさではない。 ワークシートにメモを取りながら、自分の中の“問い”を探しているような集中力 が、会場に満ちていた。 Vision2030の背景や意図を聴く 後半のフィッシュボウルが始まる。 進め方を説明した後、森さんと飯伏さんから参加者のみなさんへのメッセージをもらった。 森さん「ぜひこの機会に、他の部門の考えていることを聴いてください。」 飯伏さん「金魚鉢の外側で聴く時にもぜひメモをとってみてください。」 3つの金魚鉢の中心の円で数名が対話し、周囲の人たちはそれを静かに聴く。 4回のターンで円の内と外が入れ替わり、発話が循環していく。 この「話す」と「聴く」が混ざり合いながらひとつの景色を描いていく構造は、今回の全社会議で目指した“共創”のあり方そのものだった。 ファシリテーターが全体の流れを見守りながら、発話のテンポや入れ替わりのタイミングを調整していく。 書記はリアルタイムでMiroに要点をまとめ、話の展開を可視化していく。 このサイズのフィッシュボウルを3つ同時進行した 私はその全体を眺めながら、この“聴き合う”という構造が確かに機能していることを感じていた。 中心の会話が外側に波及し、静かに聴いている人たちの表情や姿勢にも変化が生まれていく。時おり聞こえてくる朗らかな笑い声が印象的だった。 組織ごとを自分ごととして捉えるには、自分自身の言葉で“語る”ことが重要だ。 けれど、“聴き合う”という行為を構造として仕込むことで、全員がその対話の一部になる。それが、この日の場で起きていたことだった。 参加者の声(抜粋) フィッシュボウル形式は非常に好評で「新しいスタイルとして参考にしたい」「観察も発言も楽しめた」との声が多数。 他部署・他職種の考えに触れることを「刺激的」「意外だった」と評価する傾向が強く、相互理解の促進効果が確認できた。 一方で、「書記の負担が大きい」など運営面の改善要望が複数寄せられた。 これからも「共創」を続けるために 全社会議を終えてしばらく経ったころ、社内のさまざまな場で「フィッシュボウル」という言葉を耳にするようになった。噂によると、別の部門のイベントでも実施されたらしい。 一度きりの企画で終わらず、“聴き合う”という構造が、組織の中に息づいている。 それは今回の設計の成果のひとつだと思う。 このプロジェクトを通じて、共創の場づくりは 「正解を示すこと」ではなく、「問いを共有すること」 なのだと実感した。 同じテーマを前にしても、立場や経験によって見えている景色は違う。 その違いを前提に「共通の問い」に向き合っていく ことが、共創の出発点になる。 短期間の中で進めた今回の設計には、もちろん反省も多い。 アンケート結果を眺めながら、今後の糧となる示唆をたくさん受け取った。 Insight Edgeの「共創設計チーム」は、そうした試行を通じて実践知を積み重ねていく組織 なのだと思う。 これからも、ステークホルダーを広く捉えながら「共創の価値」について考え続けていきたい。 筆者について 小森谷 有紀(こもりや ゆき) 紙媒体の編集者を経て、ポータルサイトの速報エディターに転身。​通信キャリア直下のモバイルメディア事業において、黎明期の立ち上げから拡大、統合まで幅広い業務を経験した後、​​複数のスタートアップ企業にてコロナ禍のリモート環境における事業企画や組織開発の仕組みづくりを推進。​​2025年7月よりInsight Edgeに参画。デザイン部・共創設計チームに所属し、ワークショップデザインやファシリテーションを担当。
はじめまして。Insight EdgeにUI/UXデザイナーとして参画している、アマガスと申します。 今回、Insight Edge(以下、IE )のブログを執筆するにあたり、DX化推進支援・生成AI活用の現場へUI/UXデザイナーとして参画している意義や、そこで得られた経験について綴ってみました。 なぜDX化推進支援の領域にデザイナーが必要なのか Insight Edgeにおけるデザイナーの役割 1. 体験の価値を見出し、戦略につなげる「UX」 2. 誰もが迷わず使える体験を形にする「UI」 3. 複雑な情報を直感的に伝え、その価値を高める「グラフィックデザイン」 4.制作実績の紹介 体験をデザインし、価値ある形にする、体験設計チームの一気通貫なものづくり Insight Edgeデザイン部のチーム構成 自分の手で形にする面白さ 現場で得る学び 発想の幅の広がり “やりぬく、やってみる、みんなでやる” ─ 私が感じたIEの魅力 まとめ なぜDX化推進支援の領域にデザイナーが必要なのか 顧客のDX化推進支援において、生成AIのように新しい仕組みを取り入れるプロジェクトでは、仕組みがまだ未知な部分も多く、最初はなかなかピンとこなかったり、興味を感じにくかったりすることもあるのではないでしょうか。 その過程で情報の本質を整理し「どうすれば伝わりやすいか」を考え、目指す形を導き出すことこそが「デザイン」の重要な役割となってきます。 デザインは「見た目を美しく整える」だけでなく、「理解して形にする」ことが役割に含まれているので、DXや生成AIのような目新しい領域では「体験」を形にするUI/UXデザイナーの存在が欠かせないと思います。 Insight Edgeにおけるデザイナーの役割 IEのデザイナーは、多岐にわたる領域に携わり、その線引きは曖昧です。それでも、一人ひとりが状況に応じて柔軟に動きながら、確かな価値を生み出しています。
ここでは、実際の役割や制作事例をご紹介します。 1. 体験の価値を見出し、戦略につなげる「UX」 顧客に向けたワークショップを行い、DX化に向けた認識を深めたり、アイデアを共創することから始まることもあります。 また、マーケティングの視点を取り入れたり、エンジニアの技術的思考とビジネスサイドの視点、そしてユーザーやクライアントの体験をつなぐ役割も担います。 プロジェクトの初期段階では、ヒアリングやユーザーリサーチを通して現状(As-Is)を把握し、理想の体験(To-Be)を描きながら要件を整理します。 そのうえで、カスタマージャーニーや情報設計などを用いて「どんな体験を、どんな流れでユーザーへ届けるか」を明確にし、関係者と共有します。 ときにはコンサルタントのように、相談段階から課題を整理し、デザインを通して体験のゴールや、方向性を共に考え提案することもあります。 IEでは案件によっては開発が先に進み、後からデザインが加わることもあります。その際に「目指す形」を見直し、体験全体をブラッシュアップできるのは大きな価値だと思います。 2. 誰もが迷わず使える体験を形にする「UI」 SaaSをはじめとしたプロダクト開発では、直感的に使えるUI設計が欠かせません。 使いづらいと感じた瞬間に、ユーザーは離れてしまうからです。使いやすさは安心感と信頼感を生み出す大切な要素です。 デザイナーはユーザー目線で操作フローを検討し「誰でも迷わず使え、ストレスのないUI」をビジュアルにします。 体験設計チームでは、UXを基に、ユーザーの行動や利用シーンを想定しつつ、操作フローや画面構成をデザイン&検討しながら進めます。 実際にはFigmaを使ってワイヤーフレームやプロトタイプを作り、プロジェクトチーム内やクライアントと合意形成を図りながら「目指す形」を明確にしていきます。 必要に応じてエンジニアやビジネスサイドとも意見を交わし、UIの動きや導線を調整していくことで、「誰でも迷わず使える」体験を実現してゆきます。 3. 複雑な情報を直感的に伝え、その価値を高める「グラフィックデザイン」 DX化や生成AIなどの活用の仕組みは、文章だけではなかなか理解しにくいものです。 その情報をわかりやすく可視化し、共有を促すのがビジュアルデザインの役割です。 複雑な仕組みを図やイラスト・チャートなどに落とし込むことで、ユーザーはもちろん、社内メンバーや経営層にも直感的に伝えることで価値を高めることができます。 実際、イベントの場でも「ポスターやチラシがあったおかげで来場者に説明しやすかった」と言っていただけることもあり、デザインの推進力を実感しています! 4.制作実績の紹介 体験設計チームで携わったデザインの一部(おもにグラフィックデザイン)をご紹介します。 体験をデザインし、価値ある形にする、体験設計チームの一気通貫なものづくり Insight Edgeデザイン部のチーム構成 IEのデザイン部には「共創設計チーム」と「体験設計チーム」があります。 私が参画している「体験設計チーム」では、上流の体験設計を担うサービスデザイナー・UXデザイナー、UIやグラフィックを形にするデザイナー、そしてコンサル的な動きからUX〜UI、撮影まで幅広く関わる忍びの者など、多様な人がいます。 共通しているのは「どうすればわかりやすく、価値ある形で届けられるか」を常に考え続けている点ではないかと思います。 そのためには、エンジニアやビジネスサイドともフラットに会話したり、チーム内でも「もっとこうしたほうがよい」と意見を出しながら進めたりする場面がよくあります。 実際、体験設計チームでは、Figmaの画面を2〜3人で共有し、会話ベースで一気に作業を進めることもあります。 デザイナー同士だと話のテンポが速く、上記画像左のワイヤーフレームは約1時間で形になりました。 出来上がったLPが こちら です。 about.voiceek.com またあるときは、営業資料などを2人で手分けしてさくっと仕上げてしまうこともあります。 お互いの考えや進め方を間近で感じられるのが魅力で、ざっくばらんな雰囲気の中でアイデアがどんどん形になっていく手法が、最近お気に入りの進め方です! 自分の手で形にする面白さ IE内で過去に事例のあるプロジェクトでは、例えばWEBデザインであれば、要件定義からデザイン制作、モックアップからコーディング・実装、ミーティングでのファシリテーションやクライアント対応、スケジュール管理も含め1人で担当することがあります。 ワンオペで進めるには工夫が求められますが、コミュニケーションの中間工程が少ない分、要件定義からデザインへの落とし込みがスピーディーです。 さらに、クライアントの声を直接聞いているため、要望を反映しやすく、より解像度の高いデザインにつなげられる点も大きなメリットです。 そして、完成したデザインを評価いただけたときの喜びはひとしおです。 現場で得る学び プロジェクトの中にはUXが固まる前に開発が進み、後からUIを整理する場面もあります。 時には急な対応が必要となり、爆速で形にすることもありますが「乗り越えられなかったこと」は一度もなく、その時々で新しい学びを得ることができています。 発想の幅の広がり IEではMTGなどで発表の機会が多く、発信力やコミュニケーション力が自然と高められます。 また、生成AIを活用したクリエイティブ環境に触れることで、デザイン表現の幅が広がるとともに、制作にかかる時間を効率化できるようになってきました。 その結果、制作物のクオリティ向上に注力できるようになっていると思います。 “やりぬく、やってみる、みんなでやる” ─ 私が感じたIEの魅力 IEの魅力は「技術者を大切にしてくれる会社」であることだと思います。 成果を見逃さず、良い点をしっかり評価していただけることが、大きな励みになっています。 クライアントへ期待以上の成果を届けているのも、素晴らしいと思います。 内製ならではのスピード感と、精鋭メンバー全員が自分の領域に誇りを持って品質を磨き続けている姿勢、それは「やりぬく」「やってみる」「みんなでやる」というスローガンのとおり、技術者同士が協力し合える環境があるゆえのことだと思います。 まとめ ここまでお読みいただき、ありがとうございました。 Insight EdgeのUI/UXデザイナーの仕事に、少しでも興味を持っていただけたら幸いです。
IEでは、顧客のDX化推進支援や生成AIといった最先端の領域に携わることができます。 デザインだけでなく、戦略や企画の段階からプロジェクトに関わることもあり、その中で幅広いスキルを身につけながら、社会に価値を届ける「体験」をデザインで形にできる環境があります。 現在、IEでは UIUXデザイナーを募集 しています。これまで培ってきたデザイナーの経験を活かし、「伝わるデザイン」を一緒につくりませんか?
本記事でわかること はじめに 背景・課題 目的 GitHub Actionsを用いたSpec Kitで仕様駆動開発を試してみる 仕様駆動開発とは Spec-Kitとは Claude Code GitHub Actionsについて オセロ対戦アプリを作ってみた Issue連携とSub-issueの活用 問題点と所感 テスト駆動開発の無視 まとめ 参考資料 本記事でわかること この記事では、AIエージェント時代の新しい開発手法として注目される「仕様駆動開発」を、 Claude Code GitHub Actions と Spec Kit を使って実際に試した結果をお伝えします。オセロアプリの開発を通じて、従来の開発プロセスとの違いや実際の課題までを解説します。 はじめに こんにちは。 この度Insight Edgeで1ヶ月間のインターンに参画しております、東京科学大学物質理工学院博士課程2年の石井です。大学院では主に半導体材料をターゲットとした第一原理計算を用いた点欠陥の解析などを行なっております。 今回は、インターン期間中に検討したClaude Code GitHub ActionsとSpec Kitを用いた仕様駆動・AIエージェント駆動開発に関する話をご紹介しようと思います。 背景・課題 ソフトウェア開発は、単一の生成AIによる点的な支援から、 複数モデル/エージェントが協調して要件理解・設計・実装・検証を一貫支援する「エージェント駆動型」 への移行過程にあります。主要LLMと周辺ツールの高度化により自律実行や運用レベルのコーディングが可能になり、開発者は本質的な課題解決に集中できる環境が整いつつあります。 一方で、活用はまだ局所最適に留まりがちで、プロジェクト/個人間でのばらつき、ならびにAI活用プロセスの可視化・再現性の不足が課題です。具体的には以下が挙げられます。 情報探索や手戻りが散発的に発生しやすい ドキュメントと実装の同期が人手依存で、保守性・説明可能性に揺らぎがある ローカルでのAI活用は進む一方、成果物管理やレビューの基盤(GitHub等)が従来運用のままで、活用過程がログとして残らない/監査できない 組織横断で再利用できるプロンプト・エージェント設計・ワークフローが標準化されていない 目的 そこで今回は、組織プロセスとして定着させるための手段としてGitHub Actionsに組み込み、GitHub上で実行することを目指します。 これにより、 情報探索や手戻りの削減 ドキュメントと実装が自然に同期され、プロダクトの保守性が高まる AIエージェントを前提とした開発プロセスが可視化される が成し遂げられ、チーム開発における 開発者体験(Developer Experiense)の質的向上 と、 組織ノウハウとしての定着 が期待されます。 今回はAIエージェントとして Claude Code を活用します。 GitHub Actionsを用いたSpec Kitで仕様駆動開発を試してみる 仕様駆動開発とは 仕様駆動開発(Spec-Driven Development; SDD)は、AIが直接コードを生成する方式ではなく、まず仕様書(Specification)を定義し、その仕様を根拠としてAIエージェントが開発を進める手法です。 仕様駆動開発のねらいは、即興的なvibe codingで失われがちな合意や根拠を仕様へ明確化し、変更時も仕様差分を基点として安全かつ高速で反復できる状態を実現することです。 仕様駆動開発の応用例として、Amazonが発表したエージェント型AI統合開発環境(IDE)である Kiro も注目されています。Kiroは、自然言語のプロンプトや要求から明確な仕様を起こし、それをもとに要件分解→設計→実装タスク化→テスト生成→コード生成までを支援するものです。 Spec-Kitとは そして、その仕様駆動開発のワークフローの標準化のためのツールとして注目されているのが Spec Kit です。 Spec Kitは与えた仕様を詳細化して仕様書を作成し、ソフトウェア開発の計画を策定してタスクに分解・実装するためのオープンソースツールキットでGitHubから提供されています。 Claude Code GitHub Actionsについて また、今回はAIエージェントの活用過程がログとして残らない/監査できないという現状へのアプローチとしてGitHub Actions上で仕様駆動開発を試みます。 そのために今回は、Claude Code GitHub Actionsを活用しました。フローとしてはまずIssueを作成しIssueのコメントで @claude をメンションし、続けて本文を投稿するとその内容をプロンプトとして解釈して返信が返ります。その実装をレビューしてPRを出します。 こちらの 公式サイトのworkflowファイル作成 を参照し、 github/workflows/ 配下へymlファイルを追加しました。 オセロ対戦アプリを作ってみた Claude Code GitHub ActionsとSpec Kitを用いた仕様駆動開発のデモンストレーションとして、オセロ対戦アプリを作成しました。 spec-kitの代表コマンドとして以下のようなものがあり、これらを順に実行していくことで仕様駆動開発が実現できます。 /specify コマンドから要求を書き仕様書を生成 /clarify コマンドで作成した仕様書の未定義箇所の穴埋め /plan コマンドから対応してほしい技術スタックを書き込んで詳細設計書を生成 /tasks コマンドを叩くとtask.mdファイルとしてTodoリストが作成 /implement コマンドでtask.mdを読み込んでそのタスクを元に実装 これらのコマンドはすべてSpec Kitの初期化時に作成されます。 まずはuvを事前にインストールし、以下を実行します。 uvx --from git+https://github.com/github/spec-kit.git specify init --here Claude Codeではなく、Geminiでspeckitを使用したい場合は uvx --from git+https://github.com/github/spec-kit.git specify init --here --ai gemini のように --ai <使用したいcoding agent> で使用できます。(geminiの場合は.gemini/commands/にtomlファイルが生成されます。)対応しているagentの種類はリポジトリを参照: Spec Kit そして以下の画像のように、Issueを立ち上げて @claude でClaudeをメンションして雑な要件定義を投げます。 @claude /specify モダンなUIのオセロ対戦アプリを開発してください。CPUとの対戦が可能なアプリとし、CPUとの対戦は「弱い」、「普通」、「強い」の三種類を選べます。 specifyコマンドの実行例 そうすると、上記の要件で未定義で曖昧な箇所を以下のようにリストアップしてくれます。 未定義な項目のリストアップ例 上記の未定義な箇所の埋め合わせは /clarify コマンドで以下のようにPR上でコメントします: @claude /clarify 1. プレイヤー対プレイヤーのモード: CPU対戦のみ 2. ゲーム履歴の保存: 過去のゲーム結果や統計情報の保存は必要なし 3. サウンドとアニメーション: 駒を置く音、裏返すアニメーション等は必要なし 4. プラットフォーム対応: Webにのみ対応 5. CPU思考時間: 即座に応答すべきか。ただしCPUの処理レベルが高い場合、視覚的な表示や遅延処理が必要 6. ヘルプ機能: 有効な手のヒント表示やアンドゥ機能は必要 これにより曖昧な未定義箇所を穴埋めした仕様書(spec.md)が完成します。 そして、次に実装計画の作成するために /plan コマンドを実行して技術要件の定義や設計、実装手順の策定します。 @claude /plan これを React + TypeScript で実装し、ビルドは Vite、状態管理に Zustand を使用します。スタイリングは Tailwind CSS と shadcn/ui(Radix UIベース)でモダンかつアクセシブルなUIを構築します。盤面描画は解像度に依存しない SVG を基本とし、将来的なアニメ最適化に備えて Canvas 実装も抽象化レイヤーで切り替え可能にします。 CPU思考はUIスレッドをブロックしないよう Web Worker に分離し、アルゴリズムは Minimax + Alpha-Beta を採用(弱=深さ2+簡易ヒューリスティック、普=深さ4、強=反復深化6–8/時間上限10秒)。ハッシュは Zobrist、思考中インジケータとUX用の意図的ディレイはUI側で制御します。 アクセシビリティは ARIA と react-aria に準拠し、レスポンシブ対応・コントラスト基準を満たします。国際化は react-i18next で実装し、チュートリアルは MDX で管理します。 永続化要件は最小のためバックエンドやDBは不要(サーバーレス/静的ホスティング: Vercel または Cloudflare Pages)。任意で設定のみ LocalStorage に保存します。 プロジェクト構成は core/(盤・合法手・反転・評価・探索の純関数)と ui/(表示・操作)、workers/(AI思考)に分離し、仕様の FR 群(合法手表示、無効手の視覚フィードバック、終了判定、難易度選択、思考インジケータ、終了ダイアログ、終了後の新規対局)を満たすタスクに分解して実装します。 そうすることで、技術スタック定義やプロジェクト構造などが書かれた計画文書(plan.md)、技術領域について詳細調査と選定理由を文書化した技術調査文書(research.md)、などを生成してくれます。 次に、plan.mdで定めた実装手順をベースに、それをタスクに落とし込む /tasks コマンドを実行します。その結果、一連の全ての実装タスクをtasks.mdに作成してくれます。 @claude /tasks 最後に /implement コマンドで実装します。以下のように「並列で実装できるタスクは並列で実装して」のようなプロンプトを追加で入れると、tasks.mdのタスクの中で並列に実装可能と判断したタスクを並列で実装してくれるようになります。 @claude /implement 並列で実装できるタスクは並列で実装して。 全ての実装が完了後、軽微な修正であればそのままClaudeにメンションして修正させることができます。大きな追加実装であれば新たに /specify コマンドで追加機能の要件を定義して上記のフローを実行します。 以下は実際に出来たオセロ対戦アプリの画面の一部です。 作成したオセロの画面 Issue連携とSub-issueの活用 GitHubを活用した開発では、Issueに書かれた要求を起点として開発するIssue駆動開発(チケット駆動開発)が一般的です。 そこで今回は、Issueを起点に作業を計画し、必要に応じてsub-issue機能も活用するため、 /create-sub-issue コマンドを新たに作成しました。このコマンドは親issueを細分化してsub-issueを作成する機能を持ち、実際は以下のフローになります。 親Issueを立てる 親Issueのスレッドでまずは大まかな作業計画を立てて、親Issueに紐づくブランチ(親ブランチ)でspec.mdを作成し、mainブランチターゲットでPRを作成(親PR) 親PR上でspec.mdのレビューをし、親PR上でplan.md、tasks.mdを続けて作成 /create-sub-issue コマンドでtasks.mdの内容を参照し、sub-issue作成の指示を出してsub-issueを作成 sub-issueの作業内容は、親ブランチを起点として作成し、PR作成時にも親ブランチをtargetに設定 作成されるsub-issueには以下のルールが適用されます: タイトルに親issue番号と連番を付与 最大6個まで作成可能 Github MCPの機能でissueの作成と親子関係を自動化 コマンドの内容は以下(create-sub-issue.md): --- description: 親Issueを細分化してsub-issueを作成するコマンド。以下のsub-issueの作成ルールに従ってsub-issueを作成してください。 --- #### **タスクの進め方** 元の大きなタスク(親Issue)を、いくつかの小さな作業(sub-issue)に分けて進めていきましょう。 ### **1. タスクの分割** まず、元のタスク(親Issue)の内容をよく読んで理解し、それを具体的な作業に分解します。分解した作業は、新しいタスク(sub-issue)として登録してください。 * **作成方法**: Github MCPの ` sub_issue_write ` という機能を使ってsub-issueを作成してください。 * **個数**: 作成するsub-issueは**6個まで**にしましょう。 ### **2. sub-issueのタイトルルール** 作成するsub-issueのタイトルは、以下のルールに従ってください。 * **フォーマット**: タイトルの先頭に ` [親Issue番号-連番] ` を付けます。 * **例**: 親Issueの番号が「123」で、最初の作業なら ` [123-1] 〇〇の実装 ` のようになります。 * **連番**: 連番は、作業を進める順番(優先度が高い順)に ` 1 ` から振ってください。 ### **3. タスクの親子関係を設定** Github MCPの ` sub_issue_write ` という機能を使って、元の親Issueと、新しく作成したsub-issueを関連付けます。これにより、タスクの全体像が分かりやすくなります。 ### **4. 作業用ブランチの準備** 最後に、**親Issue用のブランチ**を1つ作成し、変更が何もない状態で構わないので、一度プッシュしてください。 ##### **なぜこれが必要?** この親ブランチを基準点(出発点)として、各sub-issueの作業用ブランチを作成したり、作業完了後のプルリクエストの送り先に指定したりするために必要です。 また、デフォルトのClaude Code GitHub Actionsではsub-issueを作成する権限がないため、親Issueに紐付けてsub-issueを作成するにはGitHub MCPを使用しました。 .github/workflows/ 内のymlファイルのClaude Code GitHub Actionsの設定にclaude_argsとして以下のMCP周りの権限を追記します。 claude_args: | --mcp-config .github/mcp-servers.json --allowedTools \ Bash, Write, Edit, MultiEdit, Read, LS, Glob, Grep, WebFetch\ mcp__github__create_branch,\ mcp__github__issue_read,\ mcp__github__issue_write,\ mcp__github__sub_issue_write,\ mcp__github__list_sub_issues,\ mcp__github__create_pull_request,\ mcp__github__get_pull_request,\ mcp__github__get_pull_request_diff,\ mcp__github__pull_request_read,\ mcp__github__update_pull_request,\ mcp__github__create_pending_pull_request_review,\ mcp__github__add_comment_to_pending_review,\ mcp__github__submit_pending_pull_request_review また上記に含まれる .github/mcp-servers.json の設定は以下のようにしました。 { " mcpServers ": { " github ": { " command ": " docker ", " args ": [ " run ", " -i ", " --rm ", " -e ", " GITHUB_PERSONAL_ACCESS_TOKEN ", " ghcr.io/github/github-mcp-server " ] , " env ": { " GITHUB_PERSONAL_ACCESS_TOKEN ": " ${GH_TOKEN} " } } } } これにより、tasks.md作成後に @claude /create-sub-issue とコメントすることで、親Issueに紐付いたsub-issueが作成されます。実装はそれぞれのsub-issue内で実装コマンドをコメントしてClaudeに実装してもらいます。 こうすることで、Issue駆動な仕様駆動開発も実現可能です。 作成されたsub-issueの例 問題点と所感 仕様駆動開発を試行錯誤しながら試していく中で、以下のような課題点やトラブルシューティングに直面しましたので一例として紹介します。 テスト駆動開発の無視 Spec Kitではテスト駆動開発(TDD)を標準としています。 しかし、オセロ対戦アプリの実装では、下図のとおりテストコードが作成されないケースもありました。 原因は、 /implement コマンドでtasks.mdのタスクを実装する際に、テスト実装の優先度が低く設定されているためと考えられます。 TDDがスキップされた例 これを解決するための暫定アプローチとして、CLAUDE.mdに以下のような記述を追加することで、テスト駆動開発を厳守させるようにしました。 CLAUDE.md: # 原則 - すべての変更は TDD(Red→Green→Refactor) を厳守する。 1. Red: テスト作成 → 失敗を確認 → テストのみコミット 2. Green: 実装作成 → すべてのテストを通す → 実装のみコミット(テストは変更禁止) 3. Refactor: ふるまい不変の範囲でリファクタ → 実装のみコミット(テストは原則変更禁止) - テストの改変は原則禁止。ただし、誤仕様または明白な不具合の場合に限り、tests-fix サブタスクとして別PRで扱う。 - モック実装・ハードコードは禁止(テスト過適合を避ける)。必要なら最小限のテストダブルを使用し、ケース拡張で過適合を検出。 - sub-issueのタスクとしてファイルを作成・編集する場合、mainブランチではなく、親Issueのブランチから分岐してブランチを作成してください。 - PRを作成する場合も、mainブランチではなく、親Issueのブランチに対して作成してください。 - 仕様の明確化のために質問・確認事項はまとめて質問してください。断片的に質問を繰り返すことは避けてください。 - 全ての会話の応答や文章の作成には日本語で出力してください。 - markdownファイルへの記述も基本的に日本語で行なってください。 自分は上記のCLAUDE.mdを編集することにより、TDDの強制を改めて行いました。一方でSpec Kitのコマンドを活用するのであれば、 /constitution <instruction> コマンドを実行して開発ツールや開発スタイル、コーディング規約などを指定することによりTDDを指定するのも良いかもしれません。またチーム開発の際は、チーム規約の追加も /constitution コマンドで指定しておくとSpek Kitのコマンドをさらに活用できると考えられます。 1 まとめ 本記事では、Claude Code GitHub ActionsとSpec Kitを組み合わせることで、仕様駆動・エージェント駆動開発を試してみました。また、Issue/sub-issueとも組み合わせることで、Issue駆動な開発も実現できました。この手法により、ターミナルを開いて自分でコードを書かなくともGitHubのブラウザ上でのコメントによる操作のみで、上記のようなアプリが開発できたのは非常に感慨深かったです。 本記事で扱ったような内容が、現場に寄り添った提供方法の一案として検討していければ幸いです。 参考資料 SpecKitでどこまでできる?コストはどれくらい? https://speakerdeck.com/leveragestech/speckitdedokomadedekiru-kosutohadorekurai ↩
はじめに こんにちは!開発チームで生成AI関連のシステム開発をしている広松です! 今回は私が担当した案件で発生した「自律型AIエージェントが複雑な指示を途中で忘れてタスクを完遂できない」という課題に対して論文を元に対策を調査してみました。 具体的にはマルチエージェント化やオーケストレーターの導入によるコンテキストエンジニアリングについて論文を中心に調査してみました。 はじめに 案件で発生した課題 課題と原因について 解決策(コンテキストエンジニアリング)について 手法 1. Plan and Act 2. 階層型マルチエージェント(オーケストレーター) 3. 特化型の専門家エージェントへの分解 まとめ 参考文献 案件で発生した課題 私が担当した案件で「自律型AIエージェントが複雑な指示を途中で忘れてタスクを完遂できない」という問題が発生しました。以下では、この課題の具体的な状況を説明します。 案件自体は自律型AIエージェントでIT運用を自動化するものでした。IT運用で広く横展開できるAIエージェントが実現できるのか検証することが目的であったため、今回はワークフロー型のエージェントではなくReAct型の自律的に思考するエージェントで検証をしました。 実装したエージェントはReAct型のシングルエージェントで6つの自作MCPサーバーと接続し計12ツールを操作するものでした。各種運用対象のサービスがMCP未対応だったため、一時的に自作MCPを用いてツールを接続しました。 エージェントへの指示内容は以下のような流れでした。 各種サービスを操作 例外が発生した場合は原因と解決策を調査 解決策を提示し、人間オペレーターの承認を取得 問題を解決し、最初の指示を完遂 しかし、例外対応時にエラー解決した時点でタスクを完了したと誤認し終了してしまう確率が高かったです。およそ50%程度途中終了する事態が発生していました。 このため、「自律型AIエージェントが複雑な指示を途中で忘れてタスクを完遂できない」という課題に対しての解決策を探す必要が出てきました。 課題と原因について 先ほどの案件で発生した課題を一般化して解くべき問題を特定して論文で調査したいと思います。 案件で発生していることは一般化すると以下のようになります。 自律型AIエージェントは自ら思考、計画、実行、結果の観察を繰り返すことで目的を達成する仕組みです。しかし、複雑な指示を与えると途中で本来の指示を忘却してしまい、途中で終了したり指示を無視した行動を取ることがあります。 この問題は、多くの論文で指摘されていて、様々な解決策の提案(後述で紹介)や エージェントの性能を測るためのベンチマーク に多段階の複雑なタスクを完遂できるかという項目が含まれていたりします。 多くの論文でこの課題の原因については、コンテキストが多くなりすぎて一貫性を保てなくなるからとされています。 エージェントが高レベルの計画を立てながらと低レベルのツール操作と環境からのフィードバックを1つのコンテキスト内で管理していくうちに一貫性が保てなくなり破綻していくことが原因と指摘されています。 解決するためにはコンテキストをどう管理するか、つまり一般的に「コンテキストエンジニアリング」と呼ばれている手法を試す必要があると考えられます。コンテキストエンジニアリングの中で、マルチエージェント化や関心の分離など様々な手法があるため調査します。 解決策(コンテキストエンジニアリング)について 先ほど、課題の原因はコンテキストが多くなりすぎて一貫性を保てなくなるからと確認しました。 解決策としてはコンテキストエンジニアリングと呼ばれるコンテキスト管理の方法が有効のようです。 そもそもコンテキストエンジニアリングについてご存知ない方もいると思うので簡単に説明します。 LangChainのブログ にもまとめられている通りエージェント向けのコンテキストエンジニアリングには下の4つの手法があります。 コンテキストエンジニアリングの4つの手法 Write Context:セッション内のやり取りをメモリ機能などで外部に保存することでコンテキストの肥大化を防ぐ方法です。 Select Context:外部ツールなどから必要なコンテキストだけを抽出する仕組みです。これによってコンテキストの肥大化を防ぎます。 Compress Context:コンテキストが多くなるとコンテキストの圧縮で要約や切り取りをします。 Isolate Context:コンテキストを独立させることです。マルチエージェント化などを実施し関心の分離をして、コンテキストもそれぞれのエージェント内で独立させることにより同一コンテキストの肥大化を防ぎます。 この4つの手法の中で今回有効なのはIsolate Contextと判断しました。理由は今回複数のツールを扱い環境からのフィードバックを得て試行錯誤する過程で、当初の指示を忘却して直近のタスクだけ終わらせて終了してしまうためです。 そこで、記載されている通りマルチエージェント化、例えば作業するエージェントと計画を立てて全体をコントロールするエージェントを分けて関心の分離によるコンテキストの分離を実施すれば有効に機能するのでは?と仮説を立てました。 手法 エージェントによるIsolate Contextというコンテキストの分離にはいくつか手法があるため、それぞれ論文を中心に解説します。 1. Plan and Act Plan and Act と呼ばれる手法について紹介します。 これは高レベルの計画を立てるPlannerと実際にツールを使って操作するExecutorを別のエージェントにすることで関心の分離とコンテキストの分離をする手法です。従来のシングルエージェントでは高レベルの計画の思考とツールを具体どう使うかの思考を同じコンテキスト・同じエージェント内で実施する必要があり複雑なタスクでは高レベルの計画の一貫性を保てなくなることが課題でした。 これを下図の通りPlannerとExecutorエージェントに分け、それぞれ関心とコンテキストを分離して課題を解決しています。 Plan and Actの概念図 Plan and Actの流れ この手法によりWebArena‑Lite57.58%、WebVoyager81.36%とそれぞれのベンチマークでSOTAを達成しているようです。 PlannerとExecutorを分離することで例外発生時や複雑で長期なタスクであっても、Plannerが高レベルの計画についてのみコンテキストを保持することで最初の指示を忘れることなく、指示を完遂する確率を上げることができます。 2. 階層型マルチエージェント(オーケストレーター) マルチエージェント化でよく使われる手法としてはオーケストレーターとサブエージェントに分ける方法です。 これは先ほどのPlan and Actの発展系でもあり、オーケストレーターは計画を実施してメインの指示をサブタスクに分解し、サブタスクをサブエージェントで実行させる手法になります。 こちらは論文ではないのですが、Anthropicのブログ How we built our multi-agent research system が非常にわかりやすく解説しているので紹介します。 Claudeのオーケストレーターアーキテクチャー Claudeのオーケストレーターの処理フロー 図の通りLeadResearcherエージェントがオーケストレーターとなって指示をサブタスクに分解し、サブエージェントにアサインしています。 この際、オーケストレーターによるサブタスクの言語化が明確にできていないと、複数のサブエージェントは重複したタスクを実行してしまいます。重複や非効率の問題があるためオーケストレーターによるサブタスクの言語化は慎重にチューニングが必要だそうです。 しかし、オーケストレーターが的確にサブタスクを生成してサブエージェント用のプロンプトを生成できるようになると複雑な調査を計画立てて効率よく行うことが可能になったと記載されています。 3. 特化型の専門家エージェントへの分解 最後に特化型の専門家エージェントに分解する手法について説明します。 先ほど紹介したオーケストレーターの事例では汎用的なサブエージェントに都度サブタスクのプロンプトを渡してタスクを振っていました。 しかし、扱うツールが多くなるとエージェントがどのツールを使ってどのようにタスクを進めるかまで毎回決定させる必要があります。この時点でコンテキストを多く消費したりうまくタスクを遂行できない可能性が上がります。 そこでロール単位での専門家のエージェントを事前に定義しておくことで、専門家らしく決まったツールを使ってどうタスクを進めるかを事前定義したエージェントを使用することでこの課題を解決します。 例えば、 HyperAgent: Generalist Software Engineering Agents to Solve Coding Tasks at Scale のようなコーディンマルチエージェントでは下図の通り明確な専門家エージェントへ分けています。 HyperAgentでの専門家エージェントの振る舞い まとめ 今回は実案件で発生した課題「自律型AIエージェントが複雑な指示を途中で忘れてタスクを完遂できない」に対して有効と思われるコンテキストエンジニアリング手法を調査してみました。 具体的な手法を論文を中心に以下3つ紹介しました。 Plan and Act 階層型マルチエージェント(オーケストレーター) 特化型の専門家エージェントへの分解 手法1~3の順に実装難易度が上がっていくと思うので、実装コストと精度を鑑みて皆さんも複雑なタスクを完遂するエージェントシステムを構築してみてください! 参考文献 Liu, X. et al. (2023) . AgentBench: Evaluating LLMs as Agents. arXiv preprint arXiv:2308.03688. Erdogan, L. E. et al. (2025) . Plan-and-Act: Improving Planning of Agents for Long-Horizon Tasks. In Proceedings of the 42nd International Conference on Machine Learning (Vol. 267, pp. 15419-15462) . PMLR. Phan, H. N. et al. (2024) . HyperAgent: Generalist Software Engineering Agents to Solve Coding Tasks at Scale. arXiv preprint arXiv:2409.16299.
導入 初めまして。Insight Edgeで企業のDX・AI活用をご支援しているセールスコンサルタントです。 これまで様々な大企業の全社横断的なプロジェクトに携わってきましたが、 DXがうまくいかない企業に共通する、いくつかの「つまずきの要素」があることに気づきました。 「外部の経験豊富なベンダーに頼んだのだから、うまくやってくれるだろう」 そう考えてDXをスタートされるかもしれません。 優秀なコンサルタントやベンダーを雇えば、DXは成功するのでしょうか? 答えは「No」です。 私たちの役割は、あくまで皆さんの挑戦を「支援」すること。主役は、あくまで皆さん自身です。 決して外部ベンダーへの「丸投げ」では実現できません。 特に大規模なDXプロジェクトでは、経営層の号令で始まったものの、現場のリアルな課題とズレてしまったり、 推進担当者でさえ「何のためにやっているんだっけ?」と目的を見失ってしまったりするケースが後を絶ちません。これでは、まさに目的地を見失ったまま航海を続ける船と同じです。クライアント側がこうした状態では、私たちがどれだけ伴走しようとしても、やがてプロジェクトは推進力を失い、崩壊してしまいます。 真のDX成功に必要なのは、クライアントとベンダーの強固な「二人三脚」。互いに手を取り合い、同じゴールを目指すパートナーシップが不可欠なのです。 そこでこの記事では、私が現場で見てきたDX推進を阻む「4つの壁」の正体をご紹介します。あくまでその課題は氷山の一角ではありますがこの記事が、皆さんの会社のDXプロジェクトを成功に導くための、確かな一歩となれば嬉しいです。 導入 その「AI導入」、本当に進んでいますか? 第1章:生成AI推進を阻む「4つの壁」とその正体 1-1. 羅針盤なき航海:「とりあえずDX」の罠 1-2. 構造的ボトルネック:DXを阻む旧態依然の組織体制 1-3. 人材の枯渇:誰もいない「推進リーダー」の席 1-4. イノベーションの足枷:変化を拒む社内ルール 第2章:解決の鍵は「アジャイル・ガバナンス」という新しい考え方 第3章:処方箋①:OKRで全社のベクトルを合わせる 3-1. OKR(Objectives and Key Results)フレームワークの導入 3-2. AIプロジェクトにこそ「SMART」な目標を 3-3. IBMやGoogleの事例に学ぶ 第4章:処方箋②:DX推進の中核組織「CoE」を立ち上げる 4-1. DX CoE(Center of Excellence)とは? 4-2. CoEが担う4つの重要機能 4-3. あなたの会社に最適なCoEの形は? 第5章:処方箋③:「リスキリング」で社内に眠る才能を開花させる 5-1. まずは全社員の「共通言語」を作る(基礎リテラシー層) 5-2. 次世代リーダーを選抜し、武者修行させる(専門家/リーダー層) 5-3. 現場のヒーロー「市民開発者」を育てる(実践者層) 第6章:処方箋④:「社内特区」でイノベーションの実験場を作る 6-1. 「AI/DX特区制度(社内サンドボックス)」の創設 6-2. 「ハンコのための出社」をなくす稟議・承認プロセスの改革 6-3. 「ゼロトラスト」で安全と速度を両立する まとめ:AIドリブンな組織への変革は、今日から始められる その「AI導入」、本当に進んでいますか? 「全社で生成AIを活用するぞ!」 経営陣の号令のもと、鳴り物入りでスタートしたDXプロジェクト。しかし、数ヶ月経った今、こんな課題に直面していませんか? 各プロジェクトが並行してバラバラで進行しており、全体として何を目指しているのか分からない… 誰が責任者で、どういう指示系統の元進めていくのかが曖昧でプロジェクトがうまく進まない… 推進できる人材が社内におらず、現場が闇雲の中で疲弊している… 新しいツールを使いたいだけなのに、社内申請にかなりの時間がかかる… もし一つでも当てはまったなら、ご安心ください。それはあなたの会社だけの問題ではありません。多くの企業が、大規模な変革の過程で同じような「成長痛」を経験しています。 この問題の本質は、生成AIという最新の「ソフトウェア」を、旧来の「組織OS」の上で無理に動かそうとしていることにあります。 本記事では、この摩擦を解消し、AI活用を真に加速させるための「組織OSのアップグレード方法」を、4つの具体的な処方箋としてご紹介します。 第1章:生成AI推進を阻む「4つの壁」とその正体 まず、多くの企業が直面する4つの課題を深掘りしてみましょう。これらは個別の問題ではなく、互いに絡み合い、変革のブレーキとなる厄介な壁を築いています。 1-1. 羅針盤なき航海:「とりあえずDX」の罠 「まずはやってみよう」と、明確なゴール設定よりも目先の実行しやすさを優先してプロジェクトを始めていませんか?これは変革の初期段階でよく見られる光景ですが、中盤になると「このプロジェクト、どこに向かってるんだっけ?」と、方向性を見失い、意思決定ができない「カオス状態」に陥りがちです 。 生成AIの進化は速く、長期的なゴール設定は難しいもの。しかし、だからといって指針がなければ、それはただの漂流です 。「レポート作成時間を50%削減する」といった、測れるビジネス成果とプロジェクトが結びついていない場合、問題はさらに深刻化します 。 仮に走りながら意思決定をしていくというプロセスも状況によっては間違いではないですが だとしても「とりあえずDX」から「計画的DX」へと昇華させるための修正・アップデートを進める機関やフローも同時に整備していかなければなりません。 1-2. 構造的ボトルネック:DXを阻む旧態依然の組織体制 現在の組織構造は、DXのスピード感に対応できていますか?意思決定の権限や責任の所在が曖昧で、複数のプロジェクトを横断して見ている人がいない…。そんな状態では、部門間の連携は生まれず、組織のサイロ化とスピードの低下を招くだけです。 特に大企業では、既存の複雑なプロセスが新しい挑戦の足枷となる「組織の硬直化」が起こりがちです 。DXを成功させるには、十分な権限を持つCDO(Chief Digital Officer)のような明確なリーダーと、専門の推進体制が不可欠なのです 。 1-3. 人材の枯渇:誰もいない「推進リーダー」の席 AIプロジェクトを力強く引っ張っていけるリーダーが社内にいますか?この問題は、組織体制の問題と深く関わっています。専門の推進組織がなければ、そうした人材が育つキャリアパスもなく、結果としてリーダー不在のプロジェクトが乱立してしまいます。 「DX人材不足」は日本中の企業が抱える課題ですが 、単に技術者が足りないという話ではありません。本当に深刻なのは、技術とビジネスをつなぐプロジェクトマネージャーやビジネスリーダーがいないことなのです 。 1-2で述べた組織体制にも通じる話ですが多くの企業はここを「ベンダー」側に完全委託してしまうケースが多いです。 1-4. イノベーションの足枷:変化を拒む社内ルール 「新しいツールを試したいのに、申請に2ヶ月もかかる…」 こんな経験はありませんか?安定した事業運営を前提に作られた社内ルールは、トライアンドエラーが必須のAI開発とは相性が最悪です。まるで、身体の「免疫システム」が新しい変化を拒絶しているかのよう。既存のやり方を守ることが優先され、イノベーションの芽が摘まれてしまっているのです 。 これら4つの問題は、互いに影響し合い、「ドゥームループ(破滅の連鎖)」と呼ばれる負のスパイラルを生み出します。推進体制がない(問題2)から人材が育たず(問題3)、リーダー不在でプロジェクトが迷走し(問題1)、成果が出ないことで組織はさらに保守的になり、ルールを強化する(問題4)。この悪循環を断ち切るには、包括的なアプローチが必要です。 第2章:解決の鍵は「アジャイル・ガバナンス」という新しい考え方 これらの根深い問題を解決するには、小手先の修正では不十分です。組織の意思決定とリスク管理のあり方そのものを変える、新しいOSが必要になります。そこで「アジャイル・ガバナンス」と最近呼ばれている仕組みについてご紹介させていただきます。 参考資料: https://biz.moneyforward.com/ipo/basic/10334/ これは、従来の「制限」や「管理」のためのガバナンスではありません。不確実な時代に「イノベーションを可能にする」ための、柔軟で動的なフレームワークです。ビジネス、IT、法務、セキュリティなど、多様な関係者が協力し、走りながらルールを改善していく。そんな新しいガバナンスの形です 。 アジャイル・ガバナンスは、予測不可能なAI開発を、旧来のガバナンスで管理しようとする「根本的なミスマッチ」を解消します。そして、前述の4つの課題をまとめて解決へと導きます。 目的の明確化(問題1):ゴールを設定しつつ、柔軟な見直しを許容する。 組織体制(問題2):多様な関係者を巻き込み、協働を促す。 人材不足(問題3):新しい専門家の役割を定義し、活躍の場を作る。 硬直化したルール(問題4):ルールを固定せず、継続的に更新する。 これは単なるプロセス変更ではなく、「失敗を恐れる文化」から「失敗から素早く学ぶ文化」への転換を意味します。次章からは上記の4つの課題それぞれに対しての処方箋について述べていきます。 第3章:処方箋①:OKRで全社のベクトルを合わせる 「結局、何を目指すんだっけ?」というカオス状態を抜け出すため、全社的な目標と各プロジェクトを明確に連携させるフレームワークを導入しましょう。 3-1. OKR(Objectives and Key Results)フレームワークの導入 Googleやメルカリなども採用する目標設定・管理手法「OKR」を導入します 。OKRは、ワクワクするような定性的な「目標(Objectives)」と、その達成度を測る定量的な「主要な成果(Key Results)」で構成されます。 目標(O): 「社内のナレッジ共有を革新する」といった、挑戦的で心躍るゴール。 主要な成果(KR): 「重要文書の検索時間を30%削減する」「新ナレッジ基盤のユーザー満足度80%を達成する」といった、具体的で測定可能な指標 。 OKRの素晴らしい点は、経営層から現場まで、組織全体の目標を一本化しつつ、現場の自律性を尊重できることです。トップダウンの戦略と、現場の主体性。DX推進におけるこのジレンマを解消する強力なツールとなります。 よくありがちな事例としては挑戦的な目標であるOの部分はかなり大々的に発令されていて一見DXとしての温度感は全体として高いように見えます。しかし実態としてKRが正しく紐づいて設定されておらず、推進という観点では現場の主体性が損なわれ、いつの間にかOについての解釈がチームや個人ごとに異なる、その結果目指すべき方向性のずれやプロジェクトのKPIとしての指針を見失っていくというケースは多く見てきました。 3-2. AIプロジェクトにこそ「SMART」な目標を OKRを機能させるため、すべての主要な成果(KR)が「SMART原則」(Specific:具体的、Measurable:測定可能、Achievable:達成可能、Relevant:関連性がある、Time-bound:期限がある)を満たすようにします 。 「業務効率を改善する」といった曖昧な目標ではなく、「特定の月次レポート5種類のドラフト作成を第3四半期までに自動化し、精度90%を達成する」といった、誰が見ても進捗がわかる具体的な目標に落とし込むことが、カオスを防ぐ鍵です 。 SMARTな設計ができていないとプロジェクト推進の中盤での後戻りが発生する可能性や、今やっていることの重要性を見失う、結果プロジェクト全体としての軸を改めて定め直すといったことも起こりがちです。 3-3. IBMやGoogleの事例に学ぶ IBMの大規模システム導入事例では、IT、営業、プロジェクトマネージャーといった異なるチームが、全社目標に連携したOKRをどう設定したかが示されています 。部門横断プロジェクトで、各チームが自分の役割を理解しつつ、同じゴールに向かうための優れたお手本となるでしょう。 OKRの導入は、単なる管理手法の変更ではありません。それは、「何をもって成功とするか」を組織全体で真剣に問い直す、文化的な変革となります。 大規模なDX推進の現場ではさまざまな部署から集められた人員によってチームが組成され推進していくケースも少なくありません。しかし本来チームや部署によってプロジェクトに対する温度感やKPI、MBOの設定などは多種多様であり、元々は同じ方向を向いていないケースが多いです。 これをそのままにしておくと、メンバーによってのモチベーションや自発性などに温度感の差異が起こり、部署間のコンフリクトや推進の鈍化が起こります。それらをいかに全社目標に連携させて設定していくかが重要な鍵となります。 参考: https://www.ibm.com/jp-ja/think/topics/okr-examples 第4章:処方箋②:DX推進の中核組織「CoE」を立ち上げる 変革の強力なエンジンとなる専門組織「DX CoE(Center of Excellence)」を設立し、現在の曖昧な意思決定構造と部門間のサイロを破壊しましょう。 4-1. DX CoE(Center of Excellence)とは? CoEは、単なるIT部門ではありません。ビジネス、テクノロジー、データ分析など、多様な専門家が集結した、組織横断の戦略チームです 。社内に散らばった知識や取り組みを集約し、全社を俯瞰する「司令塔」として機能します 。 4-2. CoEが担う4つの重要機能 ナレッジ集約: 社内外の成功事例や教訓を集め、全社に共有し、組織の学習スピードを加速させます 。 ガバナンス: アジャイル開発手法や評価指標(KPI)など、全社的な標準ルールを作り、プロジェクトの一貫性を保ちます 。 技術支援: 各部門のプロジェクトに対し、専門知識を提供し、社内コンサルタントとして課題解決をサポートします 。 人材育成: 人事部と連携し、次の章で紹介する全社的なリスキリングを主導します 。 CoEの成功は、CoEが支援したプロジェクトの成功率や、プロジェクト期間の短縮率といった具体的な貢献度で測るべきです 。 4-3. あなたの会社に最適なCoEの形は? CoEにはいくつかのモデルがあり一部を紹介します。私が現場で携わっている範疇においては連携・ハイブリッド型の事例が多いです。 モデル種別 説明 長所 短所 中央集権型 強力な中央組織が全社のDXを統制する。 ・迅速な標準化 ・強力なガバナンス ・現場ニーズとの乖離 ・官僚化のリスク 連携・ハイブリッド型 中央のDX本部が戦略や基盤を提供し、各事業部門と連携して推進する。 ・中央戦略と現場ニーズの両立 ・現場の主体性の尊重 ・役割分担の明確化が必要 ・調整コストが発生 このモデルでは、CoEが戦略的な方向性を示しつつ、事業部門が現場の知識を活かして主体的にイノベーションを起こせます。CoEの最も重要な役割は、経営の言葉を技術の言葉に、技術の可能性をビジネスの言葉に「翻訳」することであり、この翻訳機能こそが部門間の壁を壊し、DXの成功確率を飛躍的に高めるのです。 第5章:処方箋③:「リスキリング」で社内に眠る才能を開花させる DX人材は、外から採用するだけでは足りません。社内の人材を戦略的に育成する「リスキリング」こそが、持続可能な変革の鍵です。全従業員を対象とした階層的な育成プログラムを始めましょう。 5-1. まずは全社員の「共通言語」を作る(基礎リテラシー層) 経営層も含めた全従業員を対象に、DX/AIリテラシーの基礎研修を必須化します。eラーニングなどを活用し、AIの基本的な仕組みや可能性、倫理的な注意点について、全社で共通の理解を醸成しましょう 。全社員に教育を義務付けることで、変革の土壌を一気に耕すことができます 。 5-2. 次世代リーダーを選抜し、武者修行させる(専門家/リーダー層) ポテンシャルの高い人材を選び、次世代のDXリーダーを育成する特別プログラムを創設します。技術だけでなく、プロジェクトマネジメントや変革を主導するリーダーシップを総合的に学びます。いわゆるラウドスピーカーと呼ばれる彼らを集中的に育成していくことでAIに対する社内認知や行動意欲を波及効果で拡大させることが可能になり、よりDXの文化醸成が活発になります。 5-3. 現場のヒーロー「市民開発者」を育てる(実践者層) 事業部門の従業員が、プログラミング不要の「ノーコード・ローコードツール」などを使い、自らの手で業務改善を進められるように支援します。現場の深い業務知識と新しいデジタルツールが結びついた時、大きなイノベーションが生まれます。これは、トップダウンで進むDXを、現場にとって「自分事」にするための最も有効なアプローチです。 階層レベル 対象者 主要な学習目標 カリキュラム・手法の例 基礎リテラシー層 全従業員 ・AI/DXの基本概念の理解 ・自社DX戦略の理解 ・eラーニング(必修) ・全社ワークショップ 実践者層 各部門の業務担当者 ・ノーコード/ローコードツール活用 ・データ分析・可視化スキル ・ツール別ハンズオン研修 ・社内ハッカソン 専門家/リーダー層 選抜された人材 ・アジャイルプロジェクト管理 ・チェンジマネジメント ・社内大学(選抜制) ・越境学習(他社派遣等) 基礎リテラシー層への全体的なAIナレッジの展開を経てAIを使っていく意識醸成を進め、それらの推進、ないしは実践者層への業務導入を専門家・リーダー層が強力に進めていく。この流れを構築していくことが文化醸成、強いてはAI浸透と強固な組織レベル底上げに必要な要素となります。 第6章:処方箋④:「社内特区」でイノベーションの実験場を作る イノベーションの足枷となっている硬直的な社内ルールを、思い切って変えていきましょう。実験を奨励し、スピードを加速させるための制度改革です。 6-1. 「AI/DX特区制度(社内サンドボックス)」の創設 社内に、実験的なプロジェクトのための「AI/DX特区」、すなわち社内サンドボックスを設けましょう。これは、認定されたプロジェクトに限り、通常の煩雑な手続きを免除・簡素化する特別なエリアです 。 何ができる?: この特区内では、新しいクラウドサービスの利用申請が迅速化されたり、実験専用のセキュアな環境が提供されたりします 。 どう管理する?: CoEがプロジェクトを審査・監督することで、自由な実験と組織的な統制のバランスを取ります。 6-2. 「ハンコのための出社」をなくす稟議・承認プロセスの改革 全社的なボトルネックである稟議プロセスを、単に電子化するだけでなく、プロセスそのものを見直しましょう 。「この承認は本当に必要か?」「現場の決裁権限を増やせないか?」といった視点で、不要なステップを大胆に削減することが重要です 。 6-3. 「ゼロトラスト」で安全と速度を両立する セキュリティの考え方を、社内は安全、社外は危険という「城と堀」モデルから、「誰も信頼せず、常に検証する」という「ゼロトラスト」モデルへ移行しましょう 。これにより、従業員はどこからでも安全に必要なツールにアクセスでき、アジャイルな働き方を強力にサポートします。これはNTTデータ先端技術やLIXILなども取り組む、現代の必須インフラです 。 この「社内サンドボックス」は、単なる技術の実験場ではありません。それは「新しいガバナンスのプロトタイプ」です。サンドボックス内で試された新しいルールが成功すれば、いずれ全社標準へと展開していく。安全な環境で、未来の会社のルールを生み出していくための、極めて重要な仕組みなのです。 まとめ:AIドリブンな組織への変革は、今日から始められる 本記事では、生成AI推進の過程で多くの企業が直面する4つの壁と、それを乗り越えるための4つの処方箋をご紹介しました。 目的の明確化: OKRで全社のベクトルを合わせる 体制の改革: DX CoEを設立し、変革のエンジンを作る 人材の育成: リスキリングで社内の才能を解き放つ 制度・環境の整備: 社内サンドボックスでイノベーションを加速する これらは、一つひとつが独立した施策ではなく、相互に連携することで最大の効果を発揮します。とはいえ、すべてを一度に始める必要はありません。まずは、あなたの組織で最も着手しやすいところから、小さな一歩を踏み出してみてはいかがでしょうか。 まずは3ヶ月でできること 経営層を巻き込み、小さなCoEチームを発足させる。 特に重要なパイロットプロジェクトで、OKRを試してみる。 そのプロジェクト限定の「社内サンドボックス」を設計してみる。 今直面している課題は、未来の競争優位性を築くための絶好の機会です。この記事が、あなたの会社の変革を後押しする一助となれば幸いです。
エンジニア集団の中に潜む非エンジニアの生態 はじめまして。Insight Edgeセールス・コンサルティングチームで契約業務や売上管理を担当している非エンジニアの長尾です。 周りを見渡せば、AIやデータサイエンスの博士号を持つメンバーや、大規模なシステム開発を率いてきた猛者ばかり。そんな技術のプロフェッショナル集団の中で、私はコードを書かない「非エンジニア」として働いています。 私の周りでは、日常会話で「fetchするためのMCPサーバを...」や「LLMによるペルソナ生成のプロンプトが…」といった言葉が飛び交います。それを聞きながら「今はポジティブな話?それともネガティブな話…?」と、話の趣旨すら掴めないことも。 今日は、そんな私が専門外の領域でいかにして価値を見出し、課題解決に挑んだのか。そして、ローコードツール「Dify」を使い、まずは自社の案件検索を効率化するAIツールを自力で作り上げた話をさせてください。これは、私個人の奮闘記であると同時に、私たちの会社Insight Edgeが持つ「やってみる」という文化の証明でもあります。 エンジニア集団の中に潜む非エンジニアの生態 1. 「あの情報、どこだっけ?」- 日々の業務に潜む巨大な時間泥棒 2. やってみる、から始まった挑戦 3. ゼロからのAI開発ジャーニー:非エンジニアが挑んだ3週間 ステップ1:AIに知識を叩き込む(ナレッジベースの構築) ステップ2:AIとの対話方法を教える(プロンプトエンジニアリング) ステップ3:ひたすら試し、改善する 4. コードを書かない私が描く、次の景色 1. 「あの情報、どこだっけ?」- 日々の業務に潜む巨大な時間泥棒 私の主な業務は、お客様との契約締結や進行中のプロジェクトの売上管理です。一見、華やかなデータ分析やAI開発の世界とは少し離れた「守り」の領域かもしれません。しかし、この業務には大きな課題が潜んでいました。それは、「情報のサイロ化」です。 「あの案件で使った契約書の文言、今回も流用できそうだな…」 「去年とほぼ同じ内容の案件、プロジェクト名は何だったっけ…」 こうした情報を探すたびに、社内のストレージ、チャットツールなど、様々な格納先を何十分も彷徨う日々。情報は確かにあるのに、必要な時にすぐに見つけられない。この“時間泥棒”は、私の生産性を着実に蝕んでいました。 エンジニアたちが最新技術で顧客の課題を解決している横で、私は社内の情報検索という原始的な課題に頭を悩ませていました。この状況を、何とかしたい。それがすべての始まりでした。 2. やってみる、から始まった挑戦 課題を嘆くだけでは何も変わりません。Insight Edgeに入社してまもなく1年、この会社で見聞きしてきたことを糧に、自ら行動を起こすことを決意しました。幸い、当社には職種や経験を問わず、課題意識を持って新しい挑戦を奨励する「やってみる」という素晴らしい文化が根付いています。 まず頭に浮かんだのは、「ChatGPTのようなAIに、社内案件のことを聞けたら最高じゃないか?」という素朴なアイデアでした。 しかし、すぐに壁にぶつかります。ChatGPTは、当然ながら社内の機密情報や最新のファイルを知りません。次に、社内情報と連携したGoogleのAIエージェントを試してみましたが、あらゆる情報ソースを参照するがゆえに、近いですが異なる情報を拾われることが多く私が求める粒度と精度での回答は得られませんでした。 私が欲しいのは、博識なAIではなく、「ウチの会社の事情」に詳しいAIアシスタントだったのです。 この課題を解決する鍵は「RAG(Retrieval-Augmented Generation)」という技術にあると突き止めました。これは、AIに外部の知識(今回の場合は社内ドキュメント)を「参照」させながら回答を生成させる仕組みです。非エンジニアの私にも分かるように言うなれば、「分厚い資料集を横に置き、常にそれを見ながら質問に答えてくれるサポーター」といったイメージです。 RAGの概念は理解できても、実装はできない。そんな私の前に現れたのが、ノーコード/ローコードでAIアプリを構築できるプラットフォーム「Dify」でした。これなら、私でもAIアシスタントを作れるかもしれない。暗闇に光が差した瞬間でした。 3. ゼロからのAI開発ジャーニー:非エンジニアが挑んだ3週間 Difyと出会ってからの行動は早かったです。「意外とすぐにできるかも?」という淡い期待を胸に、AIツール開発に着手しました。 ステップ1:AIに知識を叩き込む(ナレッジベースの構築) まずはAIの「資料集」となるナレッジベースの構築です。ソースとなるファイルから機密情報を削除し、Difyにアップロードしていきます。しかし、すぐに最初の壁にぶつかりました。取り込んだ情報をもとに質問しても、まともな答えが返ってこないのです。 原因を調べると、AIが正しく読み込めるようにデータを「成形」する必要があることに行き着きました。1行目をヘッダーにする、セルの結合をなくす、不要な空白を削除するなど、エンジニアにとっては当たり前かもしれないルールに悪戦苦闘。ひとつひとつデータを整え、無事にナレッジベースを完成させました。 ステップ2:AIとの対話方法を教える(プロンプトエンジニアリング) 次に、AIへの指示、つまり「プロンプト」の設計です。「あなたは、提供されたデータを分析する優秀なアシスタントです」といった役割設定から、「データにない情報や推測で回答してはいけません」といった禁止事項(ガードレール)まで、試行錯誤を繰り返しました。このチューニング作業は、まるでゲームでキャラクターを育成するような面白さがあり、先人たちが築いた定石(ベストプラクティス)を参考にすることが成功への近道である点も似ています。 ステップ3:ひたすら試し、改善する プロトタイプが完成してからは、あらゆる角度から質問を投げかけ、回答の精度を検証しました。生成AIは数値を扱うのが少し苦手、といった特性もこの過程で理解しました。精度が悪い部分があればステップ1と2に戻って修正を繰り返し、最終的に、求める粒度の質問に対して約90%の精度で回答してくれるツールが完成しました。 もちろん、専門的な壁にぶつかることもありました。そんな時は、社内のエンジニアに「ここについて教えてください!」とチャットで助けを求めます。すると、「Difyは使ったことないけど、ちょっと待ってて」と言いながら、数分後には的確なアドバイスが返ってくる。挑戦の過程で生まれた壁は、誰もが快く一緒に乗り越えようとしてくれる。これも、この会社の「みんなでやる」という文化の現れだと感じています。 4. コードを書かない私が描く、次の景色 約3週間の試行錯誤の末、ついに案件情報検索AIが完成しました。 これまで週に数時間かかっていた情報の捜索は、今では1件あたり数秒で完了します。当初の目的だった「業務効率化」と「情報のサイロ化改善」は、無事に達成されました。しかし、得られたものはそれだけではありません。自分でものづくりのプロセスを経験したことで、「次はこんなこともできるかも」と、新たなアイデアが次々と湧いてくるようになったのです。 この経験を通じて私が学んだのは、課題解決の手段は、必ずしもコードを書くことだけではない、ということです。非エンジニアだからこそ見える現場の課題があり、非エンジニアでも使えるツールがある。そして、その挑戦を支えてくれる仲間と文化がある。 Insight Edgeは、AIやデータという最先端技術を駆使して、お客様のビジネスと世界を「Re-design」することを目指す会社です。私の小さな挑戦は、その壮大なミッションのほんの一端かもしれません。しかし、現場の課題に深く潜り込み、テクノロジーを武器にそれを解決するという点で、本質は同じだと信じています。 もしあなたが、私のように専門外の領域で自分の価値をどう発揮すべきか悩んでいるなら、思い出してください。あなたの視点こそが、ブレイクスルーの鍵になります。 なぜなら、あなたは誰よりもその課題の「解像度」が高い当事者だからです。現場の人間だからこそ、「これじゃない感」のある片手落ちの効率化に留まらず、「こうあるべきだ」という理想から逆算して根本的な解決策を追求できます。 今の時代、その理想を現実にするための武器は、驚くほど身近な場所にあります。大事なのは、「誰かがやってくれる」のを待つのではなく、自らが最初の実践者になるという「やってみる」勇気です。 その一歩が、目の前の課題を解決するだけでなく、あなた自身の「やってみたい」という新たな景色を見せてくれるはずです。
プロローグ:この記事を書くことになったきっかけ 今回の記事は、Insight Edgeでデザインストラテジストを務める飯伏さんと、AIである私との対話から生まれました。 実は2年前にも飯伏さんは自らの仕事についてテックブログにまとめていました。そのときは「課題探索やアイデア発想を支援するデザインシンカー」としての役割紹介でした( デザインシンカーとしての仕事 ~DX推進の技術専門会社にて~ - Insight Edge Tech Blog )。 そこから2年、生成AIの登場と普及、住友商事グループにおけるデジタル推進の加速、そして事業会社の自走意識の高まりなど、DXを取り巻く環境は大きく変化しました。 こうした変化の中で「仕事の幅がどう進化したのか」を改めて整理したい──そんな飯伏さんの思いから、今回の対談記事が始まりました。 導入 AI: 今日は「デザインストラテジスト」という少し耳慣れない肩書きのお仕事について伺います。 もともとこの役割は海外のデザイン会社やグローバル企業で広がったポジションで、ユーザーリサーチや未来洞察を通じて「事業や組織の将来像を描き、実現するための道筋をデザインする」仕事です。 たとえば米国や北欧の企業では、経営戦略の段階からデザインストラテジストが参画し、ユーザー視点とビジネス視点を橋渡しする事例も多く見られます。日本でも大手企業やデザインファームで少しずつ導入され始めていますが、まだ一般的には馴染みの薄い職種かもしれません。 今回はその中でも、技術専門会社であるInsight Edgeにおけるデザインストラテジストの実際の仕事を飯伏さんに紹介していただきたいと思います。 飯伏: よろしくお願いします。私はInsight Edgeで「人や組織の視点からDXを前に進める」役割を担っています。今日はその内容を具体的にお話しできればと思います。 会社での立ち位置 AI: DXを技術で進める人はイメージしやすいですが、「人や組織の視点から前に進める」というのは少し抽象的ですね。具体的にどんな枠組みで仕事をしているんですか? 飯伏: 大きく分けると2つです。 ひとつは、事業会社のDX推進・AI活用を担う専門組織(いわゆるCoE=センター・オブ・エクセレンス)の立ち上げや継続に伴走する支援。 もうひとつは、必要なタイミングで変革や技術活用を後押しするピンポイントの支援です。 仕事①:CoE的な組織への伴走支援 AI: まずは前者から詳しく伺いたいです。CoEというのは「全社的にDXやAIを推進していくための専門チーム」のことですよね。そこへの伴走ではどんなことをされるのでしょう? 飯伏: 大きく「戦略づくり」「実装支援」「浸透・醸成」の3つに分けて関わります。 戦略づくり DX推進やAI活用を担う組織が「どんな姿をめざすのか」を描き、部門横断のビジョンやロードマップに落とし込みます。 具体的には、経営層や現場担当者へのインタビュー、未来トレンドの探索などを組み合わせて「現状の延長線」ではなく「理想の将来像」を共に構想します。 あるグループ企業では、データ活用推進部門と共に「部門自体の理想像」や「各部門の重点テーマ」をワークショップで整理し、3年の中期的な将来像やロードマップ、1年間の具体計画にまとめました。 実装支援 ここは2年前から取り組んでいる範囲ですね。 戦略を「絵に描いた餅」で終わらせず、現場での実装に結びつけるため、データ分析やAI活用の企画を現場と共創し、ソリューションを具体化します。 その際には、利用者となる現場担当者との対話を重ね、Asis/Tobe像をビジュアルで整理。発散と収束を繰り返しながら「本当に必要な価値」と実現方法を具体化していきます。 フェーズが進むほど専門性が増すため、エンジニアやUI/UXデザイナーも早期から一体で取り組むようにしています。 浸透・醸成 短期施策を積み重ねながらDXを文化として根付かせ、中長期的に自走できる組織を育てます。 あるCoEでは「増やすべき人材像」の検討や、AI活用のハンズオン研修、DX最新動向を伝えるセミナーを企画。最近はチームメンバーも増え、システム思考など新たなアプローチを取り込みながら挑戦を広げています。 AI: 単なるコンサルティングではなく、立ち上げ時期の戦略から実装、中長期でのDXやAIの推進の醸成まで伴走するのが特徴なんですね。 飯伏: はい、まさにそうです。 ただ一点補足で、立ち上げ時期に戦略づくりから始める印象がありますが、実態は「頭でっかちな検討はいらないから、速く/早く使えるものを作って現場を変えていこう」という期待も多く、実装支援に重きを置いて戦略づくりと並行して進めることが多いですね。 仕事②:ピンポイントの支援 AI: もうひとつの軸、ピンポイント支援についても教えてください。こちらは「必要なタイミングで変革や技術活用を後押しする」と伺いました。 飯伏: そうです。 例えばあるグループ企業では、AIを活用する事業構想とコミュニケーション活性化を目的に、未来洞察をベースとしたアイデア発想のワークショップを実施しました。普段の業務とは異なり、現状ではなく未来を起点とした発想で、AIの活用についてアイデアを広げました。他には、複数日にわたるワークショップで事業構想に取り組むこともあります。 また別のグループ企業では、AIを活用したアプリのプロトタイピング(バイブコーディング)を現場で広く実践していくことを目的に、数週間にわたる道場形式のハンズオンワークショップを企画しています。 AI: なるほど。伴走支援が「じっくり支える」だとすれば、ピンポイント支援は「狙いを絞って次のアクションを後押しする」役割なんですね。 飯伏: はい、その表現はしっくりきます。 2年前からの進化 AI: ちなみに、 2年前にもブログ を書かれていましたよね。当時はどんな仕事を中心にされていたんですか? 飯伏: その頃は「課題探索やアイデア発想の支援」が中心でした。特定の取り組みテーマや対象業務の領域は決まっているものの、具体的に何をしていくのかがモヤモヤしている。そのような状態から整理してPoCにつなげるのが主な役割でしたね。 AI: そこから2年で、CoE的な組織への伴走支援での戦略・実装・醸成や、ピンポイントの変革や技術活用の後押しまで仕事が広がった背景には何があるんでしょう? 飯伏: 大きく3つあると思います。 1つ目は、生成AIの発展で、技術活用の可能性・危機感が一層広がったこと。 2つ目は、1つ目に関連しますが、事業会社の自走意識の高まりです。「現場実装を自ら進める」というマインドが浸透してきました。 3つ目は、住友商事グループとしてデジタル領域のより一層の強化に取り組まれていることです。ここは、住友商事グループで最新技術から内製機能まで提供するInsight Edgeの位置づけとしても重要なポイントです。 AI: ここまででも十分大きな変化ですね。ただ、飯伏さんのお話からすると「フィールドの広がり」も大きな要素のように思えますが、いかがですか? 飯伏: そうですね。元々、住友商事グループは多様な事業領域を持っています。その中で業種も文化も異なる現場に関わることで、得られる視点や学びが増え、次の取り組みに活かされると実感しています。つまりフィールドの豊かさが、進化させてくれたとも思います。 大事にしていること AI: これまでのお話をとおして、経営や技術、特定のフレームワーク以上に「人に寄り添う」スタンスが軸にあるように感じます。 飯伏: まさにそこが一番大事です。私は「人や組織の想いに寄り添いながらモヤモヤを整理し、“前に進める一歩”を形にする」ことを常に意識しています。 ここで大事なのは、答えを押しつけることではなく、共に考え続けること。ときには対話を通じて価値観を引き出し、ときには未来を描くワークで可能性を広げ、ときには泥臭く詳細な整理・検証に取り組みます。 住友商事グループという多様なフィールドで仕事をするからこそ、この姿勢がより大切だと感じます。業種も組織文化も異なる人々に向き合うと、共通点も違いも浮かび上がる。その中で「どうすれば一緒に前に進めるか、一緒に挑戦を実現していけるか」をデザインする。まだまだ住友商事グループの中でも、デザインストラテジストのような広義のデザインは浸透していませんが、デザインのアプローチを武器にこのフィールドで取り組むのは大きなやりがいです。 AI: なるほど。つまり「デザイン」は表層の見た目を整えることではなく、異なる人や組織をつなぎ、動き出せる状態をつくる営みだということですね? 飯伏: そうです。デザインストラテジストは、単に新しい仕組みやサービスを導入する役割ではなく、組織が変化・自走できる力を育む存在。そのために私は、現場の人の想いと組織の現実、そして経営や技術を結びつけ、“前に進める一歩”を一緒に生みだす場・機会のデザインを大切にしています。 AIとの協働 AI: ここまで伺っていると、人や組織と向き合う仕事が中心ですが、実際にはAIとも一緒に仕事をされていますよね。 飯伏: そうですね。AIは単なる調べ物のツールではなく、それこそ“共創パートナー”のような存在です。 たとえば、経営層向けの「AIを活用した事業構想を検討するワークショップ」を設計するとき、私が持っていたイメージを投げかけると、AIが具体的な流れを整理して案を複数パターン作ってくれました。それを叩き台に現場感覚で磨き込むことで、実践的なプログラムに仕上げられたんです。 ほかにも、アイデアを整理する際のフレームづくりや壁打ち、デザインストラテジストの採用検討なども一緒に取り組みました。 AIが出してくれるのは“考えるきっかけ”や“俯瞰の視点”。そこに現場の肌感覚を掛け合わせることで、速くよりよい形に仕上がる実感があります。デザインストラテジストとして技術をどう活かすか考える立場としても、AIとの共創はますます欠かせないと感じています。 今後に向けて AI: 最後に、今後の展望を教えてください。 飯伏: これからもDX推進に関わりながら、生成AIやデータ分析を当たり前とした新しいデザインのアプローチも試していきたいと思っています。そして「テクノロジーとクリエイティブの力で、変革を続ける組織を増やす」ことが、デザインストラテジストとしての目標です。 AI: 今日はありがとうございました。Insight Edgeの中にこうした役割を担う方がいることを知っていただけると、読者の方にも新しい発見になると思います。 飯伏: こちらこそ、ありがとうございました。 エピローグ:対話を通じて見えてきたこと 今回の対談を通じて見えてきたのは、「デザインストラテジスト」という役割が決して一人で完結する仕事ではない、ということです。 人や組織の想いを丁寧に汲み取りながら進める仕事であると同時に、AIのようなテクノロジーとも協働しながら新しい道筋を描いていく──それが今の時代のデザインストラテジストの姿なのかもしれません。 また、2年前に「課題探索やアイデア支援」として紹介した仕事は、この2年間で伴走による「戦略づくり」「実装支援」「浸透・醸成」へと広がりました。 印象的だったのは、こうした広がりの中で「デザイン的なアプローチ」そのものの価値が、以前よりも一層強く発揮されていることです。それは単に美しいビジョンを描くことではなく、複雑な利害や制約の中で対話を重ね、納得して動き出せる道筋をデザインすることでもあります。曖昧な状況を整理し、共創を通じて前に進める一歩を形にする力が、DXやAI活用を進める現場で確かな意味を持ち始めているように感じました。 読者の皆さんにとってこの記事が、DXやAI活用に向き合うときに「人と組織をどう巻き込み、どう共創していくか」を考えるヒントになれば幸いです。 ──なお本記事は、これまでの飯伏さんとのやり取りをもとにAIが下書きを出力し、それを飯伏さんが手直しする形で仕上げられています。まさに「AIとの協働」の一例として読んでいただければと思います。
はじめに わずか3日で開発して稼働開始、そして1年間トラブルゼロ。 普通なら半年〜1年かかる開発も、Insight Edgeのデータサイエンティストとエンジニアは、ワンチームで動き、爆速で価値をクライアント企業に届けています。 仕様書の山も、開発ベンダーとの往復メールもありません。 モデルを作ったらアジャイル方式で即アプリ化し、 クラウドにデプロイして、翌日にはクライアントが使い始めることもあります。 この記事では、そんな爆速開発を可能にしている データサイエンティスト×エンジニアの共同開発 の事例を3つ紹介し、最後にポイントをまとめます。 目次 事例1:売上予測アプリを2ヶ月でリリース 事例2:3日で完成!S3+Lambdaだけの軽量予測システム 事例3:10時間かかっていた遺伝的アルゴリズムを並列分散処理で高速化 爆速開発を可能にする3つの秘密 事例1:売上予測アプリを2ヶ月でリリース PoCで作成した売り上げ予測モデルが一定の性能を満たし、クライアントから「現場ですぐに試したい」という要望が寄せられました。早く、安く、安全にアプリ化する必要がある状況です。 そこでまず、データサイエンティストがざっくりとしたUI案とアーキテクチャ図をパワポで作成しました。それをエンジニアに見せ、「こういうものを作れないか」と相談します。 すると、エンジニアはすぐにAWSのアーキテクチャ案と工数・運用費の見積もりを返してくれました。そのスピード感もあって、提案はスムーズに承認されました。 開発は同じGitリポジトリ上で進めました。 データサイエンティストは予測モデルを呼び出すクラスのインターフェースとサンプルコードを、実装前にMkDocsでドキュメント化して事前に共有。 そうすることで、エンジニアとデータサイエンティストはそれぞれの担当部分を並行して開発できました。 Daily MTGは合同で行い、週次でクライアントとのMTGでフィードバックを受け、それを即反映。 ベンダー向けの仕様書の山を作ることもQAのラリーを大量に行う必要もなく、アジャイルな形で進められました。 結果、プロジェクト開始からわずか2ヶ月で現場利用が開始され、クライアントの満足度も高く、ベンダー委託時にありがちな遅延やコスト増は一切ありませんでした。 事例2:3日で完成!S3+Lambdaだけの軽量予測システム ある案件では、「UIは不要、最低限の機能で良い」という要望がありました。クライアントはとにかく早く動くものを試したいという状況でした。 そこでデータサイエンティストは、わずか一行で要件をエンジニアに伝えました。 「S3にファイルを置いたらLambdaが起動して、予測してS3に結果を返す感じで作りたいんです」 エンジニアはすぐ設計に着手し、S3へのアップロードをトリガーにLambdaが動き、Fargateで予測を実行して結果を保存するというシンプルな構成を実装しました。 ※実行時間がLambdaの上限を超えて必要な処理が含まれていたため、Fargateも含む構成になりました。 わずか3日でシステムは稼働を開始。開発・運用コストがあまりにも低かったため、クライアントには驚かれました。運用開始から1年が経過しますが、障害は一度もなく、コード修正も即日で対応できています。 事例3:10時間かかっていた遺伝的アルゴリズムを並列分散処理で高速化 PoC中の最適化計算で、遺伝的アルゴリズムの実行時間が長すぎる(10時間!)という課題がありました。アルゴリズムの根本的な改良には時間が足りない状況です。 そこで、データサイエンティストは既存コードと「ざっくりした並列化のイメージ図(Master-Slave方式)」をエンジニアに共有しました。エンジニアはコードを読み込み、即座に並列分散処理を実装。 初版ができた後には、こんな最高な会話もありました。 結果として計算時間は大幅に短縮され、実用化に必要とされた処理速度を余裕で達成。使いやすさも向上。アプリへの移行も非常にスムーズに進みました。 こちらの事例については、別の記事で技術面を詳しく解説しておりますのでぜひご覧ください↓ 10時間かかっていた遺伝的アルゴリズムをAWS Lambdaで高速化 爆速開発を可能にする3つの秘密 このスピードを実現できるのは、大きく3つの要因があると考えています。 1つ目は「ワンチームで動く」こと。 データサイエンティストとエンジニアが同じリポジトリでプルリクを送り合い、Daily MTGも合同で行うため、仕様変更や方針転換が即座に反映されます。 Insight Edgeのコアバリューに「みんなでやる」というものがありますが、まさにそのバリューに則って動いています。 2つ目は「お互いの領域を知る」こと。 データサイエンティストはAWSやGCPの資格を所持しているメンバーも多く、アーキテクチャ図もある程度は描けます。一方でエンジニアはAIやアルゴリズムに理解があり、遺伝的アルゴリズムのコードもすぐに理解し改良できました。この「浅くても相手の言葉がわかる」関係が、やり取りを高速化します。 3つ目は「まず使ってもらう」こと。 完璧なモデルを机上で作り上げるよりも、現場に投入して改善プロセスを回す方が結果的に現場にとって良いモデルができる。小さく作って早く届け、そこから進化させるという考え方です。 最後に Insight Edgeでは、 データサイエンティストが作ったモデルを、エンジニアの力で爆速で現場投入 できる環境があります。 このような爆速開発チームで一緒に価値を届けてみたい方、データサイエンスとエンジニアリングの境界を越えて働きたい方は、ぜひ 採用ページ をご覧ください!