<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:webfeeds="http://webfeeds.org/rss/1.0">
  <title>AIDD カテゴリ | フューチャー技術ブログ</title>
  <subtitle>AIDD カテゴリの記事一覧</subtitle>
  <icon>https://future-architect.github.io/feed_icon.png</icon>
  <logo>https://future-architect.github.io/apple-touch-icon.png</logo>
  <webfeeds:icon>https://future-architect.github.io/apple-touch-icon.png</webfeeds:icon>
  <webfeeds:accentColor>258fb8</webfeeds:accentColor>
  <link href="https://future-architect.github.io/categories/AIDD/atom.xml" rel="self"/>
  <link href="https://future-architect.github.io/categories/AIDD/"/>
  <updated>2026-08-19T15:00:00.000Z</updated>
  <id>https://future-architect.github.io/categories/AIDD/</id>
  <generator uri="https://hexo.io/">Hexo</generator>
  <entry>
    <title>AI Dev Day 2026に参加しました！</title>
    <link href="https://future-architect.github.io/articles/20260820a/"/>
    <id>https://future-architect.github.io/articles/20260820a/</id>
    <published>2026-08-19T15:00:00.000Z</published>
    <updated>2026-08-19T15:00:00.000Z</updated>
    <author><name>坂本慎司</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260820a/top.jpg" alt="" width="1200" height="900">

<h2 id="はじめに">はじめに</h2><p>TIGの坂本です。AIエージェントの導入・推進・開発を担当しています。</p>
<p>AIエージェントを現場に導入することは、もはや至上命題になりました。プロンプトエンジニアリングから始まり、ハーネスエンジニアリング、ループエンジニアリングと、うまく動かすための語彙が半年ごとに入れ替わっています。</p>
<p>2026年7月24日、AI Dev Day 2026に参加しました。一日通して壇上で繰り返し出てきたのは、新しい呼び名ではありませんでした。コストと、信頼です。</p>
<p>フューチャー技術ブログでは、他のカンファレンスの参加レポートも公開しています。あわせてどうぞ。</p>
<ul>
<li>初めての海外カンファレンスとKubeCon Japan参加レポート</li>
<li>try! Swift Tokyo 2025に参加してきました！</li>
</ul>
<h2 id="AI-Dev-Dayとは">AI Dev Dayとは</h2><p>もともとは「Azure OpenAI Service Dev Day」（通称AOAI Dev Day）という名前で、2024年から開催されていました。2026年からはAzureに限らない形となり、<strong>AI Dev Day</strong>へ改称されています。掲げているのは、AI技術をPoCで終わらせず、本番環境へどう載せるか、という実践寄りの問いです。コミュニティ主導のカンファレンスで、成り立ちのとおりAzure関連の技術要素を使った発表が多い印象でした。</p>
<h2 id="Opening「Fast-Alone-Far-Together-」">Opening「Fast Alone. Far Together.」</h2><p>生成AIの活用が始まってから、3年ほどが経ちました。PoCは進んでいるものの、プロダクションレベルまで到達したものは多くありません。オープニングで運営から出たのは、デベロッパーが実際に価値を届けるところまでいかなければ、AIブームそのものがバブルとして終わってしまうのではないか、という危機感でした。</p>
<p>スローガンの「Fast Alone. Far Together.」は、その裏返しです。早く進むだけなら1人のほうがよく、AIを使えばコードは1人でいくらでも書けるようになりました。それでも遠くへ行くには、知見を持ち寄って皆で進むしかありません。この日のセッションは、どれもこの前提の上に置かれていました。</p>
<h2 id="Keynote-From-prototype-to-production（Jeff-Hollan氏-Microsoft）">Keynote: From prototype to production（Jeff Hollan氏 &#x2F; Microsoft）</h2><p>3年前と比べて、行っている仕事の全体が入れ替わったという話から始まりました。以前はコーディングが好きでも、デザインが得意でなければウェブサイトの構築は難しく、そこで止まった人も多かったはずです。今はモックアップまでAIが作るので、人間のメインタスクはレビューと意思決定に移りました。シンガポールの空港からエージェント経由でMicrosoft Foundryに接続し、45分座っているだけでデモが仕上がる、という実演も紹介されました。</p>
<p>では、あとは待っていればスケールするのでしょうか。Hollan氏が挙げた今のボトルネックは、モデルではなく、システム全体のエージェントフレームワークとランタイムでした。</p>
<p>比喩として挙げられたのは電気の普及でした。各工場が自前の蒸気機関を持ち、エンジンとラインシャフトで動力を配っていた時代から電気に切り替わったとき、工場は動力の置き方そのものを設計し直しました。AIも同じで、既存の組織にエージェントを足すのではなく、AIを中心とした組織構造を考えることから始めるべきだ、という話でした。</p>
<p>そのうえで、エージェントに求められる条件が4つ挙がりました。</p>
<ul>
<li>どのように動作するかが分かること</li>
<li>何を知っているかが分かること</li>
<li>行動を信頼できること</li>
<li>常に改善されること</li>
</ul>
<p>1つめと2つめは、空港から45分放っておけるデモが成立するための条件です。何をしていて何を知っているのかが見えなければ、離れた場所で任せる判断はできません。Foundryはタスクが中断されても継続でき、問題が発生したときに復旧し、他のエージェントに引き継げるように作られています。残りの2つの説明に、この日いちばん時間が割かれていました。</p>
<img src="/images/2026/20260820a/IMG_2841.jpg" alt="" width="1200" height="900" loading="lazy">

<h3 id="エージェントに身元を持たせる">エージェントに身元を持たせる</h3><p>組織内のデータは、業務データ、ナレッジグラフ、データレイクと分散しています。どこにあるかはユーザにとって重要ではなく、AIエージェントがアクセスできることだけが重要だ、という整理でした。とはいえ、無条件に何でも参照させるわけにはいきません。</p>
<p>Microsoftはエージェント間のIDを保有する方向に進んでいます。エージェントが独自のアイデンティティを持ち、他者の同意に基づいて代理で行動します。その行動はすべて監査でき、管理でき、可視化されているので、エージェントが本来の役割を果たしているかを確認し、時間の経過とともに改善していけます。信頼を成り立たせているのは<strong>アイデンティティ</strong>と<strong>可観測性</strong>の2つだ、という言い方が印象に残りました。</p>
<p>改善の話も具体的でした。企業内でエージェントを実用化したチームは、最初のエージェント作成に2〜3ヶ月を費やし、そこから展開して、理解して、学習を始めます。継続的な改善は求められますが、そのたびにエンジニアの工数を奪われるのでは回りません。Foundryには、そこを自律的に回すための最適化ジョブが用意されています。</p>
<h2 id="ソフトウェア開発の民主化、そして日本のコミュニティの未来（Microsoft-Anthropic）">ソフトウェア開発の民主化、そして日本のコミュニティの未来（Microsoft &amp; Anthropic）</h2><p>登壇は、AnthropicのJunichiro Tsujiさんと、Microsoftの榎並さんです。TsujiさんはSonyのPlayStation、GoogleのStadia、Robloxを経て現職、榎並さんもSony出身という組み合わせです。</p>
<p>プロダクションレベルで生成AIが書いたコードを使っている人が、明らかに増えました。ソフトウェアを作る障壁はほぼ消えた、という認識が両者で一致していました。民主化自体はこれまでも起きていますが、専門家でなくても誰でも作れる、という段階は今回が初めてです。</p>
<p>障壁が消えると、残る仕事は課題をいかに言葉にするかに寄ります。ドメインの深い理解と、どこを作るべきかを決めるセンスや判断が効くようになります。実際、あるハッカソンで上位入賞した5組のうち4組はエンジニアではなかったそうです。</p>
<p>分かりやすい例として、建築家の話が出ました。家を建てるまでの工程よりも、申請を通すまでの時間が長いとのことでした。92%の申請が一度却下されるので、そこに時間が吸われていました。これをClaudeで解消したとき、強かったのは技術に詳しい人ではなく、課題を1番よく知っていた人でした。</p>
<img src="/images/2026/20260820a/IMG_2847.jpg" alt="" width="1200" height="900" loading="lazy">
<img src="/images/2026/20260820a/IMG_2849.jpg" alt="" width="1200" height="900" loading="lazy">

<h3 id="生産性をどう測るか">生産性をどう測るか</h3><p>Foundryの開発では、エンジニア9人が3週間で10万行をコミットしています。意外と自分たちでもできるのではないか、と思うかもしれません。ただ、8時間労働でもコーディングに使えるのは25%程度だと考えると、この数字の見え方が変わってきます。オンボーディングもAIに任せ、3〜4週間かかっていたものを1週間に短縮した話も続きました。</p>
<p>行数が増えても、エンジニアの最終的な仕事は変わりません。動くコードであることを証明することです。だから正しい成果はアウトカムベースで評価すべきで、行数や工数で測る話ではありません。</p>
<p>AIを使っても、人によって生産性は変わります。シニアエンジニアには経験があるので、使いこなしたアウトプットを作れます。ジュニアは経験がないので、出てきたものの判断がつきません。登壇者からは「米国のような資本主義だとシニアだけを育てるから、日本にもいつか勝てる時代が来るのでは」と思っていた、という話も出ました。ただ、向こうも問題点にはさすがに気づき、看護師と同じような育成の仕組みへ変わってきているそうです。</p>
<h3 id="トップダウンとボトムアップの両輪">トップダウンとボトムアップの両輪</h3><p>経営層から「AIを使え」という指示が降りてくるものの、現場はどう使えばいいか分かりません。よくある詰まり方です。有志が集まってハッカソンをやり、使い方を実際に見せると、そこで初めて伝わるようになりました。これがボトムアップで、経営陣も言うだけでなく実際に手を動かして感じるのがトップダウンです。どちらか片方では動かない、という話でした。</p>
<p>ハッカソンのコツも共有されました。</p>
<ul>
<li>アイデアを共有する場を作ること</li>
<li>有志で手を挙げてくれる人を、トークンでブーストすること</li>
</ul>
<p>そのうえで<strong>AI CoE</strong>（AI Center of Excellence）を作り、組織としての成功に導いていきます。ここにはいくつかの段階があり、いきなり最後の段階には行けません。AI人材の育成では、結局ハッカソンやコミュニティが効いてきます。</p>
<p>最後のテーマは、コミュニティはコミュニティが作る、というものでした。Claudeのコミュニティアンバサダーを募集中で、熱量を高く書くと評価してもらえるそうです。イベント運営向けのAPIトークンやグッズのサポートもあり、会場のQRコードから応募できるようになっていました。</p>
<h2 id="DBひとつでRAGからエージェントメモリまで（PingCAP）">DBひとつでRAGからエージェントメモリまで（PingCAP）</h2><p>TiDBはMySQL互換のオープンソース分散型DBです。無停止で水平スケールでき、最初から3AZにまたがって起動します。金融、インターネット、ゲーム業界を中心に採用事例が増えています。</p>
<p>そのTiDBが、TiDB XとしてAIネイティブDBへリアーキテクチャされました。コンセプトは「全てのエージェントにDBを」です。紹介された特徴は次のとおりです。</p>
<ul>
<li>AI利用に適したマイクロサービス構成とサーバレス構成を取り、クエリが来たら起動する従量制課金</li>
<li>リレーショナルなクエリだけでなく、ベクトル検索と全文検索も同じDBで扱える</li>
<li>サインアップ不要で、アクセスした瞬間にインスタンスが作れるTiDB Cloud Zero（データは30日保持）</li>
</ul>
<p>KIMIのウェブサイト自動生成のバックエンドにも使われているそうです。</p>
<p>登壇資料が公開されています。</p>
<h3 id="なぜエージェントにDBが必要なのか">なぜエージェントにDBが必要なのか</h3><p>LLMはステートレスなので、mdファイルやプロンプトから毎回情報を引っ張ってきます。その外部情報源としてDBを置くと、用途は大きく2つに分かれます。RAGと、エージェントメモリです。</p>
<p>RAG側で強調されたのは、データの質でした。参照元のデータが問題を抱えていたら、それが表面に出てくるだけです。データの質こそがAIプロダクトの質を決めるという言い方は、自分の現場でも思い当たるところがありました。</p>
<p>TiDBはDDLの宣言だけで、ベクトル検索と全文検索の両方をサポートします。embeddingも自動で処理され、モデルの指定もできます。どちらか片方では弱点が残るので、両方に問い合わせて結果をマージするハイブリッド検索で精度を上げます。同じDBに両方あることで、データ統合をDB側で済ませられ、コンテキストを無駄に消費しません。PingCAP自身も、docsを食わせたRAGであるTiDB AIでサポートを提供していて、クエリ拡張とGraph RAG（先にグラフ構造を構築し、周辺ノードを取得して返す）で精度を上げているとのことでした。</p>
<h3 id="メモリを起点にスキルとツールを直す">メモリを起点にスキルとツールを直す</h3><p>生成AIがレシピブック、Agentic AIがシェフ、という対比が使われていました。レシピを渡すだけなら記憶は要りませんが、シェフとして働くなら、前回どうだったかを踏まえて1歩踏み込む必要があります。TiDBは、そのためのエージェントメモリとして「mem9」を提供しています。</p>
<p>やっていること自体は素直です。</p>
<ul>
<li>これまでの履歴をDBに記録し、ベクトル検索で関係する部分だけをコンテキストに載せる</li>
<li>記録するのは再利用できるFactのみで、既存の事実と矛盾しないかをリコンサイルする</li>
<li>直近の会話履歴は、そのままコンテキストとして保持する</li>
</ul>
<p>Mem0が複数テーブルで管理し、CrewAIが半減期を実装しているのに対して、実装方法が異なるという説明でした。</p>
<p>メモリだけ改善されればOKではありません。メモリを起点に、スキルやツールのほうを改善していきます。同じエージェントでも、スキルハブを通して違うスキルを利用できるようになっています。コンテキストを共有する機能もあり、チームでやり取りの内容を確認できます。スキルに相当するものを扱う「drive9」もあり、見た目はファイルシステムですが、ファイルを配置した時点でLLMがメタデータを生成し、メタ情報で検索できます。エージェントのデータハーネスとして進化していく、という位置づけでした。</p>
<h2 id="t-wadaさんに聞く-MS-Build-2026で見えたもの">t_wadaさんに聞く MS Build 2026で見えたもの</h2><p>2026年6月にGitHub Copilotが従量課金になりました。「使い放題」が終わった象徴的な出来事として、まずここが挙がりました。MS Build 2026を貫いていたのは、次の式です。</p>
<figure class="highlight text"><table><tr><td class="code"><pre><span class="line">performance = tokens / (Watt + $)</span><br></pre></td></tr></table></figure>

<p>問われるのは、トークンをどれだけ積めるかではなく、電力と金額あたりでどれだけの成果が出るかです。とにかくトークンを積む<strong>トークンマキシング</strong>の時代は、これで終わりました。</p>
<p>Microsoftは自前で7つの新しいモデルを発表しています。コーディング向けのMAI-Code-1-Flashは、Haikuくらいの価格でSonnet未満くらいの性能だという評価でした。最高性能のモデル1つに全部やらせるのではなく、モデルの使い分けが今後のテーマになります。「ほどほど頭が良くてほどほど安い」がハーネスエンジニアリングにおけるスイートスポットになる、という指摘は、この日いちばん腑に落ちた話でした。</p>
<h3 id="Foundryと3つのIQ">Foundryと3つのIQ</h3><p>Knowledge IQ、Data IQ、Work IQという3つのIQは、大体が既にGAになっています。特にWork IQは「ないと仕事にならない」くらい効くという評価でした。※使うにはM365と従量課金が必要な点は注意が要ります。どうエージェントにコンテキストを渡すかという問題に対して、Microsoftは垂直統合していることの強みをそのまま押し出してきています。</p>
<h3 id="実行環境としてのサンドボックス">実行環境としてのサンドボックス</h3><p>自分の関心にいちばん近かったのは、この話でした。Container Apps Sandboxと、Microsoft Execution Containers（MXC）です。OpenClawのようなエージェントをいかにうまく動かすかという文脈で、アイソレーションのレベルを選べてグラデーションを変えられることが重要だと説明されていました。GitHub Copilot CLIのクラウドサンドボックスとローカルサンドボックスは、実際にこれを使っています。SDKとして提供されているので、自分のアプリに組み込むこともできます。エージェント専用のサンドボックス環境として、起動が1秒以内のMicro VMも用意されています。</p>
<p>プロンプトやツール定義ではなく、実行環境のレイヤーでハーネスを用意する。この発想が製品として出てきたことが、今回いちばん大きい変化だと感じました。</p>
<p>OpenClawの作者であるPeter Steinberger氏もKeynoteに登場し、Windowsで動くようになったこと（Microsoft Scout）が発表されました。Work IQやEntra IDと繋がって自動化していく方向です。他のセッションでは、OpenClawの作り方やループエンジニアリングの話も出ていました。日々の開発のペイン自体を無くすためのものを作ろう、という考え方が共通していました。</p>
<h3 id="ソフトウェアの防御力はどこにあるか">ソフトウェアの防御力はどこにあるか</h3><p>「SaaS is dead」の文脈で、Chip Huyen氏のセッションが紹介されました。AI時代になり、ソフトウェアは簡単にコピーできるようになりました。UIやコードは、もう防御力になりません。データが堤防になっていた期間はありましたが、メガベンダーは買収でデータを手に入れられます。顧客のほうも、Claude、Codex、Cursorと簡単に移動していきます。ジブリ風の画風がプロンプト1つで再現できてしまうのと、構造は同じです。</p>
<p>では、どう守るのか。挙がったのは3つでした。</p>
<ul>
<li>競合より早く出すスピード</li>
<li>ドメインへの深い理解やローカライズで効くニッチ</li>
<li>プロダクトにAI親和性を持たせておくこと</li>
</ul>
<p>どれも決定的ではありませんが、AI親和性を持たせておけば生き延びられるかもしれない、という言い方が現実的でした。</p>
<p>Simon Willison氏のプラグマティックなAIエンジニアリングも引用されていました。<strong>我々の仕事はプロダクションで動くコードを作り、それを保証することです。</strong> 民主化のセッションで出た「動くコードであることを証明する」と、同じことを言っています。</p>
<h3 id="AI時代のテスト駆動開発">AI時代のテスト駆動開発</h3><p>「まずRedにしてからテストを書くことに、無駄なプロセスがあるのではないか」。聞き手の長瀬マキさんが、この問いを立てていました。</p>
<p>昔は「テストファーストをやれ」と書いてもLLMは従いませんでした。その代わり、「t-wadaのTDDをやる」と書けば従ったそうです。当時はこの回避策が共有されていました。今はRed-Green TDDをやらせるといい感じになるものの、効率的ではないとも思う、という評価でした。先にテストを書き、後で実装をやらせること自体はマストです。ただし、テストと実装を往復しながら徐々に歩幅を広げることはできます。diffが大きすぎると人間がチェックしきれないので、歩幅を小さくしてauto modeで回すくらいがちょうどいい、という話でした。ちなみにAIも、diffが大きいとサボるそうです。</p>
<p>なお、このセッションで言及されていたMicrosoft Build 2026（2026年6月2日、サンフランシスコ開催）のセッションは、公式のYouTubeプレイリストで視聴できます。全224本あるので、気になるトピックから拾うのが良さそうです。</p>
<h2 id="AWSヒーローがAzureでもAIエージェント構築してみた件（みのるんさん）">AWSヒーローがAzureでもAIエージェント構築してみた件（みのるんさん）</h2><p>AWSヒーローとしてAIエージェントの導入を進めている方ですが、実はMicrosoftメインでやっていた時期もあるとのことでした。所属のKDDIアジャイル開発センターは、KDDI外の人にもアジャイル開発を届けることをやっています。題材はSIぽい発注業務で、これを生成AIでやっていきます。</p>
<p>発注には踏むべきプロセスがあり、プロンプトを投げるだけでは進みません。そこで、計画、実行、観察、分析のループを回すReAct型に載せます。</p>
<h3 id="ノーコードからSDKまで">ノーコードからSDKまで</h3><p>一度ツールを用意してしまえば、あとは自然言語で頼めます。選択肢はレイヤーで並んでいました。</p>
<ul>
<li>TeamsやM365で使うなら、Copilot Studioでノーコード</li>
<li>Azureで作るなら、Foundry Agentsでノーコード</li>
<li>細かく作り込むなら、Agent FrameworkのSDK（Pythonと.NETに対応）</li>
</ul>
<p>そもそもなぜフレームワークを使うのか。共通化する処理は事前に用意してある方が良いからです。決定論的にやる部分はワークフローで書き、非決定論的に調べるところをエージェントに任せます。LangGraphのようにワークフローを作成すれば、HITL（Human-in-the-loop）も差し込めます。</p>
<h3 id="デプロイ先の3択と運用">デプロイ先の3択と運用</h3><p>MS系のデプロイ先は3つ挙がりました。</p>
<ul>
<li>軽めの処理向けのAzure Functions</li>
<li>コンテナ対応はしやすいものの、認証系が複雑なAzure Container Apps</li>
<li>色々揃っていて1番やりやすいFoundry Hosted Agents</li>
</ul>
<p>運用で必要になるのは、次の5つです。</p>
<ul>
<li>会話の記録</li>
<li>権限制御</li>
<li>ファイルの扱い</li>
<li>スケーリング</li>
<li>トレーシング</li>
</ul>
<p>Hosted Agentsならこのあたりをまとめて管理できるとのことで、登壇中に実際にデプロイしていました。渡すのはソースのZIPだけです。呼び出し方はチャット形式のResponsesと、invocations方式の2種類です。設定を変えると新しいVersionとして残り、メタデータで切り替えられます。</p>
<h3 id="他クラウドと比べてどう選ぶか">他クラウドと比べてどう選ぶか</h3><p>他のクラウドでも同じようなことはできる、という前提が最初に置かれました。そのうえで挙がった選び方の観点は、次の3つです。</p>
<ul>
<li>呼び出し元のプロトコルに応じて変える</li>
<li>実装言語で何を使いたいか（TypeScriptなど）</li>
<li>ID系はEntra統合が楽で、Teamsから呼び出すアプリもAzureが楽</li>
</ul>
<p>ハーネスでやるようなことをワークフローで実現できますが、AWSとAzureのハーネスはレイヤーから中身まで違うので、そのまま移植する話にはなりません。結果として、使われるところに置けるかどうかで決まっていました。</p>
<h2 id="さいごに">さいごに</h2><p>新しい語彙を覚え続ければ追いつけると思っていましたが、一日聞いて残ったのは語彙ではなく、置き場所の話でした。</p>
<p>持ち帰った学びは、次の4つです。</p>
<ul>
<li>ハーネスは、プロンプトの書き方ではなく実行環境のレベルで用意する</li>
<li>エージェントの行動履歴はトレースし、監査対応に使うだけでなく改善のインプットにする</li>
<li>組織に定着させるには段階的な盛り上げとAI CoEが要り、いきなり最後の段階には行けない</li>
<li>何より、実際の課題があるところに飛び込む</li>
</ul>
<p>建築家が申請の却下率を知っていたから解決できたのと同じで、課題を1番よく知っている人が強い時代になりました。</p>
<p>トークンマキシングの時代は終わりました。問われるのは、積んだトークンの量ではなく、それが生んだビジネス価値のほうです。価値がトークン使用料を上回っているかを見て、最適なモデルを選ぶ作業も必要になります。</p>
<p>デベロッパーが実際に価値を届けるところまでいかなければAIブームが終わってしまう、というオープニングの危機感は、結局ここに帰ってきます。1人でコードを書く速度は、もう十分に上がりました。それでもビジネス価値まで届かせるには、うまくいった置き方を持ち寄るしかありません。「Fast Alone. Far Together.」が一日を通してコンセプトとして機能していたのは、そういう意味でした。</p>
<p>そのうえで、次に決めなければならないのはKPIです。どのKPIなら、エージェントが出した価値を測れるのか。ここは持ち帰りの宿題になりました。</p>
]]></content>
    <summary type="html">2026年7月24日に開催されたAI Dev Day 2026に参加しました。一日を通して繰り返し出てきたのは、新しい語彙ではなくコストと信頼でした。キーノートから各セッションまで、聞いてきた内容をレポートします。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="AIエージェント" scheme="https://future-architect.github.io/tags/AI%E3%82%A8%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%B3%E3%83%88/"/>
    <category term="Azure" scheme="https://future-architect.github.io/tags/Azure/"/>
    <category term="RAG" scheme="https://future-architect.github.io/tags/RAG/"/>
    <category term="TDD" scheme="https://future-architect.github.io/tags/TDD/"/>
    <category term="参加レポート" scheme="https://future-architect.github.io/tags/%E5%8F%82%E5%8A%A0%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88/"/>
  </entry>
  <entry>
    <title>AIエージェント開発でつまずく原因は技術ではなく体制であることも多い</title>
    <link href="https://future-architect.github.io/articles/20260818a/"/>
    <id>https://future-architect.github.io/articles/20260818a/</id>
    <published>2026-08-17T15:00:00.000Z</published>
    <updated>2026-08-17T15:00:00.000Z</updated>
    <author><name>佐藤一馬</name></author>
    <content type="html"><![CDATA[<h2 id="はじめに">はじめに</h2><p>AIエージェントの検証（PoC）を一度は動かしてみた、という企業は一気に増えました。ところが「全社に広げよう」「本番で使おう」となった途端に動きが悪くなります。</p>
<p>筆者が現場で見ている限り、その原因の多くはモデルやフレームワークの選定ではなく、だれが何を担うのかという体制の構築側にあります。</p>
<p>この記事では、AIエージェント開発を組織で扱うときの前提を押さえたうえで、体制を次の3点で整理します。</p>
<ul>
<li>役割分担</li>
<li>評価・監視・ガバナンスの仕組み</li>
<li>立ち上げ方</li>
</ul>
<h2 id="前提として押さえること">前提として押さえること</h2><p>まずAIエージェント開発は、従来型のシステム開発と異なる部分があります。</p>
<ul>
<li><strong>テストによる品質保証が効かない</strong><br>LLMの出力は確率的で、同じ入力でも結果が揺れます。期待値と突き合わせる従来のテストは通ったり落ちたりするので、単体の合否ではなく、まとまった件数での成功率で品質を見ることになります。</li>
<li><strong>評価が作って終わりにならない</strong><br>従来のテストは仕様が決まれば書けて、通ればそこで終わります。エージェントの評価は「何を正解とするか」を業務側が決めるところから始まり、モデルやプロンプトを差し替えるたびに測り直します。開発中から運用中まで回り続ける、継続的な業務になります。</li>
<li><strong>ガバナンスとスピードのジレンマ</strong><br>早く試したい事業部門と、リスクを抑えたい統制側がぶつかります。どちらかを我慢させる形にすると長続きしません。</li>
</ul>
<p>いずれもツールの選定では解けず、体制の設計で吸収する領域だと考えています。</p>
<p>この問題を解決するためには、AIエージェント開発を単なる自動化ではなく <strong>AI-BPR（AIを前提とした業務プロセスの再設計）</strong> として捉え直すことが必要です。既存業務をそのままエージェントに載せ替えるのではなく、プロセス自体を見直してからエージェント化するということです。少し大きな話に感じるかもしれませんが、業務効果を得ようとするのであれば、必ずこういったマインドチェンジが求められます。</p>
<h2 id="役割分担">役割分担</h2><p>体制の主軸を4つの役割で捉え、そのうち推進機能を誰に置くかを決めます。</p>
<h3 id="開発を支える4つの役割">開発を支える4つの役割</h3><img fetchpriority="high" class="bordered" src="/images/2026/20260818a/roles.png" alt="" width="1200" height="628">

<div class="scroll"><table>
<thead>
<tr>
<th>#</th>
<th>担い手</th>
<th>役割</th>
<th>ポイント</th>
</tr>
</thead>
<tbody><tr>
<td>①</td>
<td>プラットフォーム統制チーム</td>
<td>共通基盤の提供と運用</td>
<td>「ゲートキーパー化」を避け、承認待ちなしで使えるセルフサービス化を目指す</td>
</tr>
<tr>
<td>②</td>
<td>事業部門</td>
<td>対象業務の選定と出力の評価</td>
<td>依頼側ではなく導入を主導する当事者。評価者としての継続関与を「正式な工数」に</td>
</tr>
<tr>
<td>③</td>
<td>開発エンジニア</td>
<td>エージェントの設計・実装と評価の作り込み</td>
<td>市場で最も希少。少数のLLM設計リード＋優秀な既存エンジニアの再教育</td>
</tr>
<tr>
<td>④</td>
<td>推進機能</td>
<td>業務と技術の翻訳と優先順位付け</td>
<td>人ではなく機能として設計する（後述）</td>
</tr>
</tbody></table></div>
<p>読み違えられやすいのは②の関与量です。業務知識を持つ人が評価基準づくりと出力チェックに継続的に関わらないと、精度は上がりません。1人あたり週4〜8時間といった目安を持った正式な工数として置くことが、地味ながら精度にいちばん寄与します。</p>
<h3 id="推進役は誰がやるのか">推進役は誰がやるのか</h3><p>体制を語ると必ずこの問いが出ます。ここで業務と技術の両方に精通したスーパーマンを1人探し始めると、たいてい行き詰まります。そんな人はそうそういませんし、いても優先順位付けや意思決定まで担えず、会議の調整役に留まりがちだからです。</p>
<p>人を探すのをやめて、次の機能をそれぞれ誰が担うかを決めます。</p>
<ul>
<li><strong>決める</strong>: 何をやるか、やめるかの判断と投資の意思決定。担い手は必ず②事業部門であるべきです</li>
<li><strong>さばく</strong>: 部門横断の要望をトリアージし、優先順位を付ける。担い手は推進役、または戦略・DX部門</li>
<li><strong>つなぐ</strong>: 部門間の調整と、業務と技術の相互翻訳。担い手は推進役、または③開発エンジニア</li>
</ul>
<p>「決める」の担い手だけは動かせません。また、残りのさばく・つなぐについては組織の状況に応じて柔軟に配置します。</p>
<p>この分解をしないまま進めると、会議は開かれるのに優先順位の判断が起きず、意思決定が毎回先送りになります。</p>
<h3 id="さばく・つなぐの配置パターン">さばく・つなぐの配置パターン</h3><p>さばく・つなぐを誰に置くかは優劣ではなく、組織の状況で最適解が変わります。無理に一本化せず、状況に合うものを選ぶのが現実的です。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>パターン</th>
<th>向いている状況</th>
<th>長所</th>
<th>留意点</th>
</tr>
</thead>
<tbody><tr>
<td>A. 推進役が旗振り</td>
<td>DX部門・AI推進室があり、知見と熱意ある人材を配置できる</td>
<td>横展開に最も強く、ナレッジが組織に蓄積</td>
<td>人材要件が最も高い。形だけ置くと調整役に留まる</td>
</tr>
<tr>
<td>B. 事業部門が旗振り（推進役は調整に専念）</td>
<td>対象業務に意思決定できる熱意ある人材がいる</td>
<td>立ち上がりが最も速く、当事者意識が高い</td>
<td>横展開時に推進主体を作り直す必要</td>
</tr>
<tr>
<td>C. ③開発エンジニアが推進機能を担い伴走</td>
<td>社内に推進人材がおらず、まず1件成功させたい</td>
<td>人材育成を待たずに着手できる</td>
<td>外部に依存する場合は、ナレッジが社内に残るよう引き継ぎを設計に織り込む</td>
</tr>
</tbody></table></div>
<p>どのパターンでも、旗振り役は1人に決めて明示します。</p>
<h2 id="評価・監視・ガバナンスの仕組み">評価・監視・ガバナンスの仕組み</h2><p>次の3つは特定の役割の持ち物ではなく、4つの役割が共通で使います。</p>
<p>事前にまとめておくことで、経営層や情報システム部門から飛んでくる「機密は？」「監査は？」「コストは？」に先回りで答えられ、信頼関係を構築しやすくなります。</p>
<ul>
<li><strong>評価</strong>: 出力品質を継続測定するEval基盤／モデル・プロンプトのバージョン管理</li>
<li><strong>監視</strong>: トレーシングで失敗箇所を追跡／監査ログ／APIコストの可視化と上限</li>
<li><strong>ガバナンス</strong>: 機密情報の取り扱い（権限・データ分類・マスキング）／プロンプトインジェクション対策（入力チェック・ツール実行の制限・人による承認）</li>
</ul>
<h2 id="立ち上げ方">立ち上げ方</h2><p>役割と仕組みを、最初から全部そろえる必要はありません。</p>
<p>小さく始めて実績を土台に、段階的に組織を組み上げるほうが結果的に速く進むことが経験上、多いです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>STEP</th>
<th>期間の目安</th>
<th>体制</th>
<th>やること</th>
</tr>
</thead>
<tbody><tr>
<td>STEP0 構想・棚卸し期</td>
<td>約1〜2ヶ月</td>
<td>事業部門＋推進機能（少人数）</td>
<td>業務の棚卸しとユースケース候補の洗い出し。旗振り役を決める</td>
</tr>
<tr>
<td>STEP1 PoC期</td>
<td>約3ヶ月</td>
<td>推進機能＋開発エンジニア2〜3名</td>
<td>1〜2ユースケースをやり切る（事業部門は評価者）。最小環境で十分</td>
</tr>
<tr>
<td>STEP2 横展開期</td>
<td>約6〜12ヶ月</td>
<td>＋プラットフォームチーム</td>
<td>複数部門へ拡大し、共通部分を基盤化</td>
</tr>
<tr>
<td>STEP3 標準化・内製化期</td>
<td>以降、継続</td>
<td>全社・内製体制</td>
<td>セルフサービス基盤・評価の自動化・ガバナンス整備</td>
</tr>
</tbody></table></div>
<p>期間はいずれも目安です。具体的な課題や施策案が既にあるなら、STEP0を飛ばしてSTEP1から着手して構いません。各STEPの終わりに効果（ROI）を測り、次の投資判断につなげます。</p>
<p>STEP1を始める場合は、以下から決めましょう。</p>
<ol>
<li><strong>旗振り役</strong>: 誰が決めるのかを1人に定める（配置は3パターンから）</li>
<li><strong>最初のユースケース</strong>: 価値と実現可能性で1〜2件に絞る</li>
<li><strong>STEP1の体制と工数</strong>: 期間の目安は約3ヶ月、評価者の工数は1人あたり週4〜8時間</li>
</ol>
<p>あわせて効果（ROI）の測定基準を最初に決めておくと、STEP1の終了時に振り返れます。</p>
<h2 id="まとめ">まとめ</h2><p>AIエージェント開発は、良いソフトウェアエンジニアリングの延長線上にあります。特別な組織を新しく作る話ではありません。最初から完璧な体制を描こうとすると、動き出す前に息切れしてしまいます。無理せずスモールスタートを心がけましょう。</p>
<p>AIエージェント導入の推進について、一助になれば幸いです。</p>
]]></content>
    <summary type="html">AIエージェントの検証（PoC）は動いたのに、全社展開や本番利用となった途端に動きが悪くなる。その原因の多くは技術選定ではなく体制側にあります。役割分担、評価・監視・ガバナンスの仕組み、段階的な立ち上げの3点で整理します。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="AIエージェント" scheme="https://future-architect.github.io/tags/AI%E3%82%A8%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%B3%E3%83%88/"/>
    <category term="組織論" scheme="https://future-architect.github.io/tags/%E7%B5%84%E7%B9%94%E8%AB%96/"/>
  </entry>
  <entry>
    <title>AIとの対話のトークン数を減らす：UIデザイン編</title>
    <link href="https://future-architect.github.io/articles/20260707a/"/>
    <id>https://future-architect.github.io/articles/20260707a/</id>
    <published>2026-07-06T15:00:00.000Z</published>
    <updated>2026-07-06T15:00:00.000Z</updated>
    <author><name>澁川喜規</name></author>
    <content type="html"><![CDATA[<p>いろいろなソフトウェアをAIに作らせてみてなかなか思い通りにいかずに苦労するのがUIデザインです。見た目がかっこいいとかではなくて、この画面は何をする画面で、どういう構成要素があるのか、という伝達がうまくいかない。2ペインで左にはツリービューがあって・・・みたいにテキストで伝えても、いまいち伝わっているのかどうか。何度も往復して時間もトークンも浪費して、ということがよくありました。</p>
<p>ラフなUIデザインをうまくAIに伝える方法はなにかあるのか、いろいろ調べたりしましたがいまいち、自分のニーズに合うものが見当たりませんでした。</p>
<ul>
<li>Figma。一時期すごい流行ったが、どちらかというと、ここで作ったものがそのままシステム化できるという、デジタルコンテンツ作成ツール的な使われ方だったと思うのでラフ用にはオーバースペック</li>
<li>Salt。フォーム要素とかの細かい定義には良さそうだが、ペインを分けて右にフォーム、みたいな大まかなラフにはちょっと向かない。というかこれならHTMLの方がみんな慣れてそう</li>
<li>draw.io。UI部品集がある。が、絵でしかなく「ここは右寄せで」みたいな意図が入れづらい。</li>
<li>PowerPointとかExcel。単なる絵でしかないし、トークン消費が多くなりがち。</li>
<li>Qt CreatorやXAMLなどのフォームデータをそのまま使う: ツールが重すぎるのと、そのまま動かす前提なので細かい定義ができすぎてしまう。意図があってもものかデフォルトなのかの区別がしづらい。</li>
<li>HTML。デフォルトだとUI部品としては弱いが、ツールキット使うとリアルになりすぎたり加減が難しい。</li>
</ul>
<p>そもそもAI以前は、既存のPowerPointに書いちゃう or 自動コード生成のためにFigma、みたい感じでラフスケッチを作る独立したツールのニーズがそもそもあまりなかったのではないか、と思ったりもしました。</p>
<p>人間との対話でも、Figmaで「リアルっぽい絵」を作ってしまうと、なんとなく良いものに見えてしまって議論が返って進まず、実際にできてから「こうじゃなかった」となりがちというのも聞きます。そういうところもカバーしつつ、AIとの対話のトークン数を減らすためのツールを作ってみました。こんな感じのラフが出るツールです。あえてリアルっぽく見せないデザイン。</p>
<ul>
<li>https://github.com/shibukawa/uisketch</li>
</ul>
<img fetchpriority="high" src="/images/2026/20260707a/スクリーンショット_2026-06-26_18.17.29.png" alt="スクリーンショット_2026-06-26_18.17.29.png" width="965" height="643">

<h2 id="UI定義のトークンを減らす">UI定義のトークンを減らす</h2><p>UIのデザインを伝える上で大事なのは親子関係を維持しつつ、どんな要素があるか、大まかなレイアウトがどうなっているのかを伝えることかな、と思います。逆に詳細な情報は相手がiOSのツールきっとなのか、ウェブなのか、いろんな文脈で変わってきます。そこの現実との橋渡しはAIが得意なところなので、がんばりすぎないようにしています。promptという属性でAIエージェント向けの情報も書けるようにしています。それらの意図や使えるコンポーネントの種類とかをAIに教えるためのskillsも作ってあります。</p>
<figure class="highlight yaml"><input type="checkbox" id="code-wrap-1f9vv2d-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1f9vv2d-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="string">```uisketch</span></span><br><span class="line"><span class="attr">browser:</span></span><br><span class="line">  <span class="attr">title:</span> <span class="string">UI</span> <span class="string">Sketch</span> <span class="string">Editor</span></span><br><span class="line">  <span class="attr">address:</span> <span class="string">https://shibukawa.github.io/uisketch</span></span><br><span class="line">  <span class="attr">children:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">vstack:</span></span><br><span class="line">      <span class="attr">children:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">hstack:</span></span><br><span class="line">          <span class="attr">children:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">label:</span> <span class="string">UI</span> <span class="string">Sketch</span> <span class="string">Editor</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">spacer</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">button:</span> <span class="string">Share</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">button:</span> <span class="string">Export</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">tabs:</span></span><br><span class="line">          <span class="attr">labels:</span></span><br><span class="line">            <span class="bullet">-</span> [<span class="string">&quot;Visual Editor&quot;</span>]</span><br><span class="line">            <span class="bullet">-</span> <span class="string">Source</span></span><br><span class="line">          <span class="attr">children:</span></span><br><span class="line">            <span class="attr">hstack:</span></span><br><span class="line">              <span class="attr">widths:</span> [<span class="number">25</span>, <span class="number">60</span>, <span class="number">15</span>]</span><br><span class="line">              <span class="attr">children:</span></span><br><span class="line">                <span class="bullet">-</span> <span class="attr">section:</span></span><br><span class="line">                    <span class="attr">label:</span> <span class="string">Components</span></span><br><span class="line">                    <span class="attr">children:</span></span><br><span class="line">                    <span class="bullet">-</span> <span class="attr">grid:</span></span><br><span class="line">                        <span class="attr">children:</span></span><br><span class="line">                          <span class="bullet">-</span> <span class="string">image</span></span><br><span class="line">                          <span class="bullet">-</span> <span class="string">image</span></span><br><span class="line">                          <span class="bullet">-</span> <span class="string">image</span></span><br><span class="line">                          <span class="bullet">-</span> <span class="string">image</span></span><br><span class="line">                          <span class="bullet">-</span> <span class="string">image</span></span><br><span class="line">                          <span class="bullet">-</span> <span class="string">image</span></span><br><span class="line">                          <span class="bullet">-</span> <span class="string">image</span></span><br><span class="line">                          <span class="bullet">-</span> <span class="string">image</span></span><br><span class="line">                <span class="bullet">-</span> <span class="attr">tabs:</span></span><br><span class="line">                    <span class="attr">labels:</span></span><br><span class="line">                      <span class="bullet">-</span> [<span class="string">&quot;Edit&quot;</span>]</span><br><span class="line">                      <span class="bullet">-</span> <span class="string">&quot;Preview(SVG)&quot;</span></span><br><span class="line">                      <span class="bullet">-</span> <span class="string">&quot;Preview(ASCII)&quot;</span></span><br><span class="line">                    <span class="attr">children:</span></span><br><span class="line">                      <span class="attr">image:</span> <span class="string">Preview</span> <span class="string">Image</span></span><br><span class="line">                <span class="bullet">-</span> <span class="attr">vstack:</span></span><br><span class="line">                    <span class="attr">heights:</span> [<span class="number">50</span>, <span class="number">50</span>]</span><br><span class="line">                    <span class="attr">children:</span></span><br><span class="line">                      <span class="bullet">-</span> <span class="attr">section:</span></span><br><span class="line">                          <span class="attr">label:</span> <span class="string">Properties</span></span><br><span class="line">                          <span class="attr">children:</span></span><br><span class="line">                            <span class="bullet">-</span> <span class="attr">table:</span></span><br><span class="line">                                <span class="attr">columns:</span> [<span class="string">Key</span>, <span class="string">Value</span>]</span><br><span class="line">                      <span class="bullet">-</span> <span class="attr">section:</span></span><br><span class="line">                          <span class="attr">label:</span> <span class="string">Inspector</span></span><br><span class="line">                          <span class="attr">children:</span></span><br><span class="line">                            <span class="bullet">-</span> <span class="string">tree</span></span><br><span class="line"><span class="string">```</span></span><br></pre></td></tr></table></figure>

<p>ちょっとウェブのチャットに渡したい場合などのため、あるいはうまくプレビューできないエディタのためにプレーンテキストのアスキーアートでも出力できるようにしています。これをコピペして意図を伝えることもできます。</p>
<figure class="highlight text"><input type="checkbox" id="code-wrap-1f9vv2d-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1f9vv2d-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">┌──────────────────────────────────────────────────────────────────────────────┐</span><br><span class="line">│ UI Sketch Editor                                                        ○ ○ ○│</span><br><span class="line">│ ← →   [ https://shibukawa.github.io/uisketch ]                               │</span><br><span class="line">├──────────────────────────────────────────────────────────────────────────────┤</span><br><span class="line">│                                                          ┌───────┐ ┌────────┐│</span><br><span class="line">│UI Sketch Editor                                          │ Share │ │ Export ││</span><br><span class="line">│                                                          └───────┘ └────────┘│</span><br><span class="line">│┌─────────────────┐┌────────┐                                                 │</span><br><span class="line">││ *Visual Editor* ││ Source │                                                 │</span><br><span class="line">││                 └┴────────┴────────────────────────────────────────────────┐│</span><br><span class="line">││┌─ Components ───┐ ┌────────┐┌────────────┐┌────────────┐       ┌─ Properties</span><br><span class="line">│││┌──────┐┌──────┐│ │ *Edit* ││Preview(SVG)││Preview(ASCI│       │┌────────┐│││</span><br><span class="line">││││ ╲  ╱ ││ ╲  ╱ ││ │        └┴────────────┴┴────────────┴─────┐ ││ Key │ Value</span><br><span class="line">││││image ││image ││ │┌────────────────────────────────────────┐│ │├────────┤│││</span><br><span class="line">││││ ╱  ╲ ││ ╱  ╲ ││ ││ ╲                                    ╱ ││ ││        ││││</span><br><span class="line">│││└──────┘└──────┘│ ││                                        ││ ││        ││││</span><br><span class="line">│││┌──────┐┌──────┐│ ││                                        ││ ││        ││││</span><br><span class="line">││││ ╲  ╱ ││ ╲  ╱ ││ ││                                        ││ ││        ││││</span><br><span class="line">││││image ││image ││ ││                                        ││ ││        ││││</span><br><span class="line">││││ ╱  ╲ ││ ╱  ╲ ││ ││                                        ││ │└────────┘│││</span><br><span class="line">│││└──────┘└──────┘│ ││                                        ││ └──────────┘││</span><br><span class="line">│││┌──────┐┌──────┐│ ││                                        ││ ┌─ Inspector │</span><br><span class="line">││││ ╲  ╱ ││ ╲  ╱ ││ ││             Preview Image              ││ │┌────────┐│││</span><br><span class="line">││││image ││image ││ ││                                        ││ ││ Tree: tree│</span><br><span class="line">││││ ╱  ╲ ││ ╱  ╲ ││ ││                                        ││ ││ ·      ││││</span><br><span class="line">│││└──────┘└──────┘│ ││                                        ││ ││ ·      ││││</span><br><span class="line">│││┌──────┐┌──────┐│ ││                                        ││ ││ ·      ││││</span><br><span class="line">││││ ╲  ╱ ││ ╲  ╱ ││ ││                                        ││ ││ ·      ││││</span><br><span class="line">││││image ││image ││ ││                                        ││ ││ ·      ││││</span><br><span class="line">││││ ╱  ╲ ││ ╱  ╲ ││ ││ ╱                                    ╲ ││ ││ ·      ││││</span><br><span class="line">│││└──────┘└──────┘│ │└────────────────────────────────────────┘│ │└────────┘│││</span><br><span class="line">││└────────────────┘ └──────────────────────────────────────────┘ └──────────┘││</span><br><span class="line">│└────────────────────────────────────────────────────────────────────────────┘│</span><br><span class="line">└──────────────────────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure>

<p>ChatGPTに聞いたところ、アスキー形式も悪くないが、オリジナルのYAMLコードが一番トークンを節約できるだろう、とのことでした。SVGは基本人間向けです。</p>
<p>実際、これをもとにウェブエディタを作ってもらいましたが、以前とは違って意図を大外しすることはなくなりました。余計なタブを追加してきたりとか要件にない機能を追加してきたりはしましたが、現物がある状態でそういう指摘を加えていくのは比較的容易です。</p>
<h2 id="変換ツール">変換ツール</h2><p>Go製のuisketchというコマンドラインツールを提供しています。renderサブコマンドで、markdownファイルの中にある、<code>uisketch</code>というフォーマット(実態はyamlだが)のコードフェンスを見つけてSVGやASCIIアートが生成できます。</p>
<p>また、markdownサブコマンドで、markdown自体を書き換えて、レンダリングした結果の絵が表示されるようにします。ただし、コメントで元のソースも残して、再実行などもできるようにしています。これでGitHubにpushしても人間にも優しいと言えます。</p>
<h2 id="ウェブエディタ">ウェブエディタ</h2><p>いくらskillsがあっても、結局人間が言葉で伝えて作らせると、苦労するタイミングが先に移動するだけで苦労の総量は変わりません。ビジュアルなエディタが欲しくなるはずです。GitHub Pagesで触れるようにしています。ドラッグアンドドロップで部品を並べると、ソースができて、プレビューが見られてSVGやascii artもダウンロードできます。ローカルでも動かせますし、Wails(Go用のTauriみたいな)のデスクトップアプリも作ってます。</p>
<ul>
<li>https://shibukawa.github.io/uisketch/</li>
</ul>
<img src="/images/2026/20260707a/スクリーンショット_2026-06-26_19.53.09.png" alt="スクリーンショット_2026-06-26_19.53.09.png" width="1200" height="795" loading="lazy">

<h2 id="まとめ">まとめ</h2><p>生成AIのコーディングエージェント周りは日々いろんなニュースが飛び交っています。もっと賢くなって全自動だ！という進歩を前提とした流れがありつつも、やはり、将来的には固定料金モデルがなくなっていくのではないか、というのをうっすら思っている人が多そうです。どんな未来が来るにせよ、ポイントとなるのはいかに効率よく、少ないトークン数でAIに意図を伝えるか、というところがポイントになっていくんだろうな、と思っているところです。</p>
<p>AIにラフデザインを提供する、というのに特化したツールを作ってみて、実際にそれ用のウェブエディタの実装のためにドッグフーディングをしてみて、意図通りのものができました。まだskillsが完成前だったので実際のワークフローはこれから試そうと思っています。あとはVSCodeのmarkdownプレビュー拡張とかも作りたいですね。</p>
<p>AIのおかげで「きちんと定義されたデータ構造から、苦労を重ねたテンプレート定義を使ってコード生成」というやり方から「意図だけ伝えて、あとはAIの持つ知識を活用して良い感じにコード生成」とやり方が大きく変わってきているのを感じます。そのムーブメントに乗るべく今回は作ってみています。ラフデザインに特化しているため、似たような画面で、テーブルのカラム定義が違うものを量産、みたいな用途は想定していません。おそらくそういうユースケースはプロジェクトごとに定義ツールとかジェネレータが作られるようになるでしょう。こんな感じで、ツールに求められる特性が大きく変わってきているので新しいツールを作るチャンスがいっぱい転がっていますね。</p>
<p>Markdownがカバーしきれなくて、Excelが登場しがちな領域をカバーするツールは今後もちょくちょく作っていきたいですね。</p>
]]></content>
    <summary type="html">いろいろなソフトウェアをAIに作らせてみてなかなか思い通りにいかずに苦労するのがUIデザインです。見た目がかっこいいとかではなくて、この画面は何をする画面で、どういう構成要素があるのか、という伝達がうまくいかない。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="LLM" scheme="https://future-architect.github.io/tags/LLM/"/>
    <category term="UI/UX" scheme="https://future-architect.github.io/tags/UI-UX/"/>
  </entry>
  <entry>
    <title>macOSでプリインストールされるLLMサービスをコーディングエージェントから使う実験</title>
    <link href="https://future-architect.github.io/articles/20260624a/"/>
    <id>https://future-architect.github.io/articles/20260624a/</id>
    <published>2026-06-23T15:00:00.000Z</published>
    <updated>2026-06-23T15:00:00.000Z</updated>
    <author><name>澁川喜規</name></author>
    <content type="html"><![CDATA[<p>みなさん、WWDC26の情報はみられましたか？WWDCを新製品発表会のように思われている方には肩透かしだったようですが、技術的に今回はかなり期待できる発表がいくつもありました。昨年発表されたDocker互換のコンテナランタイムがパワーアップし、WSLのようなLinuxコンソール体験を提供するcontainer machineとか楽しそうですよね。</p>
<p>それとも1つ。生成AIではとうとう、より気軽に使えるように拡張されました。WWDC25でも、Foundation Models Frameworkが提供されApple IntelligenceをSwiftを通じて利用できるようになりましたが、今回追加された<code>fm</code>コマンドが入り、OllamaとかLMStudioとかインストールしなくても組み込み機能で同等のことができるようになります。リーズニング、ツール呼び出し、構造化出力に対応しているため、高度なシステムに組み込むことができそうです。</p>
<p>たとえば、<code>$ fm chat</code>で起動すると以下のような画面が出てきます。</p>
<img fetchpriority="high" src="/images/2026/20260624a/スクリーンショット_2026-06-13_20.51.48.png" alt="" width="635" height="267">

<p><code>$ fm serve</code>ではOpenAI互換のAPIサーバー</p>
<img src="/images/2026/20260624a/スクリーンショット_2026-06-13_20.54.21.png" alt="" width="596" height="191" loading="lazy">

<p>が起動します。なお、モデルは2種類あります。</p>
<ul>
<li><code>system</code>: デバイス上で動くモデル</li>
<li><code>ppc</code>: プライベートクラウドで動くモデル。利用上限がある。</li>
</ul>
<p>Apple製の第三世代モデルが使われています。<code>system</code>は、AFM 3 Core Advanceですね。20Bの性能を持つが、メモリ上は1B-4B程度の容量しか食わないとされています。<code>ppc</code>はAFM 3 Cloudのモデル群ですね。サーバー側でNVIDIAのボードで動いているとなっています。プライバシーとかセキュリティはばっちり、とのこと。</p>
<p>どう設定するかはわかりませんが、これら以外にClaudeやGeminiのモデルを追加したり、Hugging Faceに上がっているようなモデルを使ったりもできるようです。</p>
<p>とはいえ、少ないメモリで動くローカルモデルというのが一番気になります。ネット使わずに生成AIで開発は果たしてできるのか？実験してみました。</p>
<h2 id="準備-macOS-27-Golden-Gate-OpenCode">準備(macOS 27 Golden Gate, OpenCode)</h2><p>現時点ではmacOS 27 Golden Gateはリリースされていません。そのため、まずは<code>fm</code>コマンドを使えるようにするために、macOSのベータ機能を有効にします。そうするとmacOS 27 Golden Gateの開発者プレビューがインストールできるようになります。会社のパソコンでやる場合は情シスと相談してくださいね。個人マシンでやりました。</p>
<img src="/images/2026/20260624a/スクリーンショット_2026-06-13_21.11.16.png" alt="スクリーンショット_2026-06-13_21.11.16.png" width="487" height="326" loading="lazy">

<p>次にコーディングエージェントをインストールします。今回はOpenCodeを入れました。インストールの方法はいろいろあるので、公式サイトを参照してください。brewで入れるとXCodeも27 Betaを入れる必要があります。</p>
<p>ローカルを参照させるために、<code>~/.config/opencode/opencode.json</code>を作成し、モデルを設定します。これで<code>/models</code>にAppleのモデルが出てくるようになります。</p>
<figure class="highlight json"><figcaption><span>~/.config/opencode/opencode.json</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;$schema&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://opencode.ai/config.json&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;provider&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;apple&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;npm&quot;</span><span class="punctuation">:</span> <span class="string">&quot;@ai-sdk/openai-compatible&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Apple Intelligence&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;options&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;baseURL&quot;</span><span class="punctuation">:</span> <span class="string">&quot;http://localhost:1976/v1&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;apiKey&quot;</span><span class="punctuation">:</span> <span class="string">&quot;dummy&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;models&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;system&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Apple Local&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;pcc&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Apple PCC&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<p>これで実行すれば完璧！？と思ったらエラーが・・・</p>
<h2 id="リクエストを正規化する">リクエストを正規化する</h2><p>Foundation Modelはモデル呼び出しなどに対応すると書かれていたのですが、実行してみるとエラーレスポンスがログに出ました。小さいリクエストを作ってちょっとしたツール呼び出しを<br>送信するとエラーが返ってきました。</p>
<figure class="highlight json"><input type="checkbox" id="code-wrap-szr3xw-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-szr3xw-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span><span class="attr">&quot;error&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span><span class="string">&quot;invalid_request_error&quot;</span><span class="punctuation">,</span><span class="attr">&quot;code&quot;</span><span class="punctuation">:</span><span class="string">&quot;400&quot;</span><span class="punctuation">,</span><span class="attr">&quot;message&quot;</span><span class="punctuation">:</span><span class="string">&quot;Invalid tool definition: The data couldn’t be read because it is missing.&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<p>どうも、リクエスト時のJSONの形式のチェックが厳格に行われるようで、試行錯誤の結果<code>required</code>属性が欠けているとエラーになるようです。そのまま実行するのはできなそうなので、JSONリクエストを変換するプロキシサーバーを途中に挟むことにします。まあ、まだベータなので、きっとリリース版までには不要になるでしょう。</p>
<img src="/images/2026/20260624a/スクリーンショット_2026-06-13_21.44.40.png" alt="スクリーンショット_2026-06-13_21.44.40.png" width="607" height="248" loading="lazy">

<p>OpenCodeが返す複雑なリクエストではエラーになりました。これには11個のツール定義が含まれます。1個ずつばらし、OpenAIのOpenAPI定義(紛らわしい)と見比べてリクエストが通るまで修正して仕様化して、という感じのプランをCodexに渡して検証させたところ、以下のような修正が必要ということがわかりました。</p>
<ul>
<li>オブジェクトスキーマに properties があるのに required がない場合、すべてのプロパティ名を required に自動追加する。</li>
<li>オブジェクトスキーマに required があるものの、一部のプロパティが含まれていない場合、すべてのプロパティ名を追加して required を補完する。</li>
<li>fm serve が受け付けないため、”type”: [“string”, “null”] のような nullable union は自動生成しない。</li>
<li>fm serve でクラッシュや拒否が確認されている数値バリデーション用キーワードを削除する：<ul>
<li>minimum</li>
<li>maximum</li>
<li>exclusiveMinimum</li>
<li>exclusiveMaximum</li>
</ul>
</li>
<li>オブジェクトスキーマに properties があるのに additionalProperties が指定されていない場合、additionalProperties: false を自動追加する。</li>
<li>クライアントが明示的に指定した additionalProperties の値はそのまま保持する。</li>
<li>properties や items の下にあるネストされたオブジェクトスキーマについても同様の正規化処理を行う。</li>
<li>配列スキーマで items.type &#x3D;&#x3D; “object” の場合、上流のアイテムスキーマを type: “string” に書き換え、モデルに「JSONオブジェクトを文字列として出力すること」を説明する description を付与する。</li>
<li>ストリーミングではないレスポンスの場合、レスポンスをクライアントへ返す前に、<ul>
<li>自動生成された null のツール呼び出し引数を削除し、</li>
<li>JSON文字列に変換されていた「オブジェクト配列型」の引数を元のオブジェクト配列に復元する。</li>
</ul>
</li>
</ul>
<p>で、これを実装したのがfmproxyです。</p>
<p>こういうの、Appleに報告したいけどどこから報告すればいいんですかね？</p>
<h2 id="動作確認">動作確認</h2><p>OpenCodeの設定をそのままに実行するために、fmを別ポートで動かし、プロキシを本来のポートで動かします。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">ターミナル1</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">fm serve --port 1977</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">ターミナル2</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">./fmproxy --upstream http://localhost:1977</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">ターミナル3</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">opencode</span></span><br></pre></td></tr></table></figure>

<p>で、実行した結果が以下のものです。バッチリ動きました。まあ動かせたので満足し、複雑な実装をさせてみたり・・・はしていませんが。</p>
<img src="/images/2026/20260624a/スクリーンショット_2026-06-13_21.50.32.png" alt="スクリーンショット_2026-06-13_21.50.32.png" width="1200" height="765" loading="lazy">

<p>ただ、いくつか落とし穴がありました。</p>
<ul>
<li><code>fm serve</code>はUnixドメインソケットにも対応するので、当初はfmproxyからfmコマンドを起動し、親子ではUnixドメインソケットで通信させるところまで実装していました。ですが、そうするとなぜかpccモデルが使えなかったので、別プロセス起動とした</li>
<li>systemモデル（完全ローカル）だとコンテキストウインドウが小さすぎるのかエラーとなってしまった。</li>
</ul>
<h2 id="まとめ">まとめ</h2><p>というころで、動くには動きましたが、完全ローカル（な方のモデル）で実装までいけるか？というのはできませんでした。まあ、コーディングはAIのやるタスクの中でも大量のパラメータを持つ複雑なフラグシップモデルで実施するようなものなので、動いたとしても性能はそこそこでしょう。</p>
<p>とはいえ、ツール呼び出し、構造化出力対応なので、他のシステムから気軽に呼び出せるのは強いです。特別なサブスクも不要でセキュリティは保証されています。ローカルの方なら外部通信も発生しません。OpenAI互換サーバーも（やや気難しいですが）あるので実装もしやすいです。ちょっとした分類タスクとかヒューリスティックな判定処理とか、ちょっとした処理の自動化には十分でしょう。なにより、これがOSインストールからいきなり使える、というのは良いですね。</p>
<p>AppleはAIでは周回遅れ、みたいに言われがちですが、個人的には結構ツボなアップデートでした。将来の発展も楽しみです。</p>
]]></content>
    <summary type="html">みなさん、WWDC26の情報はみられましたか？昨年発表されたDocker互換のコンテナランタイムがパワーアップし、WSLのようなLinuxコンソール体験を提供するcontainer machineとか楽しそうですよね。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="LLM" scheme="https://future-architect.github.io/tags/LLM/"/>
    <category term="Mac" scheme="https://future-architect.github.io/tags/Mac/"/>
  </entry>
  <entry>
    <title>エージェントスキルを評価する仕組みを作ってみる</title>
    <link href="https://future-architect.github.io/articles/20260622a/"/>
    <id>https://future-architect.github.io/articles/20260622a/</id>
    <published>2026-06-21T15:00:00.000Z</published>
    <updated>2026-06-21T15:00:00.000Z</updated>
    <author><name>澁川喜規</name></author>
    <content type="html"><![CDATA[<p>コーディングエージェントの様々なツール横断で使えるハーネスといえば、skillsという雰囲気になってきました。AGENTS.mdやMCPもありますが、作りやすさや小回りが効く点、プロジェクトやチーム単位で気軽に改善していける点など、人気になるのはうなづけます。もちろん、MCPが全てにおいて劣っているというわけではないので、必要に応じて使い分けることになると思います。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="left">項目</th>
<th align="left">AGENTS.md &#x2F; CLAUDE.md</th>
<th align="left">MCP</th>
<th align="left">Skills</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>主な役割</strong></td>
<td align="left">プロジェクト全体の方針・ルール共有</td>
<td align="left">外部データや外部システム連携</td>
<td align="left">特定タスクのノウハウ・手順共有</td>
</tr>
<tr>
<td align="left"><strong>読み込みタイミング</strong></td>
<td align="left">常時</td>
<td align="left">必要時</td>
<td align="left">必要時</td>
</tr>
<tr>
<td align="left"><strong>コンテキスト消費</strong></td>
<td align="left">大</td>
<td align="left">中〜大</td>
<td align="left">小</td>
</tr>
<tr>
<td align="left"><strong>配布方法</strong></td>
<td align="left">Gitリポジトリ</td>
<td align="left">MCPサーバー</td>
<td align="left">Gitリポジトリ</td>
</tr>
<tr>
<td align="left"><strong>更新反映</strong></td>
<td align="left">Git更新が必要</td>
<td align="left">サーバー更新のみ</td>
<td align="left">Git更新が必要</td>
</tr>
<tr>
<td align="left"><strong>動的データ対応</strong></td>
<td align="left">×</td>
<td align="left">◎</td>
<td align="left">△</td>
</tr>
<tr>
<td align="left"><strong>巨大データ対応</strong></td>
<td align="left">×</td>
<td align="left">◎</td>
<td align="left">×</td>
</tr>
<tr>
<td align="left"><strong>外部システム操作</strong></td>
<td align="left">×</td>
<td align="left">◎</td>
<td align="left">△（MCPやCLIを呼ぶ手順は書ける）</td>
</tr>
<tr>
<td align="left"><strong>リアルタイム情報取得</strong></td>
<td align="left">×</td>
<td align="left">◎</td>
<td align="left">×</td>
</tr>
<tr>
<td align="left"><strong>認証・権限制御</strong></td>
<td align="left">×</td>
<td align="left">◎</td>
<td align="left">×</td>
</tr>
<tr>
<td align="left"><strong>オフライン利用</strong></td>
<td align="left">◎</td>
<td align="left">△</td>
<td align="left">◎</td>
</tr>
<tr>
<td align="left"><strong>モデル間移植性</strong></td>
<td align="left">◎</td>
<td align="left">○</td>
<td align="left">○</td>
</tr>
<tr>
<td align="left"><strong>作成難易度</strong></td>
<td align="left">低</td>
<td align="left">高</td>
<td align="left">低</td>
</tr>
<tr>
<td align="left"><strong>メンテナンス負荷</strong></td>
<td align="left">中</td>
<td align="left">高</td>
<td align="left">中</td>
</tr>
<tr>
<td align="left"><strong>適した内容</strong></td>
<td align="left">コーディング規約、設計原則、プロジェクト方針</td>
<td align="left">DB、社内システム、SaaS、API、ファイルサーバー</td>
<td align="left">レビュー手順、設計テンプレート、運用手順</td>
</tr>
<tr>
<td align="left"><strong>長所</strong></td>
<td align="left">必ず読まれる</td>
<td align="left">動的・巨大データを扱える</td>
<td align="left">コンテキスト効率が高い</td>
</tr>
<tr>
<td align="left"><strong>短所</strong></td>
<td align="left">肥大化するとコンテキストを圧迫</td>
<td align="left">導入・運用コストが高い</td>
<td align="left">発見されないと利用されない</td>
</tr>
</tbody></table></div>
<p>実際、いくつか自分でも作ってみたりもしたのですが、これ実際に効果があるのか定量評価してみたくなったので、スキルのベンチを作ってみました。name&#x2F;descriptionをもとにした自動起動（暗黙的実行）もスキルにはあります。本当にやりたいタスクに対してスキルが使われているのか、処理時間や消費トークンがどれだけ節約できるのか、というのを確認してみたいですよね？</p>
<p>skillsのドキュメントにも、スキルの評価について書かれたページがあります。これの前のページではClaudeを前提としてシェルスクリプトで-pでプロンプトを実行して説明の評価をする、というのが書いてあります。おそらくこのページも詳細は書かれていませんが、それを前提としています。</p>
<p>https://agentskills.io/skill-creation/evaluating-skills</p>
<p>今回はCodex-5.5でやってみました。Codexには、CLIやApp以外に、プログラマブルにセッションを作ってプロンプトを入れて結果をもらえるCodex SDKがあります。それを使ってみています。作ってみたベンチは以下のところにpushしています。</p>
<p>https://github.com/shibukawa/codex-skill-bench</p>
<p>実際にデモ的なスキルを作って検証しました。こんな感じで処理時間や消費トークンをまとめてレポートを出すようにしました。</p>
<img fetchpriority="high" src="/images/2026/20260622a/スクリーンショット_2026-06-12_18.40.22.png" alt="スクリーンショット_2026-06-12_18.40.22.png" width="1200" height="662">

<h2 id="プロンプトの評価との違い">プロンプトの評価との違い</h2><p>今回はスキルの評価を目的としました。生成AIの文脈だと、プロンプトの評価というのは以前からありました。WWDCでもEvaluations frameworkというのが登場していました。</p>
<ul>
<li>WWDC26: Meet the Evaluations framework</li>
</ul>
<p>生成AIは決定性がないため、以前からあるユニットテストとは同じような評価は難しいです。同じ入力に対して同じ結果を返さない可能性があるからです。またユーザーの入力も多岐にわたる可能性があります。</p>
<p>基本的には機械学習のモデルの評価と同じく、なるべくバリエーションの広い入力として想定されるデータや、失敗データと、それぞれの期待される評価結果を用意してシステムに投入してみるしか方法はありません。Evaluations frameworkは成功の割合などのちょっと自由度のある検証ができたり、検証そのものをLLMに判断させてヒューリスティックな結果の検証ができるようにしています。</p>
<p>スキルの場合には、そのプロンプトレベルの評価と比べて追加でいろいろ行う必要があります。プロンプトであれば、入力のプロンプトと、出力のプロンプトのペアで評価すれば良いですが、まずはコーディングエージェントはワークスペースを用意する必要があります。そして入力んプロンプトを与えますが、それによってそもそもそのスキルが起動しているかどうか。結果がファイルの変更や新規作成であれば、その内容の評価も必要となるため、プロンプトの評価よりも難易度は2段ぐらいあがります。</p>
<p>ついでに、Codexのプランの利用枠内で検証させてみたいな、と思ったのが今回作ってみた理由です。</p>
<h2 id="Codexの起動の仕方による消費の違い">Codexの起動の仕方による消費の違い</h2><p>まず作ってみたのはソースコードのヘッダーにライセンスを入れるというシンプルなスキルです。スキルを使わなくてもエージェントがやってくれるような処理です。Copilotではスラッシュ＋スキル名、Codexはスラッシュかドル＋スキル名で明示的に実行できます。その明示的な処理、あるいは暗黙的実行。事前に一度ロードしておく、しない、のケースで比較しました。あとはスキルをつかわない、というものです。</p>
<p>結果は以下の通りです。暗黙的実行ではスキルは正しく使われているのは確認済みです。</p>
<img src="/images/2026/20260622a/本処理_と_プリロードの処理時間比較.png" alt="本処理_と_プリロードの処理時間比較.png" width="980" height="371" loading="lazy">

<img src="/images/2026/20260622a/トークン効率の比較.png" alt="トークン効率の比較.png" width="976" height="371" loading="lazy">

<p>結果としては、暗黙実行でも、明示的な実行でもこのようなシンプルなケースでは処理時間は大きく変動はしませんでした。同一セッション内部で一度ロードしていても処理時間への影響はありませんでした。キャッシュはされません。</p>
<p>一方、トークン実行を見ると、プリロードした分トークン消費は大きいのですが、その分キャッシュされているため、非キャッシュのトークン消費はほぼ変わらずでした。キャッシュと非キャッシュでAPIコストは大きく変わるため、ここはモニタリングすべきポイントですね。OpenAIのAPI価格表、ClaudeのAPIの価格表を見ると、どちらもキャッシュヒットすると1&#x2F;10の価格になります。非キャッシュトークンをまずはKPIとすれば良さそうです。</p>
<p>シンプルなタスクでは処理時間やトークンは変わらないというか、Codexがかしこすぎてちょっとしたスクリプト程度じゃメリットは出せないってことがわかりました。</p>
<h2 id="複雑なタスクでの違い">複雑なタスクでの違い</h2><p>データベースなどのデータの集計の集計のスキルを作成して実行してみました。2つのテーブルを見て組織ごとの値の集計、みたいなタスクにしてみました。先程はプリロードは差がなかったので、それはやめて、明示的実行、暗示的実行、スキルなしの3つでやっています。これだとかなりの差が出ていますね。</p>
<p>ちょっと謎なのがこちらのケースだと暗黙的なリクエストでトークン消費が大きい点。これはdescriptionの書き方が悪くて、該当するスキルを探すのに時間がかかったとかでしょうかね。</p>
<img src="/images/2026/20260622a/スクリーンショット_2026-06-12_18.26.46.png" alt="スクリーンショット_2026-06-12_18.26.46.png" width="1200" height="377" loading="lazy">

<p>このように、ちょっと複雑なデータ調査系のタスクはあらかじめてスキル化しておくなどするとトークン消費が抑えられて、効率は良くなりそうです。</p>
<p>なお、このテストの前に、1テーブルの集計のタスクのテストをやってみたのですが、それだとスキルの有無では差が出ませんでした。簡単すぎたようです。</p>
<h2 id="もっと詳細な分析の案">もっと詳細な分析の案</h2><p>今回はスキルのベンチマーク、というアイディアを試す簡易実装なので、業務でやるのであれば、もっと手間暇かけて演る必要があるでしょう。</p>
<p>今回はコストもかかるので、ほぼ1回ずつしか計測はしていませんし、結果の評価は目視でやりました。本来は結果を評価し、ランダムゆえのブレがありえるのでリトライして品質の安定度なども見る必要があるでしょう。結果は、structured outputで出力するようなテストケースのプロンプトにしたので簡単に白黒はつけられるようにしていますが、ドキュメントの作成とかであれば、検証自体にもLLMは必要でしょう。</p>
<p>今回は単一のモデル(Codex-5.5)でやっていますが、ここも安価なモデルとかも含めてやって結果を比較する方が良いでしょう。確定論的な結果が出せるスクリプトをきちんと作り込んだ場合は、安価なモデルでもきちんとした結果は得られます。今はスキルごとにモデルを選ぶというのはできませんが、エージェントごとに使えたカスタムプロンプト機能では実現されていたので、将来的にできるようになるんじゃないかと期待しています。</p>
<p>あと、モデルごとの結果というのは一度実行して結果を保存しておくことも大切です。たまに話題になる「エージェントのモデルの劣化」ですが、機械学習同様に、ゴールデンデータセットに対する処理結果を保存しておくことで、本当にどこに原因があったのかが追跡できるようになります。実はスキル側が変わっていたので結果が変わったとかもありえます。</p>
<h2 id="まとめ">まとめ</h2><p>今回はとりあえず実証実験ということで、Codex SDKを使って実装してみました。Codexがスキル起動とかをログとして出してくれているため、きちんと作ったスキルが動作し、複雑なタスクではきちんとトークン消費量の削減や時間削減につながっていることは確認できました。また、簡単なタスクではCodexがかしこすぎてスクリプトを書いても効果はない、というのも見えました。やはり計測してみると色々考察できて良いですね。</p>
<p>トークンコストの上昇や定額モデルはもう限界など言われ始めています。より効率の良いエージェントの使い方、というのは仕事としてコーディングをAIでやっていくには重要な指標となるでしょう。おそらく、今後の仕事の見積もりには人月の見積もり以外に、「AIの予想トークン数」も見積もる、みたいな話が出てくるんじゃないかと思っています。そうなると、トークン節約を数字で語れるのが「できるエンジニア」となっていくかと思います。</p>
<p>CopilotやClaudeではまた別の仕組みを作る必要があるかと思いますが、思いつきでやってみた割にはきちんと結果が出たので今回は大成功でした。</p>
]]></content>
    <summary type="html">コーディングエージェントの様々なツール横断で使えるハーネスといえば、skillsという雰囲気になってきました。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="AIエージェント" scheme="https://future-architect.github.io/tags/AI%E3%82%A8%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%B3%E3%83%88/"/>
    <category term="MCP" scheme="https://future-architect.github.io/tags/MCP/"/>
    <category term="Skills" scheme="https://future-architect.github.io/tags/Skills/"/>
  </entry>
  <entry>
    <title>AI に t-wada の TDD を頑張らせる</title>
    <link href="https://future-architect.github.io/articles/20260619a/"/>
    <id>https://future-architect.github.io/articles/20260619a/</id>
    <published>2026-06-18T15:00:00.000Z</published>
    <updated>2026-06-18T15:00:00.000Z</updated>
    <author><name>佐藤尭彰</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260619a/top.jpg" alt="" width="512" height="286">

<p>テスト連載2026 の4本目です。</p>
<p>AI戦略推進グループ(AIXG) の佐藤です。</p>
<p>最近は業務でも趣味でも Claude Code にコーディングさせることが増えてきました。趣味の方では、先日社内有志とともに参戦した AtCoder マスターズでも Claude Code 先生が大活躍でした。</p>
<p>さて昨今流行りの vibe coding、適当に回して放って置くと使い物にならないゴミを生じることが多々あります。</p>
<p>ゴミにならない事を祈りながら使うのは流石に宜しくありません。なんとか再現性を保ちつつ品質をコントロールしたいというところで、自分一人でちょっとしたツールを作りながら AI Slop を踏んでは潰してやっとまともに動くようになってきたためブログ記事にしようと思います。</p>
<div class="note-container note-warn"><span class="note-icon"></span><div>

<p>本記事の試行錯誤の大半は3月頃の話です。</p>
<p>業務ユースも幾らか始まっている中で、お客様に提供するコードを生成するならもっと真面目にハーネスとテストケースを作ります。作っています。</p>
</div></div>

<h2 id="今回の題材">今回の題材</h2><p>社内制度を非常にぼかして説明すると、今年から”とある”管理が結構厳しくなりました。この管理用サイト上の任意の操作が異様に重い上に、左右にウィンドウ分割したりすると画面がはみ出し操作不能となり、ちょっとイレギュラーな入力をしたりすると融通が効かず、削除操作は都度都度ポップアップが出てきて、その癖 1 日分で 10–20 回くらいの繊細なマウス操作を要求されます。当然1回の操作ごとに数秒のウェイトが挟まります。</p>
<img src="/images/2026/20260619a/tool_mosaic_720.png" alt="" width="720" height="357" loading="lazy">

<p>そこで代わりにターミナル上のキーボード入力だけで完結するツールを Claude Code をベースに作りました。</p>
<h2 id="vibe-coding-における-TDD">(vibe coding における) TDD</h2><p>新し目のところでこの辺を読んでください。</p>
<ul>
<li>【翻訳】テスト駆動開発の定義 - t-wadaのブログ</li>
<li>TDDは「開発者テストのTips集」t-wada氏が改めてひも解く“本質”</li>
</ul>
<p>“t-wada の TDD” と限定することでクオリティが上がる話は <del>X</del> 旧 twitter で流れていたのを目にしてやってみたところ、結構良くハマりました。</p>
<p>おそらく同じ方の note 解説記事 → 降霊術で t_wada を AI に降ろして PR レビューして貰うテクニックが伸びたのでその裏側記事を書きました！</p>
<h2 id="じゃあ、「”t-wada-の-TDD”-で作って」">じゃあ、「”t-wada の TDD” で作って」</h2><p>ツールのリクエストとレスポンスを平文で置いて、作りたいものを説明して、呪文を唱えてもまず動きません。</p>
<p>TDD のサイクルで実現できるのは、いま動かないもの (red) が将来にわたって動くようになること (green) です。加えて、動かなくなったときすぐ直せるようにしておくこと (refactor) も。</p>
<p>どの順番で何をどのくらい動くようにするかという取捨選択がどうしても大切です。それが「テストリスト」と呼ばれる最初のステップで、AIへ任せ切るために人の手が必要なステップです。</p>
<blockquote>
<p>多くの人が、書籍『テスト駆動開発』の中に出てくるこのステップ1を見逃しているようだ。「TDDはいきなりコードを書き始める。いつ終わるのかも全然見通せない」という意見は、全くの見当外れだ。</p>
</blockquote>
<h2 id="テストリストのコントロール">テストリストのコントロール</h2><p>今回の記事がテスト連載であることを思い出しました。</p>
<p>まずは、毎回のセッションで作りたい機能を言語化します。裏返せばこういう機能が実現されていること、という大枠の外部結合テストケースになります。この大枠を個別具体の関数の挙動に置き換えた単体～内部結合くらいのテストケース、これがAIに作らせるテストリストです。</p>
<p>そういうわけで、初期情報取得、入力、送信など、システムとして切り分けるならここが単位かな、というところで切り分けて置くとスムーズです。もし慎重にいくのならば Plan mode でここでできたリストを確認すると良いでしょう。最近の私は Auto mode で突っ走らせつつ、何かまずそうなら Esc で止めて（場合によっては過去に遡って歴史改変しながら）修正案を投げています。</p>
<p>機能ごとに切り分けておくとよいのは新規機能に限った話ではありません。いまこの記事を書きながら「1日単位ではなく1週間単位でまとめて入力送信できるようにしたい」みたいな指示をしたら、入力画面自体1週間単位になりそうだったので慌てて Esc 押しました。「入力は1日ごと、送信をまとめて」。</p>
<p>また、特に改修を実施するときには、今回のテストの内容にとどまらず過去との整合性も必要です。いまある挙動を変えないことはいまあるテストが保証してくれていますが、過去の挙動に影響がありそうな修正に対してはどうあるべきかを一緒に与えないと結構壊れます。大体は今回の指示を優先してよい一方、変えて欲しくないところまでもまとめて壊そうとするのでテストリストを見てまずそうなら介入します。</p>
<p>そうして一瞥した、あるいは Auto Mode で高速に流れていくテストリストが大丈夫そうであれば、あとは目を離しても早々酷いことにはなりません。最後に動いてそうならば CLAUDE.md, もしくはそこからリンクされている仕様書を更新して終了です。</p>
<img src="/images/2026/20260619a/image_720.png" alt="" width="720" height="200" loading="lazy">

<div class="note-container note-info"><span class="note-icon"></span><div>

<p>酷いことになるとしたら過去のテストを沢山巻き込むような大幅にデグレてしまったときで、こうなると面倒になるのか過去のテストを雑に green にするような hack をかましてくることがあります。予想以上に diff が膨らんだ際にはクリーンな別の session を立ち上げて監査することで対応しています。</p>
</div></div>

<h2 id="できたもの">できたもの</h2><p>を見せられれば良かったのですが、もれなく社外秘のため。</p>
<p>ただ、ある程度ツールが完成して以降というもの当該の管理サイトは週1で確認のために開くだけになりました。一式ぜんぶ手元で入力した後にまとめてリクエストだけ送信して事を済ませています。とても便利。</p>
<h2 id="おわりに">おわりに</h2><p>頭はいいけどそこまで詳しくない生成 AI に説明できるように、作りたいものに対するドメイン知識が今後もっと大事になってくる……という話は一般によく聞きますが、ではどのように活かすのか？　というとなんともはぐらかされがちです。<br>本記事では簡単な一ツールを例にしたところですが、質の良いテストリストを初っ端から精度よく生成できるような指示を与えられるのであれば、動き出しのスピードが早くなるということなのでしょう。</p>
<p>特に今回みたいな terminal 上のツールでよいのであれば、E2E や画像読み書き、Officeファイル作成など、AI にやらせるとトークンを結構な勢いで消費する人間向けの儀式をショートカットして進めることができます。極論一周回って半世紀前みたいなUIのツールが流行ってもおかしくないかもしれません。本当に、これならすぐできるので。</p>
<hr>
<p>ところで5月半ばくらいに <code>/goal</code> 機能ができたのでこの記事で気をつけるべきことの半分以上はもう不要なナレッジの可能性があります。キャッチアップはどこまでも。</p>
]]></content>
    <summary type="html">さて昨今流行りの vibe coding、適当に回して放って置くと使い物にならないゴミを生じることが多々あります。ゴミにならない事を祈りながら使うのは流石に宜しくありません。なんとか再現性を保ちつつ品質をコントロールしたいというところで、自分一人でちょっとしたツールを作りながら AI Slop を踏んでは潰してやっとまともに動くようになってきたためブログ記事にしようと思います。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="Claude" scheme="https://future-architect.github.io/tags/Claude/"/>
    <category term="TDD" scheme="https://future-architect.github.io/tags/TDD/"/>
  </entry>
  <entry>
    <title>生成AIに任せたら何でテストを書く？AIに聞いてみた</title>
    <link href="https://future-architect.github.io/articles/20260616a/"/>
    <id>https://future-architect.github.io/articles/20260616a/</id>
    <published>2026-06-15T15:00:00.000Z</published>
    <updated>2026-06-15T15:00:00.000Z</updated>
    <author><name>澁川喜規</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260616a/top.jpg" alt="" width="512" height="279">

<p>テスト連載2026 の2本目です。</p>
<p>生成AIにコーディングを任せると、特に指示をしなくても自動でライブラリやフレームワークを選定したりします。おそらく、選ばれるのは人気があってよく使われるものかなと思います。じゃあ具体的に何が使われるのか、普段使っているのはCodexなので、ChatGPT自身に聞いてみました。</p>
<h2 id="フレームワークごとの選ばれるフレームワーク＋テストのランキング">フレームワークごとの選ばれるフレームワーク＋テストのランキング</h2><p>過去ログ参照とかメモリ機能とか全部オフにしたChatGPTに何度か確認しつつまとめたのが以下のリストです。過去ログ参照で結果が変わったりもしますね。Goは確かにこの通りというか、Go 1.24で入ったパスパラメータ機能は明示的に指示しないと使ってくれないですね。執拗に他のライブラリを使おうとします。</p>
<h3 id="Go">Go</h3><ul>
<li>第1候補: Gin + 標準 testing</li>
<li>第2候補: chi v5 + 標準 testing</li>
<li>第3候補: Echo v5 + 標準 testing &#x2F; Testify</li>
</ul>
<h3 id="JavaScript">JavaScript</h3><ul>
<li>第1候補: Next.js 16.2.x + Jest 30</li>
<li>第2候補: React 19.2 + Vite系 + Vitest 4.1</li>
<li>第3候補: Nuxt 4 + Vitest</li>
</ul>
<p>E2Eテストは以下の通り</p>
<ul>
<li>第1候補: Playwright</li>
<li>第2候補: Cypress</li>
<li>第3候補: Puppeteer</li>
</ul>
<h3 id="Java">Java</h3><ul>
<li>第1候補: Spring Boot 4.0.5（または成熟ライン 3.5.x）+ JUnit 6</li>
<li>第2候補: Quarkus 3.34.x &#x2F; 3.33 LTS + JUnit</li>
<li>第3候補: Micronaut 4.10.x + JUnit</li>
</ul>
<h3 id="C">C#</h3><ul>
<li>第1候補: ASP.NET Core 10.0 + xUnit.net 3</li>
<li>第2候補: ASP.NET Core Minimal APIs + xUnit 3</li>
<li>第3候補: ASP.NET Core + NUnit</li>
</ul>
<h3 id="Python">Python</h3><ul>
<li>第1候補: FastAPI 0.136.0 + pytest 9.0.x</li>
<li>第2候補: Django 6.0.4 + pytest</li>
<li>第3候補: Flask 3.1.3 + pytest</li>
</ul>
<h3 id="Ruby">Ruby</h3><ul>
<li>第1候補: Rails 8.1.x &#x2F; 8.0.x + Minitest 6</li>
<li>第2候補: Rails + RSpec（RSpec Rails 8.0.4 &#x2F; RSpec 4 beta〜3.13系）</li>
<li>第3候補: Sinatra + RSpec</li>
</ul>
<h3 id="PHP">PHP</h3><ul>
<li>第1候補: Laravel 13 + PHPUnit 13</li>
<li>第2候補: Laravel 13 + Pest 4</li>
<li>第3候補: Symfony 8系 + PHPUnit 13</li>
</ul>
<h3 id="Rust">Rust</h3><ul>
<li>第1候補: axum 0.8.9 + cargo test</li>
<li>第2候補: Actix Web 4 + cargo test</li>
<li>第3候補: Rocket 0.5.1 + cargo test</li>
</ul>
<h2 id="どのような基準で選ばれるか？">どのような基準で選ばれるか？</h2><p>1位、2位とありますが、常に1位が選ばれるわけではなく条件によって選択が変わるようです。選定では作り物として指定されたものの特性も考慮に入れられるようです。例えば、管理画面も作ってというとFastAPIではなくDjangoになったりとかですかね。</p>
<ul>
<li>既存のコードで使っているもの</li>
<li>フルスタックかそうじゃないか？: ウェブフロントエンド用のフロント＋バックエンドなのか、Rest API中心なのか</li>
<li>軽量なのか、フルスタックなのか、性能重視か</li>
</ul>
<h2 id="まとめとAIの好み（クセ）を知る必要性">まとめとAIの好み（クセ）を知る必要性</h2><p>生成AIが処理を行う時に参照するのは、モデルそのものと、コンテキストという限られたメモリの2つです。一般的な知識であれば、細かい指示をしなくても、コンテキストにたくさん指示を弾込めしなくてもすいすい動いてくれます。逆に独自なものや使い方を明示的に指示しないと使えないものを指示するとその分コンテキストを消費し、知能が下がった状態で動きます。何度も同じような指示を送るのはいくらプロンプトのキャッシュヒットすれば安くなるからといっても最初から送らない無料の方がリーズナブルでしょうし。最終成果物の質に対して影響を与えない選択肢であればおまかせしてしまい、守りたい部分だけ指示するというのが良いかと思っています。</p>
<p>また、モデルも日々進化しています。テストに関しては昨年8月ごろだと、指示しないと書いてくれませんでしたが、最近のモデルだと何も指示しなくてもテストコードを書いてテストしてくれます。今時点でガチガチにハーネスを固めて思い通りに動かそうとしても、将来のバージョンでは重複した指示になったり、下手すると逆方向にAIが働きにくい指示になってしまうことも想像できます。新しいプロジェクトごとにAGENTS.mdなどを作り直すと、以前よりも必要な指示はだいぶ減ってきているなと感じます。</p>
<p>最近は自分の好みのミニマリストな設計にこだわるのをやめて、生成AIが提案してきたスタイルをなるべく受け入れるようにしています。Goだとinternalパッケージに本体をなるべく押し込もうとする、とかですね。モデルの特性に合わせてこちらも柔軟にスタイルを変えていく。子育てと一緒ですね。</p>
<p>今後も定点観測的に調べていきたいですね。あと他のモデルでも。</p>
]]></content>
    <summary type="html">生成AIにコーディングを任せると、特に指示をしなくても自動でライブラリやフレームワークを選定したりします。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="テスト" scheme="https://future-architect.github.io/tags/%E3%83%86%E3%82%B9%E3%83%88/"/>
    <category term="生成AI" scheme="https://future-architect.github.io/tags/%E7%94%9F%E6%88%90AI/"/>
  </entry>
  <entry>
    <title>テスト連載2026を開始します / AIがテストを書く中で、「あるべき仕様」を単体テストで担保するために工夫してみていること</title>
    <link href="https://future-architect.github.io/articles/20260615a/"/>
    <id>https://future-architect.github.io/articles/20260615a/</id>
    <published>2026-06-14T15:00:00.000Z</published>
    <updated>2026-06-14T15:00:00.000Z</updated>
    <author><name>市川裕也</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260615a/top.jpg" alt="" width="512" height="279">

<p>こんにちは。CSIG の市川です。普段は FutureVuls という脆弱性・サプライチェーンリスク管理 SaaS の開発を担当しています。</p>
<p>今年から、初の試みとして「テスト連載」を実施します！🎉<br>AI 時代において、重要度が増すテスト技術、動作確認、品質保証などをテーマとした連載です。<br>今回は 5 人の方が参加します。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="left">日付</th>
<th align="left">執筆者</th>
<th align="left">タイトル&#x2F;テーマ</th>
</tr>
</thead>
<tbody><tr>
<td align="left">6&#x2F;15(月)</td>
<td align="left">市川裕也さん</td>
<td align="left">AI がテストを書く中で、「あるべき仕様」を単体テストで担保するために工夫してみていること</td>
</tr>
<tr>
<td align="left">6&#x2F;16(火)</td>
<td align="left">澁川喜規さん</td>
<td align="left">生成AIに任せたら何でテストを書く？AIに聞いてみた</td>
</tr>
<tr>
<td align="left">6&#x2F;17(水)</td>
<td align="left">清水利博さん</td>
<td align="left">非機能テストを記号接地する</td>
</tr>
<tr>
<td align="left">6&#x2F;19(金)</td>
<td align="left">佐藤尭彰さん</td>
<td align="left">AI に t-wada の TDD を頑張らせる</td>
</tr>
</tbody></table></div>
<p>本記事は、テスト連載2026 の 1 日目の記事です。</p>
<h2 id="はじめに">はじめに</h2><p>最近とあるソフトウェアを 2 か月くらいかけて 0 から AI で構築しており、この開発のなかで「テストを AI にどう書かせるか、どうやってテスト品質とコード品質を担保すれば良いか」を考えることが増えました。<br>今回はそのあたりで自分が考えていることを書き、実際にどのような運用を試してみているか（作成したプロンプト&#x2F;skill や使用するタイミング、開発フローなど）を紹介します。</p>
<p>今回紹介する方法は、「AI で開発のスピードを出来る限り高めて、生産量を最大化する」という手法ではなく、品質を担保するためにある程度スピードを落とすものです。 (世間の流れからは逆行しているかもしれないです)<br>自分も AI とのかかわり方は随時アップデートしていきたいと考えているため、このやり方や思想の是非を含め、もしご意見あればぜひコメント等いただけますと幸いです。</p>
<p>本記事の中でもし参考になる部分があれば、必要な範囲で取り入れてみていただければと思います。</p>
<h2 id="TL-DR">TL;DR</h2><ul>
<li>AI が実装もテストも自律的に書くようになり、生成物を全部レビューするのは現実的でなくなった。品質を保つ手段として、単体テストが一層重要になる</li>
<li>ただし「あるべき振る舞い」を決めるのは人間であり、テストはその仕様書。ここは AI に任せきれない、人間が握り続けるべき部分</li>
<li>AI に任せると 2 つが崩れる。 <strong>(A)</strong> 振る舞いの記述が読みづらく、何を担保しているか人間が確認できない。 <strong>(B)</strong> 基準がないと写経テストや不要なテストが量産され、品質が下がる</li>
<li>対処として、「振る舞いの読みやすさのルール」と「何をテストし、何をテストしないか」を 1 つの markdown（<code>test-design.md</code>）にまとめ、AI に常に読ませている</li>
<li>一定の効果はあるが、確認の手間など課題も残っている</li>
</ul>
<h2 id="想定読者">想定読者</h2><ul>
<li>AI 時代において、単体テストの品質保証で悩んでいる人</li>
<li>AI にどのようなテストを書かせるべきか &#x2F; 書かせないべきかを悩んでいる人</li>
<li>AI の出力する日本語が読みづらく、振る舞いを理解するのに困っている人</li>
</ul>
<h2 id="スコープ外の論点">スコープ外の論点</h2><ul>
<li>E2E テスト</li>
<li>DDD 等の設計手法自体 (コードの品質を保つ、という観点ではこれも重要だと思いますが、本記事では扱いません)</li>
<li>単体テストのカバレッジの上げ方</li>
<li>AI が、テストを通すためにテストを勝手に書き換えてしまう問題への対処方法</li>
</ul>
<h2 id="AI-が自律して実装するようになった中で、品質をどう保つか">AI が自律して実装するようになった中で、品質をどう保つか</h2><p>ここ半年ほどで、AI はかなり自律的に動くようになりました。plan mode で作りたい機能を渡して、出てきた実装計画を auto mode で実装させれば、コードもテストも最後まで自律して書いてくれます。</p>
<p>この変化により、単位時間あたりのコード生成量は大きく増えました。それに伴い、生成されたコードを全部読んでレビューするのは、認知負荷の面でもスピードの面でもかなり厳しくなってきました。<br>かといって、中身を見ずにソフトウェアに取り込み続けると、気づいたときには自分でも仕様が正しいか把握できない、変更容易性の低いソフトウェアが出来上がっている、ということになりかねません。</p>
<p>では、今までの何倍ものスピードで開発していくなかで、どうやって品質を保てばいいのでしょうか。色々観点はあるかと思いますが。自分は「<strong>単体テストにより品質を保つ</strong>」ことが重要なポイントの1つだと考えています。AI が量産するロジックの退行 (バグ) を最も簡単かつ継続的に検出できるのが単体テストだからです。</p>
<p>本記事では、AI 時代において、単体テストの品質を保つ方法について、持論を述べていきます。</p>
<h2 id="前提と、自分が大事だと考えている-2-つの主張">前提と、自分が大事だと考えている 2 つの主張</h2><h3 id="前提-あるべき仕様は人間が決め、それを自動テストで継続的に担保する">前提: あるべき仕様は人間が決め、それを自動テストで継続的に担保する</h3><p>まず前提として、<strong>あるべき仕様、あるべき振る舞いは人間が決めるもの</strong>です。この部分は、AI 時代においても不変でしょう。(この前提がそもそもずれてきていたらすみません、コメント等いただきたいです)</p>
<p>そして、この「あるべき仕様、あるべき振る舞い」は、テストコードによって担保することが望ましいです。テストコードは、そのソフトウェアがどう振る舞うべきかを伝える、いわば「仕様書」でもあります。</p>
<p>品質を担保する上では、あるべき振る舞いが達成され続けているかを自動テストで継続的に担保していくことが、生成スピードが上がった今こそより重要になってくると思います。</p>
<h3 id="この前提から導かれる-2-つの主張">この前提から導かれる 2 つの主張</h3><p>AI にコード・テスト生成を任せながら、「自動テストで品質を継続的に担保する」ことを成立させるには、次の 2 つを満たす必要があります。</p>
<ul>
<li><strong>A: 実装計画やテストコードから、あるべき振る舞いを把握しやすい形にしておく (振る舞いの記述の readability をあげる)</strong></li>
<li><strong>B: そのテストコード自体の品質を保つ</strong></li>
</ul>
<p>何も対策をしないと、AI に任せたときに以下の表に記載したような課題が顕在化し、 A も B も崩れます。<br>この主張・課題と、課題への対処を、先に表にまとめておきます。</p>
<div class="scroll"><table>
<thead>
<tr>
<th></th>
<th>あるべき姿</th>
<th>AI に任せたときの課題</th>
<th>どう対処するか</th>
</tr>
</thead>
<tbody><tr>
<td><strong>A</strong></td>
<td>あるべき振る舞いを、plan &#x2F; テストコードから把握しやすくしておく</td>
<td>AI が出す「確認すべき振る舞い」の説明が分かりづらく、担保できているか人間が確認しづらい</td>
<td>振る舞いの「読みやすさのルール」を決める</td>
</tr>
<tr>
<td><strong>B</strong></td>
<td>テストコード自体の品質を保つ</td>
<td>品質の基準を与えないと、写経テストや不要なテストが量産され、保守性やリファクタリングへの耐性が下がる</td>
<td>振る舞いをテストさせる &#x2F;「何をテストし、何をテストしないか」を決める</td>
</tr>
</tbody></table></div>
<p>以降、A・B それぞれについて「なぜ大事か → AI に任せると何が問題か → どう対処してみているか」の順に見ていきます。</p>
<h2 id="A-振る舞いを把握しやすい状態にする">A: 振る舞いを把握しやすい状態にする</h2><h3 id="なぜ「把握しやすさ」が必要なのか">なぜ「把握しやすさ」が必要なのか</h3><p>テストが「あるべき振る舞い」を担保できているかを人間が判断するには、まず、そのテストが<strong>どんな振る舞いを確認しようとしているのか</strong>が人間にとって把握しやすい状態でなければいけません。</p>
<p>テスト内容を人間が把握しづらい場合、「確認したつもり」で中身を流してしまい、結局は何も担保できていない、という状態になる恐れがあります。</p>
<h3 id="課題A-AI-が出力する「確認すべき振る舞い」は分かりづらい">課題A: AI が出力する「確認すべき振る舞い」は分かりづらい</h3><p>何も指示しないと、AI が出力する「振る舞い」の説明は、英語を機械的に和訳しただけのような日本語になったり、実装の中身に踏み込んだ書き方になったりしがちです。</p>
<p>こうなると、その振る舞いの説明を読んでも「結局このテストは何を保証したいのか」が頭に入ってこず、人間がチェックする役割を果たせません。</p>
<h3 id="対処A-読みやすさのルールを決めておく">対処A: 読みやすさのルールを決めておく</h3><p>そこで自分は、 plan 時に振る舞いの説明を AI に書いてもらうようにし、その際次のような点を守って書かせるようにしています。</p>
<ul>
<li>コード内の変数名や内部のデータ構造を、説明にそのまま持ち込まない</li>
<li>その<strong>コードベースを知らない人（たとえばドメインに詳しい非エンジニア）が読んでも理解できる日本語</strong>にする</li>
<li>体言止めで止めず、「何をしたら、どうなれば正しいのか」を文章で言い切る</li>
</ul>
<p>テストコード内のテストケース名やコメントも、同様の観点を守らせています。<br>この運用の効果については、「上記の考え方を、どのように開発フローに反映させているか」の節に記載しています。</p>
<h2 id="B-テストコードの品質を保つ">B: テストコードの品質を保つ</h2><h3 id="前提-そもそも「品質の高いテスト」とは何か">前提: そもそも「品質の高いテスト」とは何か</h3><div class="note-container note-warn"><span class="note-icon"></span><div>

<p>テストにおいてどのような観点を大切にすべきかは、作っているソフトウェアによって異なると思います。<br>下記の観点には過不足があると思うので、あくまで参考として捉えていただければと思います。</p>
</div></div>

<p>結論から言うと、以下の 2 点は「品質の高いテスト」を満たすための重要な要素であると考えています。</p>
<ul>
<li>① 実装の詳細に立ち入らず、振る舞いを確認していること</li>
<li>② テストする価値が高いコードに絞って、テストを記述していること</li>
</ul>
<p>以下、『単体テストの考え方&#x2F;使い方』という書籍を下敷きにし、この理由を記載します。</p>
<p>https://amzn.asia/d/04vVsRk9</p>
<p>同書では、良い単体テストは次の 4 つの要素で評価できるとされています。</p>
<ul>
<li>退行（≒ バグ）に対する保護</li>
<li>リファクタリングへの耐性</li>
<li>迅速なフィードバック</li>
<li>保守のしやすさ</li>
</ul>
<h4 id="①-なぜ振る舞いをテストする必要があるか">① なぜ振る舞いをテストする必要があるか</h4><p>上記 4 点のうち、特に意識したいのが「リファクタリングへの耐性」です。<br>これはざっくり言うと「偽陽性の少なさ」です。偽陽性とは、実装は正しく振る舞っているのに、テストが落ちてしまう状態を指します。</p>
<p>偽陽性が増えると、開発者は次第にテストの結果を信用しなくなります。「また誤検知だろう」と落ちたテストを軽く扱うようになり、テストの存在意義が薄れていきます。また、何が正しい振る舞いかが分かりづらくなるため、変更容易性が低くなっていきます。</p>
<p>そして、この偽陽性は、テストが実装の詳細を確認してしまっているときに起きやすくなります。実装の詳細に立ち入っていると、実装の中身が少し変わっただけで、振る舞いは正しいのにテストが落ちる、という状況が発生しやすくなります。そのため、「実装の詳細」に立ち入らず、「振る舞い」のみを確認するテストを書くことが重要なのです。</p>
<h4 id="②-なぜテストする価値が高いコードに絞るべきか">② なぜテストする価値が高いコードに絞るべきか</h4><p>これは、主に「保守のしやすさ」の観点で重要です。</p>
<p>「実装の中身をそのまま書き写した写経テスト」のような、取るに足らないテストが多いと、「肝心の振る舞いがちゃんと確認できているか」を人間が読み取る邪魔になります。そのようなテストは、段々保守されなくなっていきます。</p>
<p>継続的に保守していく、という観点において、「不要なテストを書かない」ことも重要です。</p>
<h3 id="課題B-基準を与えないと、写経テストや不要なテストが量産される">課題B: 基準を与えないと、写経テストや不要なテストが量産される</h3><p>テストについて特に基準を与えないと、AI は次のようなテストを書きがちです。</p>
<ul>
<li>実装の中身をそのまま書き写したような「写経」テスト</li>
<li>「振る舞い」ではなく「実装の詳細」を確認するテスト</li>
</ul>
<p>先ほどの 4 要素で言えば、これらは「リファクタリングへの耐性」や「保守のしやすさ」を下げる、質の悪いテストです。<br>さらに、AI に任せると際限なくテストを増やせてしまうので、取るに足らないテストも大量に生成されがちです。<br>こうしたノイズが多いと、「肝心の振る舞いがちゃんと確認できているか」を人間が読み取る邪魔になります。<strong>テストは多ければ多いほど良い、というわけではありません。</strong></p>
<h3 id="対処B-振る舞いをテストさせる-「何をテストし、何をテストしないか」を決める">対処B: 振る舞いをテストさせる &#x2F;「何をテストし、何をテストしないか」を決める</h3><p>そこで、「実装の詳細」ではなく、「振る舞い」をテストさせるようにします。<br>具体的には、</p>
<ul>
<li>可能な限り出力値ベース・テストを優先する (状態ベース・テストやコミュニケーション・ベース・テストは最小限にする)</li>
<li>期待値はリテラルで直書きする</li>
<li>モック系とスタブ系を使い分ける。外に出る副作用が存在しないときに、そのオブジェクトが呼ばれたかの検証は行わない</li>
</ul>
<p>また、コードの性質によってテストの厚みを変えるようにしています。</p>
<ul>
<li><strong>ドメインロジック &#x2F; アルゴリズム</strong>は厚めにテストさせています。境界値、条件の組み合わせ、状態遷移、壊れやすい入力など、確認すべき振る舞いを網羅的に押さえます。ここはテストしやすく、かつバグが下流に波及しやすいからです。</li>
<li><strong>usecase 層 &#x2F; コントローラ層の処理</strong>は、代表的なハッピーパスと、純粋ロジック側では確認できない「複数の処理を組み合わせたときにだけ起きる異常系」だけで十分、としています。個々の値の網羅は、純粋ロジック側で済んでいるはずだからです。</li>
<li>それ以外の <strong>取るに足らないコード</strong>はテストしないようにします。</li>
</ul>
<p>「何をテストしないか」をはっきり決めておくことが、不要なテストの量産を抑え、結果として「担保すべき振る舞い」を見通しよく保つことに繋がります。</p>
<h2 id="上記の考え方を、どのように開発フローに反映させているか">上記の考え方を、どのように開発フローに反映させているか</h2><h3 id="markdown-にまとめる">markdown にまとめる</h3><p>A（振る舞いの読みやすさ）も B（テストの品質基準）も、毎回 AI に口頭で伝えるのは大変です。<br>そこで自分は、これらのテスト方針を 1 つの markdown（<code>test-design.md</code>）にまとめ、plan mode 時やレビュー時に AI に自動で読ませるようにしました。</p>
<details>
<summary>実際に使っている <code>test-design.md</code>（特定プロジェクトに依存する記述を削った一般化版）</summary>

<div class="note-container note-info"><span class="note-icon"></span><div>

<p>実物から、プロジェクトの構成やコードに依存する部分を取り除き、どのプロジェクトでも読めるように一般化したものです。雰囲気が伝わればと思って載せています。</p>
</div></div>

<figure class="highlight md"><input type="checkbox" id="code-wrap-g2ilu2-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-g2ilu2-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="strong">**原則**</span></span><br><span class="line"></span><br><span class="line"><span class="bullet">-</span> <span class="strong">**振る舞い（契約）をテストする。**</span> 呼び出し側・利用者から見た結果を確認する。実装の詳細（関数名・内部データ構造・テスト手法）はテストしない。実装が変わっても契約が保たれていれば通り、バグが入れば落ちる ―― これを目標にする。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**判定軸: そのテストは「バグ」で落ちるか、「意図した編集」で落ちるか。**</span> 意図した編集でだけ落ちるテスト（＝実装のデータをテスト側に書き写しただけの写経テスト）は書かない。バグを捕まえず、直す箇所が 2 つに増える保守コストだけが残る。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**壊れにくさを最優先する。**</span> 退行検出力（壊れたら気づく）と壊れにくさ（意図した編集では落ちない）が両立しないときは、壊れにくさを守る。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**assert の無いテストを書かない。**</span> 値を出力するだけのものはテストではない。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**出力値ベースを優先する。**</span> 「この入力でこの出力／エラーになる」を確認するのが基本。状態ベース・相互作用ベース（モックで「こう呼ばれたか」を見る）は、それでしか確認できない振る舞いのときだけ最小限に使う。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**読み手に「何を確認するテストか」が伝わること。**</span> 読み手は実装を知らない同僚だと思って、テスト名・コメント・計画の「確認すべき振る舞い」を書く。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**テストしやすさはコード設計のサイン。**</span> 1 つの操作の振る舞いに落ちず、複数操作の組み合わせをテストする羽目になったら、確認すべき振る舞いの定義が雑か、実装が複雑すぎる。後者ならテストを捻る前にコードのリファクタを検討する。</span><br><span class="line"></span><br><span class="line"><span class="strong">**記述スタイル**</span></span><br><span class="line"></span><br><span class="line">各項目は「何をしたら／どうなれば正しいか」を 1 文で書く。実装・テスト用語で説明せず、呼び出し側・利用者から見た結果で書く。読みやすさの規則:</span><br><span class="line"></span><br><span class="line"><span class="bullet">1.</span> 造語・略語は初出で定義するか、既知の言葉に置き換える。実装内部の言葉より利用者の言葉を選ぶ。</span><br><span class="line"><span class="bullet">2.</span> 変数名・式を文の主語・述語にしない。日本語で言い切ってから、コードや式は括弧で参照として添える。</span><br><span class="line"><span class="bullet">3.</span> 状態は動詞化する（「＝true」で止めない）。主語と助詞を省略しない。</span><br><span class="line"><span class="bullet">4.</span> 1 項目＝1 振る舞い。条件が複数あるなら箇条書きを割る。</span><br><span class="line"><span class="bullet">5.</span> 括弧の中に括弧を入れない。補足を主文の途中に挟まない。</span><br><span class="line"><span class="bullet">6.</span> 出自メモや相互参照を振る舞いの文に混ぜない。</span><br><span class="line"></span><br><span class="line">Go の場合は、テスト関数名は英語にし、振る舞いはその関数の直上に日本語コメントで 1 文書く。<span class="code">`t.Run(name, …)`</span> のサブテスト名は日本語でよく、ここが「何をしたらどうなれば正しいか」を読み手に伝える主役になる。</span><br><span class="line"></span><br><span class="line"><span class="strong">**観点リスト（対象の性質に合うものを選ぶ）**</span></span><br><span class="line"></span><br><span class="line"><span class="bullet">-</span> 境界値（空／最小／最大／オフバイワン）</span><br><span class="line"><span class="bullet">-</span> 同値分割（入力を同じ振る舞いのクラスに分け、各クラスの代表を 1 つ）</span><br><span class="line"><span class="bullet">-</span> decision table（条件の組み合わせで結果が変わるロジックの網羅）</span><br><span class="line"><span class="bullet">-</span> 状態遷移（許される遷移と許されない遷移）</span><br><span class="line"><span class="bullet">-</span> エラー推測（nil・ゼロ値・重複・順序・並行・桁あふれなど、経験上壊れやすい所）</span><br><span class="line"><span class="bullet">-</span> 往復（encode→decode / write→read で欠落・破損しない）</span><br><span class="line"><span class="bullet">-</span> 不変条件（「壊れていたら必ずバグ」な性質。例: 識別子が重複しない）</span><br><span class="line"><span class="bullet">-</span> キャンセル／タイムアウト（打ち切りと中断を区別する）</span><br><span class="line"><span class="bullet">-</span> 部分失敗の継続方針（途中でエラーが出たとき、止めるのか続けるのか）</span><br><span class="line"><span class="bullet">-</span> 冪等性（同じ操作を 2 回実行しても結果が変わらない。必要な処理のみ）</span><br><span class="line"><span class="bullet">-</span> 環境依存の注入（時刻・乱数・実行環境を外から渡して決定論的にする）</span><br><span class="line"></span><br><span class="line"><span class="strong">**網羅度の出し分け（純粋ロジックは厚く、繋ぎ・I/O は薄く）**</span></span><br><span class="line"></span><br><span class="line"><span class="bullet">-</span> <span class="strong">**純粋ロジック（ドメイン層など）**</span> ― 観点を網羅的に。境界値・decision table・状態遷移・エラー推測などを厚く押さえる。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**orchestration / 繋ぎ（ユースケース層など）**</span> ― 代表的なハッピーパス＋異常ケースの代表で足りる。純粋ロジック側で網羅済みの値を、繋ぎ側で重複してフル比較しない。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**presentation（CLI・出力フォーマットなど）**</span> ― ほぼテストしない。入力検証に固有のロジックがあるなら、純粋な検証関数に寄せて出力値ベースでテストする。出力フォーマットは構造（ヘッダ・列順・行数）だけ確認し、各セルの値は計算側の関数のテストに委ねる。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**I/O 実装（DB・ファイル・外部 API など）**</span> ― 結合寄り。本物の依存に対する確認か、契約を模した fake で確認する。</span><br><span class="line"></span><br><span class="line"><span class="strong">**unit と結合の境界**</span></span><br><span class="line"></span><br><span class="line"><span class="bullet">-</span> 外部サービスとの契約（API メソッド名・送信 JSON など）は unit で検証しない。本物に投げないと真偽が分からないので、結合テストか不変条件に寄せる。送信内容をテスト側に再記述する写経テストは書かない。</span><br><span class="line"><span class="bullet">-</span> スナップショットはグレー。外部契約の写しは unit では真偽を検証できない。</span><br><span class="line"><span class="bullet">-</span> 不変条件テストは「壊れていたら必ずバグ」になる構造的性質だけ網羅してよい。それ以外の値の写経は書かない。</span><br><span class="line"></span><br><span class="line"><span class="strong">**外部 I/O を含むテストの書き方**</span></span><br><span class="line"></span><br><span class="line"><span class="bullet">-</span> <span class="strong">**境界で分離する。**</span> 「データをどう組み立て／解釈するか（ロジック）」と「実際に読み書きする（I/O）」を分ける。ロジックは純粋にテストできる形に寄せる。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**ファイルは round-trip で見る。**</span> 書き出して読み戻すと欠落しないことを確認する。実ファイルが要るなら一時ディレクトリを使う。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**期待値はリテラルで直書きする。**</span> 実装と同じ計算式でテスト内に期待値を作らない（実装とテストが同時に間違えても気づけない）。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**テストダブルの使い分け。**</span> 既定は stub / fake。データ取得のダブルは「呼ばれたか」を検証しない。mock / spy は、外部への副作用の送出そのものが契約のときだけ最小限に使う。fake は本物の契約を模倣する。</span><br><span class="line"></span><br><span class="line"><span class="strong">**確認しないと決めた振る舞いも書く**</span></span><br><span class="line"></span><br><span class="line">「価値の高いテストだけ残す」ためには、確認することと同じくらい、確認しないと決めたことが重要になる。計画・レビューでは「確認すべき振る舞い」の一覧の後に「確認しないと決めた振る舞い」の一覧を併記し、各項目に除外理由を 1 つ添える。除外理由は次の語彙から選ぶ:</span><br><span class="line"></span><br><span class="line"><span class="bullet">-</span> 写経 / change-detector になる</span><br><span class="line"><span class="bullet">-</span> 取るに足らない</span><br><span class="line"><span class="bullet">-</span> 結合テスト / 手動確認に隔離</span><br><span class="line"><span class="bullet">-</span> 上流の純粋ロジックのテストで担保済み</span><br><span class="line"><span class="bullet">-</span> 既存テストがカバー済み</span><br></pre></td></tr></table></figure>

</details>

<h3 id="運用フロー">運用フロー</h3><p>この markdown を用いて、ざっくり次のような流れで運用してみています。</p>
<ul>
<li>Claude Code の plan mode を使用して plan を立てる。plan を立てる段階で、「どんな振る舞いをテストするつもりか&#x2F;どんな振る舞いを確認しないと決めたか」を先に書き出させる</li>
<li>その一覧を人間がチェックし、過不足がなさそうかを確認してから実装に進ませる</li>
<li>出来上がった plan を、他の AI（Codex など）にも <code>test-design.md</code> を物差しとして渡したうえでレビューさせる</li>
<li>Opus に実装させる</li>
<li>plan を実装させた後のレビュー時にも、テストが <code>test-design.md</code> に沿っているかを他 AI に確認してもらう</li>
</ul>
<h3 id="出力はどう変わったか">出力はどう変わったか</h3><h4 id="「振る舞い」の記述について">「振る舞い」の記述について</h4><p><code>test-design.md</code> を入れる前と後で、AI が出してくる「確認すべき振る舞い」の説明は、自分の体感ではだいぶ読みやすくなりました。いくつか例を挙げます。</p>
<ul>
<li>実装用語ではなく、外部から見た振る舞いで書くようになりました。</li>
</ul>
<figure class="highlight text"><input type="checkbox" id="code-wrap-g2ilu2-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-g2ilu2-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">Before（実装・テスト用語で書いた、AI っぽい書き方）:</span><br><span class="line">  機能固有 logic (*HogeError wrap + ctx 連携) を representative cases で pin</span><br><span class="line"></span><br><span class="line">After（利用者から見た結果で言い切る書き方）:</span><br><span class="line">  外部コマンドが異常終了したら、その終了コードを保ったままエラーを返す。</span><br><span class="line">  実行中に呼び出し側がキャンセルしたら、途中でも速やかに止まる。</span><br></pre></td></tr></table></figure>

<ul>
<li>何をどう確認するのかを具体的に書くようになりました。</li>
</ul>
<figure class="highlight text"><input type="checkbox" id="code-wrap-g2ilu2-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-g2ilu2-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">Before:</span><br><span class="line">  sampleDepths の boundary を網羅</span><br><span class="line"></span><br><span class="line">After:</span><br><span class="line">  総件数が 0 / ちょうど 1 ページ分 / 大量、のどれであっても、最も浅いページと最も深いページを必ず含むようにする。</span><br></pre></td></tr></table></figure>

<ul>
<li>体言止めせず、主語や条件を省略せずに動詞で言い切るようになりました。</li>
</ul>
<figure class="highlight text"><input type="checkbox" id="code-wrap-g2ilu2-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-g2ilu2-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">Before:</span><br><span class="line">  時間切れに達すると以降の深度を叩かず CutoffHit=true、親キャンセルでは残りを叩かず CutoffHit=false</span><br><span class="line"></span><br><span class="line">After:</span><br><span class="line">  時間切れ（cutoff）に達すると、以降のページは叩かず、時間切れだったと記録される。</span><br><span class="line">  強制キャンセル（Ctrl-C）では、残りのページを叩かず、時間切れではないと記録される。</span><br></pre></td></tr></table></figure>

<h4 id="テストコードの品質について">テストコードの品質について</h4><p>テストコードについても、読みやすさと品質がどちらも向上しました。<br><code>test-design.md</code> を入れる前は、以下のようにメソッド名の対応表をテストにそのまま書き写しただけの、「誰かが値を変えたら落ちる」だけのテストがありました。</p>
<figure class="highlight go"><input type="checkbox" id="code-wrap-g2ilu2-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-g2ilu2-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// 各画面が呼ぶべき API メソッド名を、対応表としてテストに固定する</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestCatalogMethods</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"> want := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>&#123;</span><br><span class="line">  <span class="string">&quot;user&quot;</span>:    <span class="string">&quot;getUsers&quot;</span>,</span><br><span class="line">  <span class="string">&quot;order&quot;</span>:   <span class="string">&quot;getOrders&quot;</span>,</span><br><span class="line">  <span class="string">&quot;product&quot;</span>: <span class="string">&quot;getProducts&quot;</span>,</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">for</span> screen, method := <span class="keyword">range</span> want &#123;</span><br><span class="line">  <span class="keyword">if</span> CatalogFor(screen).Method != method &#123;</span><br><span class="line">   t.Errorf(<span class="string">&quot;screen=%s method=%q, want %q&quot;</span>, screen, CatalogFor(screen).Method, method)</span><br><span class="line">  &#125;</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>test-design.md</code> を入れたことで、AI がこの写経テストを検出し、「識別子が重複していない」等の、壊れていたら必ずバグと言える性質だけを確認するテストに書き換えてくれました。</p>
<figure class="highlight go"><input type="checkbox" id="code-wrap-g2ilu2-6" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-g2ilu2-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment">// 壊れていたら必ずバグになる、カタログの構造的な性質だけを確認する</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestCatalogWellFormed</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"> seen := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">bool</span>&#123;&#125;</span><br><span class="line"> <span class="keyword">for</span> _, e := <span class="keyword">range</span> endpoints &#123;</span><br><span class="line">  <span class="keyword">if</span> seen[e.Slug] &#123;</span><br><span class="line">   t.Errorf(<span class="string">&quot;slug が重複している: %q&quot;</span>, e.Slug) <span class="comment">// 重複すると画面の選択が壊れる</span></span><br><span class="line">  &#125;</span><br><span class="line">  seen[e.Slug] = <span class="literal">true</span></span><br><span class="line">  <span class="keyword">if</span> e.Method == <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">   t.Errorf(<span class="string">&quot;%s: メソッド名が空&quot;</span>, e.Slug)</span><br><span class="line">  &#125;</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="comment">// メソッド名そのものは照合しない。</span></span><br><span class="line"> <span class="comment">// それはバックエンドとの契約で、結合テストでしか真偽を確かめられないため。</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="その他、この運用を試してみて感じたこと">その他、この運用を試してみて感じたこと</h3><p>「確認すべき振る舞い」を読んでいると、「これは自分が思っていたのと違う」と気づけるケースがそこそこありました。実装に入る前に認識のズレを潰せるのは嬉しいです。</p>
<p>また、「確認しないと決めた振る舞い」も一緒に出してもらうことで、「漏れているのではなく、意図して外したのだ」と分かり、一定の安心感が生まれました。</p>
<p>とはいえ、振る舞いの分量は依然として多く、確認するのはそれなりに大変です。「そもそも毎回律儀に全部の振る舞いを確認すべきなのか、ある程度は AI に任せてしまっても良いのではないか」というのは、別の論点としてあるなと思っています。</p>
<h2 id="おわりに">おわりに</h2><p>AI を使えば、アプリケーションコードもテストコードも簡単に書けるようになりました。テストまで含めて勝手に生成される状況になると、つい楽な方に流れて、中身を確認しないまま取り込んでしまいがちです。</p>
<p>ですが、ソフトウェアに品質が求められる限り、その品質に責任を持つのは人間です。あるべき振る舞いを決める役割は、生成のスピードがどれだけ上がっても人間に残り続けると思っています。この役割が残る以上、「このソフトウェアはどう振る舞うべきで、そのためにどんなテストを書くべきか」を考えたり確かめたりする部分からは逃げずに、考えるべきなのではないでしょうか。</p>
<p>AI 時代におけるテスト品質の保ち方について、「自分のチームではこうしている」「ここはこう思う」といった意見があれば、ぜひコメント等いただけますと幸いです。</p>
]]></content>
    <summary type="html">AIが実装もテストも自律的に書くようになり、生成物を全部レビューするのは現実的でなくなりました。品質を保つ手段として単体テストが一層重要になる中で、「あるべき振る舞い」を人間が握り続けるために工夫している運用を紹介します。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="Claude" scheme="https://future-architect.github.io/tags/Claude/"/>
    <category term="インデックス" scheme="https://future-architect.github.io/tags/%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9/"/>
    <category term="テスト" scheme="https://future-architect.github.io/tags/%E3%83%86%E3%82%B9%E3%83%88/"/>
  </entry>
  <entry>
    <title>AIネイティブ時代の設計書を考える</title>
    <link href="https://future-architect.github.io/articles/20260609a/"/>
    <id>https://future-architect.github.io/articles/20260609a/</id>
    <published>2026-06-08T15:00:00.000Z</published>
    <updated>2026-06-08T15:00:00.000Z</updated>
    <author><name>澁川喜規</name></author>
    <content type="html"><![CDATA[<p>Goならわかるシステムプログラミングなどのウェブ連載や書籍化でお世話になりまくりの鹿野さんのブログ記事が話題となっています。</p>
<ul>
<li>golden-luckyの日記: AI時代のコンピューター技術書</li>
</ul>
<p>これを読んで、今までモヤモヤ思っていたことに光明が射してきたので、ちょっと実験してみました。</p>
<h2 id="AI開発のボトルネックは今どこか">AI開発のボトルネックは今どこか</h2><p>1年ほど前はあまり長時間モデルが働き続けることもなく、生成AIの月あたりのクオータを使い切るということはあまりありませんでした。そのため、AI開発のボトルネックは主に、人間とAIのコミュニケーション部分にあると思っていました。</p>
<p>ここ1-2か月ぐらい、Planモードで走り続けて1回の消費が大きくなったせいか、5時間枠を使い切ることが増えてきました。ボトルネックはAPIリクエストのクレジット数になってきたのを感じます。それに追い打ちをかけてGitHub Copilotのモデルの乗数の変更。Claude Opus 4.6はx3からx27へと9倍になりますし、課金体系も、リクエスト数ではなくて、API呼び出し数に変わるということで、こちらも深刻です。</p>
<p>そうなると、トークン数の節約が大切となります。</p>
<p>例えばコンテキスト数の節約はトークン数の節約になります。コンテキスト節約は推論コスト削減になり、APIコスト削減になります。APIコストも削減され、レイテンシーも改善されます。いいことばっかりですね。</p>
<p>今まではコミュニケーション速度がボトルネックだったので得意な日本語でガンガン情報をインプットするのが最適かなと思っていましたが、コンテキスト数を節約する場合は日本語よりも英語の方が良いと言われています。ということは英語で読み書きするのが良いということですね。</p>
<img fetchpriority="high" src="/images/2026/20260609a/スクリーンショット_2026-05-19_20.49.30.png" alt="スクリーンショット_2026-05-19_20.49.30.png" width="471" height="310">

<p>まあ無理に英語を使うことで会話のスピードが1&#x2F;10になったら良くないわけですし、そもそも技術的に難易度が高すぎて言葉で表現できない！となったら元も子もなかったりはしますね。1週間のクオータを使い切って待つのと、人間が情報を伝えるのと、どちらの待ち時間が長いか次第かと。</p>
<h2 id="大規模開発ではドキュメントが大事だが・・・">大規模開発ではドキュメントが大事だが・・・</h2><p>趣味開発だと、自分の頭にあるものをどんどん伝えていけばいいのですが、仕事の開発だと作るものを決定するのは開発者ではなくユーザーだったりステークホルダーだったりが多いかと思います。こちらの場合はドキュメントをしっかり書いて、要件を固めてすテークホルダと合意して実装、みたいな方式が基本的に採用される流れですね。AmazonのAI-DLCもそうですね。ドキュメントをしっかり書くことで、手戻りを減らしたり、複数のエージェントを活用したりとか。Codexには&#x2F;goalというexperimentalな機能も追加されました。</p>
<p>で、AI-DLCをやってみたのですが、どうもドキュメント作成が重い。AI-DLCのサンプル実装というワークフローをCopilotに入れてみて試しましたが、隅々までAIから質疑応答を受けてそれに回答してそれでドキュメントを更新し・・・と。1サイクルがかなり時間がかかる。また、「後回しでもいいのに」というような細かいところまで聞かれるのでフロントローディングで設計を考える必要があります。結果として2サイクル回しましたが、それで1月のCopilotのProのクレジットの6割ぐらい消費し、3000行ぐらいのコードと、12000行のドキュメントができました。趣味開発でもウォーターフォールを強制時短適用しているような、なんかすごい疲れたな、という感想です。</p>
<p>そもそも、AI-DLCの基本的に大量のドキュメントが生成されて、それを人間がチェックをして・・・という流れは「AIは解読が得意」という鹿野さんの記事とは真逆です。AIが大量に生成したドキュメントを人間が確認し・・・これは苦痛です。もちろん、AI-DLCのワークフローはサンプルのフローであって、これそのものが将来の開発の姿というわけではないと思いますが、何かしらのアップデートが今後必要となるのは間違いないでしょう。</p>
<h2 id="AI時代のドキュメント">AI時代のドキュメント</h2><p>鹿野さんの考える未来の読書では、技術書は人間が読むものではなく、AIの中にあり、人間はAIというフィルタを通じて情報を得れば良いということであり、ドキュメントの苦痛を減らす方向性としても魅力的に感じます。今までの延長線上では、たとえMarkdownになったとしても、ドキュメントをの整合性をとりつつ、全体構成を考えるのは人間のタスクになります。しかし、人間が読みやすい構造ではなく、AIが理解しやすい構造としてドキュメントを表現してあげて、AIを通じて必要な情報を得れば良い、という方針の方に未来がありそうと感じます。</p>
<h3 id="構成の改善">構成の改善</h3><p>今までの設計書のまま、MarkdownにしてAIが読みやすいように、というのを今まで考えてツールを作りかけてたこともありますが、単純にMarkdown化しようとすると表現力が劣るという問題がでてきます。</p>
<p>そもそも人間が仕様書を書く場合は、あらかじめ「○○○詳細設計書」などの形式を決め、それを穴埋めしていくことで設計の抜け漏れがないか、またはレビューのしやすさを維持して、全体の生産性が落ちないようにします。しかし、そもそもAIが書いて読むならそのような形式化は不要となります。AI自身が書いてAI自身が検証できるようになれば、このような今までに近い仕様で書こうという必要性は減りそうです。</p>
<h3 id="読み込みの改善">読み込みの改善</h3><p>AIフレンドリーなプロンプトというと、MCPサーバーよりもコンテキスト消費が少ないというskillsがあります。必要な情報を必要なタイミングで読み込むことで、常駐コンテキストを大幅に減らしますし、編集などは専用のスクリプトを使って決定性の高い方法で行えるようにしています。</p>
<p>AIでMarkdownでドキュメントを作成する場合も、SkillsのSKILL.mdで行われているように、リンクで別のファイルに飛ばすような構成にすることで一度に消費するコンテキストを抑えられるということを聞きました(@ryushi曰く)つまり、ドキュメントはAIが消費しやすいように小さくしていけば良いと。AI時代よりも前に、意味を表現しようとする「セマンティックウェブ」というものがありました。ウェブの父であるティム・バーナーズ＝リーが提唱したもので、XMLを使って意味の定義を作り込む必要があります。</p>
<ul>
<li>立命館大学: 知識マネジメント論&#x2F;第5週　知識資源管理技術の基礎（４）</li>
</ul>
<p>これを人力でボトムアップで作り上げて、意味のある知識構造を作り出すのは相当至難の業ですが、今のAI時代であれば大きい情報は小さく分割して、また適切なリンクテキストを張り巡らせるという感じでやればよさそうです。リンク切れなどを起こさないように自動チェックをスクリプトで行えば良さそうです。</p>
<h3 id="完全性を後回しにできる要件">完全性を後回しにできる要件</h3><p>AI-DLCワークフローの場合は、おそらく与えられた要件から完全なドキュメントを最初に作成してレビューを受けようとしているように見えます。今回作ったのは「未完了」を許容するようにしています。アジャイル開発の「スループットの高さ」は要件定義を「次の1イテレーション分回すのに十分な量だけ行う」ことにあります。優先度が低いものは後回し。AIでドキュメントのワークフローを作ると「要件が満たせるまでループする」という指示を入れようとする記事などをよく見る気がしますが、人間側がまだ細かいところまで把握できていないのにAIだけに完全を求めると、やたらと待ち時間が長くなるし、投機的に決めたことの修正が必要になった時のトークン消費もかなり多くなると思われます。</p>
<img src="/images/2026/20260609a/バックログ.png" alt="バックログ" width="768" height="432" loading="lazy">

<p>https://do-scrum.com/pbl: 【完全攻略】スクラム開発におけるプロダクトバックログの作り方より引用</p>
<br>

<p>システム全体を見て、先に開発が必要で要件をまとめない、これ以外の部分はまだ手をつけなくていい、みたいなところは、結構ファジーな直感に頼るところが多いのかな、と思います。まだまだアーキテクトスキルを持ってアジャイルに優先度別に考えられるような人間がやった方が圧倒的に効率は高いと感じます。</p>
<p>まだ定義が完了していないものはバックログとしてまとめるようにもしています（もうちょっと表現とかワークフローは工夫できそう）</p>
<h2 id="仕様書をAIチューンするskill">仕様書をAIチューンするskill</h2><p>さっそくこのようなskillを作ってみました。</p>
<ul>
<li>Spec Compiler Skill</li>
</ul>
<p>ドキュメント種別を毎回ゼロから再発明させるのは結果が安定しなくなりそうなので、デフォルトのドキュメントの種別を定義して、テンプレートから作れるようにしています。ただ、テンプレートは今までのものよりかはだいぶ少ない内容としています。それに従って文章を作っていくのですが、1つ1つの文書は小さく、そして文書の間にリンクをいっぱい作成するようなskillとなっています。また、スクリプトでビルドするとインデックスなどはJSONL形式で書き出します。DuckDBで扱いやすいように。人間が作る時は一覧表も作りつつ詳細を作ったりしますが、詳細だけ作りインデックスや文書間リンクはAIが自動で貼っていく、ローカルWikiみたいな感じとなっています。ちなみに本文はコンテキストを節約するために英語ですが、末尾に人間向けの日本語向けの説明も軽くいれる形式としました。</p>
<img src="/images/2026/20260609a/スクリーンショット_2026-05-27_22.28.59.png" alt="スクリーンショット_2026-05-27_22.28.59.png" width="1113" height="1128" loading="lazy">

<p>サンプルのデータモデルのドキュメントからは、データ構造からそれを編集する画面のリンク、シリアライズ形式、それを使うプロセスの情報などにリンクが貼られています。これは下の方1&#x2F;3ぐらいで、上の方には概要、フィールド一覧、ルールや制約、みたいなのがざっと書かれています。</p>
<p>このような文書管理はRAGを使う手もありますが、結局類似文章が見つかったらそれから見つかった全件メモリに入れて分析するような動きになるかと思います。ヒットするものの関係がないかもしれないものも一度コンテキストに格納して・・・となると効率が悪い。RAGの検索の精度が悪いと必要な情報がヒットしない。リンクのラベルから必要性がわかっているものを順次辿る方が早いだろう、という目論見です。</p>
<h2 id="AI用ドキュメントをAIを使って読む">AI用ドキュメントをAIを使って読む</h2><p>ドキュメント形式はプロジェクトの特性によって変更できるようにしています。そんな感じで人間に合わせた目次分けとかにはなっていませんので、どこに何が書いてあるか人間が扱うのはやや扱いにくいと言えます。鹿野さん方式で、AIに読み込ませ、何かあればAIにお願いすることになります。結構軽快です。編集を依頼してもすぐに返ってきます。ドキュメントはコンテキストを節約するために英語ですが、日本語で聞いたり修正の依頼を投げても大丈夫です。横断的に必要な情報を集めてサマリーしてくれます。</p>
<ul>
<li>仕様の確認: AIに聞く</li>
<li>ドキュメントの修正: AIに聞く</li>
<li>要件定義の足りない部分: AIに聞く</li>
</ul>
<p>今回考えてみて何本かやってみて、この仕組みはかなりうまくいきそうです。ある程度AIと一緒にドキュメントを作り、あとはゴール機能を使って一気に実装完了まで依頼する、という流れでコードを書いてもらうというのを行いましたが、実装を開始したら止まることなく最後まで毎回作ってくれます。</p>
<p>内製開発では不要だと思いますが、ウォーターフォールみたいな、フェーズでの設計品質のチェックゲート的なものも、おそらくAIとやりとりしながら抜けがないか確認していけば良さそうな気がします。あるいは、この形式のドキュメントから従来方式のドキュメントを生成させるとかですかね。構造化されたソースコードが生成できるので、ドキュメントも出せるでしょう。</p>
<h2 id="まとめ">まとめ</h2><p>思いつきでぱっとつくったスキルでしたが、思った以上にうまくいきました。やはり、AIが大量に作ったものを人間が読まされるのはストレスとなりますし、そこがボトルネックとなります。AIは読むのが得意、という能力を活かさないのはもったいないですね。</p>
<p>ドキュメントの形式一覧は「デフォルト設定」で、プロジェクトごとにカスタマイズできるような構成にはしています。今回はあまり大きくないコードだったのでそれを使うことはなかったのですが、より大きいものでも触ってみて、必要に応じてドキュメントタイプを追加し、1つ1つのドキュメントを小さく保つ、というのは引き続き試してみたいですね。</p>
<h3 id="今後やりたいこと">今後やりたいこと</h3><p>まだまだスキルが正しく期待通りの動きをしているかは精査が必要です。CodexはOpenTelemetryでアクティビティが取得できるのですが、おそらくそのような仕組みを使って、モニタリングを行い、期待されたスクリプトが呼ばれているのか、呼ばれていないのか、呼ばれていないなら何が原因かなどの追求が必要な気がします。skillの仕様の評価の仕方もありますが、skill creatorスキルと組み合わせていかに効率よく検証して改善していくかは、まだ課題かなと思いました。</p>
<p>あとはテストの連携もしたいですね。各ドキュメントごとにテスト観点などを書いたドキュメントのリンクを貼り、テストの仕様をきちんと書けるようにしてそれを元にコード生成ができたりすると良さそうです。テストコードからはそれが定義されているドキュメントがリンクされている、ということはあんまりなく情報が一方通行になりがちなので、そのあたりもAI中心のドキュメントだと利便性を上げられるんじゃないかと思います。</p>
]]></content>
    <summary type="html">Goならわかるシステムプログラミングなどのウェブ連載や書籍化でお世話になりまくりの鹿野さんのブログ記事が話題となっています。これを読んで、今までモヤモヤ思っていたことに光明が射してきたので、ちょっと実験してみました。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="Markdown" scheme="https://future-architect.github.io/tags/Markdown/"/>
    <category term="ドキュメント" scheme="https://future-architect.github.io/tags/%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88/"/>
    <category term="設計" scheme="https://future-architect.github.io/tags/%E8%A8%AD%E8%A8%88/"/>
  </entry>
  <entry>
    <title>AI駆動開発は「ツール導入」ではなく「協働チーム設計」である —— 現場で運用して見えてきたこと</title>
    <link href="https://future-architect.github.io/articles/20260602a/"/>
    <id>https://future-architect.github.io/articles/20260602a/</id>
    <published>2026-06-01T15:00:00.000Z</published>
    <updated>2026-06-01T15:00:00.000Z</updated>
    <author><name>平川裕蔵</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260602a/top.jpg" alt="" width="1024" height="572">

<div class="note-container note-info"><span class="note-icon"></span><div>

<p>本稿は、AI駆動開発をエンジニアリング組織に導入・運用してきた経験を踏まえ、「AI Agent をどう”チームメンバー”として扱うか」という設計論を、現場目線で整理したものです。</p>
<p>提案書でも、ベンダー資料でもなく、<strong>実際に手を動かしているエンジニアが、同じく手を動かしているエンジニアに向けて書いた考察</strong>として読んでいただければと思います。</p>
</div></div>

<h2 id="はじめに-なぜ今、”設計論”が必要なのか">はじめに:なぜ今、”設計論”が必要なのか</h2><p>ここ1〜2年、現場では Cursor &#x2F; Claude Code &#x2F; Devin &#x2F; 各種 Copilot 系の導入が一気に進みました。手元での生産性は確かに上がりました。一方で、こんな声をよく聞きます。</p>
<ul>
<li>「AI が出してくるコードのレビュー負荷で、結局トータルの工数は変わっていない」</li>
<li>「便利だが、属人化している。チームでどう運用するかが見えない」</li>
<li>「PoC は動いたが、本番ワークフローに組み込む段で詰まった」</li>
<li>「責任の所在が曖昧。AI が壊したのか、人間が見落としたのかが切り分けられない」</li>
</ul>
<p>これらはツールの問題ではなく、<strong>チーム運用の設計が欠けている</strong>ことの帰結だと感じています。</p>
<p>AI 駆動開発の本質は、「速いコード補完を手に入れる」ことではありません。<strong>AI Agent をチームメンバーとして編成し、人間とは違う性質を持つメンバーを前提にエンジニアリングプロセスを再設計すること</strong>です。</p>
<p>本稿では、その設計論を 5 つの観点に分けて整理します。</p>
<h2 id="観点-1-プロセスの骨格は、対象の複雑さに応じて変える">観点 1:プロセスの骨格は、対象の複雑さに応じて変える</h2><p>AI 駆動開発の議論には、相反する主張が混在しています。一方には “vibe coding” に代表される「設計と実装の境界を溶かす」論調があり、他方には「従来プロセスをそのまま AI で短縮するだけ」という慎重論があります。</p>
<p>この問いには、私自身の実体験から答えたいと思います。<strong>結論を先に書くと、対象の複雑さによってプロセスの骨格を変えるべき</strong>、というのが私の立場です。</p>
<h3 id="私の実体験-小さい道具づくりに、ウォーターフォールはいらなかった">私の実体験:小さい道具づくりに、ウォーターフォールはいらなかった</h3><p>私は普段、設計開発を支えるアプリケーション、テスト工程の自動化スクリプト、エビデンス収集スクリプト、レポート生成スクリプトを、<strong>ほぼすべて AI に作ってもらっています</strong>。要件定義書も設計書も書きません。AI Agent に要件を伝え、ツールが出てきて、E2E で動作確認する。それで完結しています。</p>
<p>1 年半前は、出てきたコードを自分でレビューしていました。しかし GitHub Copilot や Claude Code を本格的に使い始めた頃から、コードレビューをやめました。代わりに、<strong>E2E テストの結果を AI にフィードバックするループだけで、ツールの作成・活用が成立しています</strong>。</p>
<p>この領域では、極論で言われている「設計工程レス」「コードレビュー不要」は、実際に機能します。<strong>対象が小さく、要件が私の頭の中に収まり、壊れても私だけが困る範囲</strong>だから成り立つわけです。</p>
<h3 id="しかし、プロジェクトで構築するシステムは話が違う">しかし、プロジェクトで構築するシステムは話が違う</h3><p>一方で、私がプロジェクトで構築するシステムは、サービス・モジュール・機能ごとに<strong>要件が大量にあり、それらが互いに依存して複雑な構造</strong>を成しています。</p>
<p>ここで「要件定義 → 設計 → 実装 → テスト」の各フェーズを工程管理せず、AI で一気に短縮しようとするのは、<strong>リスクが高すぎる</strong>と考えています。理由は単純で、</p>
<ul>
<li>AI が一度に正しく扱える文脈の量には限界がある</li>
<li>要件間の依存関係を、構成管理なしに頭の中だけで追える規模を超えている</li>
<li>壊れたときに困るのが「私だけ」ではなく、顧客と運用チーム全員になる</li>
<li>設計判断の根拠が残らないと、後の改修・障害対応で詰む</li>
</ul>
<p>つまり、<strong>AI の能力 × 対象の複雑さ × 失敗時の影響範囲</strong>で、プロセスの濃度を決める必要があります。これを無視して「世間が AI で速くなると言っているから」を理由にプロセスを薄めるのは、無謀でしかありません。</p>
<h3 id="だから、骨格は対象ごとに使い分ける">だから、骨格は対象ごとに使い分ける</h3><div class="scroll"><table>
<thead>
<tr>
<th>対象の例</th>
<th>骨格の濃度</th>
<th>私の運用</th>
</tr>
</thead>
<tbody><tr>
<td>個人ツール、自動化スクリプト、レポート生成</td>
<td>限りなく薄い</td>
<td>要件 → AI → E2E フィードバックループ</td>
</tr>
<tr>
<td>小規模アプリ、社内向け補助システム</td>
<td>軽い骨格</td>
<td>仕様だけは Planner が言語化し、構成管理する</td>
</tr>
<tr>
<td>プロジェクトで構築する業務システム</td>
<td>従来工程の骨格を維持</td>
<td>要件定義 → 設計 → 実装 → テスト、ただし各工程の “中身” を AI で再構築</td>
</tr>
</tbody></table></div>
<p>そして、プロジェクト規模のシステムにおいては、各工程の<strong>中身</strong>を次のように再構築します。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>工程</th>
<th>従来の中身</th>
<th>AI 駆動開発での中身</th>
</tr>
</thead>
<tbody><tr>
<td>要件定義</td>
<td>人間が対話で整理</td>
<td>対話は人間、構造化・抜け漏れチェックは AI</td>
</tr>
<tr>
<td>設計</td>
<td>設計書を人間が書く</td>
<td>設計書と機械可読版を並走させる(双方向同期)</td>
</tr>
<tr>
<td>実装</td>
<td>設計書 → コード</td>
<td>テストファーストが現実解になる(テスト先行生成 → 実装 → 検証)</td>
</tr>
<tr>
<td>テスト</td>
<td>物量制約で限定的</td>
<td>AI Component が大規模に検証カバレッジを担保</td>
</tr>
<tr>
<td>レビュー</td>
<td>人 → 人の単層</td>
<td>Component → 人 → Harness の PDCA ループ</td>
</tr>
</tbody></table></div>
<p>工程名は同じでも、<strong>中で起きていることがまったく違う</strong>。これが現場の実感です。</p>
<p>骨格を維持する理由は、認知負荷だけではありません。<strong>規模と複雑さに対して、判断の根拠と変更の履歴を残す</strong>ためです。これがなければ、AI が出したものを誰も検証できず、改修も継承もできなくなります。</p>
<blockquote>
<p><strong>エンジニアへの実装的含意</strong>:「AI で速くなるからプロセスを省ける」ではなく、「<strong>対象の複雑さに対して、プロセスの濃度を意識的に選ぶ</strong>」。個人ツールでウォーターフォールを敷くのは滑稽だし、業務システムで構成管理なしに進めるのは無謀です。判断軸は「壊れたら誰が困るか」と「複雑さが頭の中に収まるか」の 2 つで十分です。</p>
</blockquote>
<h2 id="観点-2-AI-Agent-を「ツール」ではなく「マイクロサービス」として扱う">観点 2:AI Agent を「ツール」ではなく「マイクロサービス」として扱う</h2><p>ここが、現場で最も意識を変えるべき点だと感じています。</p>
<blockquote>
<p><strong>用語の整理</strong>:本稿では、<strong>AI Agent</strong>(チームメンバー単位の単体 AI)と、<strong>AI Component</strong>(Agent &#x2F; Sub Agent &#x2F; Skill &#x2F; MCP &#x2F; Script を組み合わせた稼働単位)を使い分けます。I&#x2F;O 契約は本来 Agent 粒度で結ばれるものですが、実運用では複数 Agent を束ねた Component に対しても同じモデルで適用されます。以降、<strong>個の振る舞いを論じるときは Agent</strong>、<strong>稼働させるまとまりを論じるときは Component</strong> と読み分けてください。</p>
</blockquote>
<p>多くのチームで AI は「便利な補完ツール」として扱われています。これは個人の生産性向上には機能しますが、<strong>チームとして再現性のある成果を出す体制にはなりません</strong>。</p>
<p>代わりに、私は AI Agent を <strong>マイクロサービス的に扱う</strong> ことを推奨します。具体的には、Agent を作るときに以下を明文化します。</p>
<figure class="highlight yaml"><input type="checkbox" id="code-wrap-ewrdk7-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-ewrdk7-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># 例: Agent 定義(I/O 契約)</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">code-reviewer-agent</span></span><br><span class="line"><span class="attr">purpose:</span> <span class="string">PR</span> <span class="string">のコード品質を機械的にレビューする</span></span><br><span class="line"></span><br><span class="line"><span class="attr">functional:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">静的解析では見つからない設計上の問題を指摘する</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">修正提案を</span> <span class="string">unified</span> <span class="string">diff</span> <span class="string">形式で返す</span></span><br><span class="line"></span><br><span class="line"><span class="attr">non_functional:</span></span><br><span class="line">  <span class="attr">latency:</span> <span class="string">p95</span> <span class="string">&lt;</span> <span class="string">30s</span></span><br><span class="line">  <span class="attr">cost:</span> <span class="number">1</span> <span class="string">PR</span> <span class="string">あたり</span> <span class="string">$0.20</span> <span class="string">以内</span></span><br><span class="line">  <span class="attr">reliability:</span> <span class="string">月次の誤検知率</span> <span class="string">&lt;</span> <span class="number">15</span><span class="string">%</span></span><br><span class="line"></span><br><span class="line"><span class="attr">input:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">PR</span> <span class="string">diff</span> <span class="string">(unified</span> <span class="string">diff</span> <span class="string">format)</span></span><br><span class="line">  <span class="attr">context:</span> <span class="string">PR</span> <span class="string">description,</span> <span class="string">related</span> <span class="string">files</span></span><br><span class="line"></span><br><span class="line"><span class="attr">output:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">Markdown</span> <span class="string">report</span></span><br><span class="line">  <span class="attr">schema:</span> &#123; <span class="attr">findings:</span> [&#123;<span class="string">severity</span>, <span class="string">file</span>, <span class="string">line</span>, <span class="string">message</span>, <span class="string">suggestion</span>&#125;] &#125;</span><br><span class="line"></span><br><span class="line"><span class="attr">out_of_scope:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">セキュリティレビュー(別</span> <span class="string">Agent)</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">パフォーマンスレビュー(別</span> <span class="string">Agent)</span></span><br></pre></td></tr></table></figure>

<p>この <strong>I&#x2F;O 契約モデル</strong> が効くのは次の理由です。</p>
<ol>
<li><strong>エンジニアリングプロセスのデザインに組み込める(最大のメリット)</strong>:契約という共通言語があるからこそ、Agent を工程の中に “部品” として配置でき、入出力を起点にプロセス側を設計し直せる。プロセスに組み込めないものは、いずれ属人運用に逆戻りする</li>
<li><strong>責任分界が明確になる</strong>:Agent が何をやって、何をやらないかが言語化される</li>
<li><strong>改善サイクルが回る</strong>:契約に対する充足率を測れるので、Agent の改善が定量的になる</li>
<li><strong>置き換えが効く</strong>:契約が同じなら、中身のモデル(GPT-x → Claude-x → ローカル LLM)を差し替えられる</li>
<li><strong>属人化しない</strong>:誰が呼んでも同じ振る舞いが期待できる</li>
</ol>
<p>特に 1 つ目が私にとっての本丸です。AI Agent を “便利な相棒” 止まりにせず、<strong>エンジニアリングプロセスの構成要素として設計できるかどうか</strong>で、得られるパフォーマンスが <strong>個人レベル</strong> に留まるか、<strong>組織レベル</strong> にまで広がるかが決まります。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>パフォーマンスの広がり</th>
<th>状態</th>
<th>典型像</th>
</tr>
</thead>
<tbody><tr>
<td><strong>個人レベル</strong></td>
<td>AI が個々人の生産性を底上げする</td>
<td>各自が思い思いに Copilot を使いこなしている状態</td>
</tr>
<tr>
<td><strong>組織レベル</strong></td>
<td>AI Agent が工程の部品として再利用され、組織全体のスループットを底上げする</td>
<td>契約された Agent が、誰のどの工程からでも同じ品質で呼び出される状態</td>
</tr>
</tbody></table></div>
<p>両者の差は、AI の性能ではなく、<strong>契約という共通言語をプロセスに編み込めているかどうか</strong>で決まります。</p>
<blockquote>
<p><strong>エンジニアへの実装的含意</strong>:Agent ごとに README.md(または agent.yaml)を必須化する。<code>purpose</code>, <code>non_functional</code>, <code>out_of_scope</code> の 3 つは特に重要。「これは何をしないか」を書かない Agent は、必ず期待値ズレを起こします。</p>
</blockquote>
<h2 id="観点-3-検証は-“層”-ではなく-“PDCA-サイクル”-として組む">観点 3:検証は “層” ではなく “PDCA サイクル” として組む</h2><p>AI は確率的に間違えます。これは仕様です。</p>
<p>問題は、<strong>間違いをどう検知し、どう次の改善に回すか</strong>です。人間レビューに全量を流せば速度が犠牲になり、ノーチェックで流せば信頼が犠牲になる。1回限りの “検証” ではこのジレンマは解けません。<strong>検証を改善ループに組み込む</strong>ことで、はじめて構造的に解けます。</p>
<p>私が運用しているのは、AI Component を中心に据えた PDCA サイクルです。</p>
<pre class="mermaid" data-mermaid="de3ccce6c90e61ef830143f840ebbc4d409e68e30e0a3df47443d9a890924ce6">stateDiagram-v2
    direction LR
    [*] --> Plan

    Plan: 🧩 Plan<br/>AI Component を作成<br/>(Agent / Skill / Script を組み合わせて設計)
    Do: ⚙️ Do<br/>AI Component を利用<br/>(実装・テスト・レビュー等の現場運用)
    Check: 🔍 Check<br/>人間レビューでチェック<br/>(抜打ち + クリティカルポイント)
    Action: 🛠 Action<br/>Harness が AI Component を改善<br/>(契約・指示・構成の更新)

    Plan --> Do : デプロイ
    Do --> Check : 成果物
    Check --> Action : 是正フィードバック
    Action --> Plan : 改版した Component

    classDef plan fill:#dbeafe,stroke:#2563eb,color:#1f2937
    classDef do fill:#dcfce7,stroke:#16a34a,color:#1f2937
    classDef check fill:#fef3c7,stroke:#d97706,color:#1f2937
    classDef action fill:#fde2e8,stroke:#db2777,color:#1f2937

    class Plan plan
    class Do do
    class Check check
    class Action action</pre>

<p>各フェーズの中身を補足します。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>フェーズ</th>
<th>主体</th>
<th>中身</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Plan</strong></td>
<td>Harness + Planner</td>
<td>要件を仕様に落とし、<strong>AI Component</strong>(Sub Agent &#x2F; Skill &#x2F; Script を組み合わせた構成体)を設計する</td>
</tr>
<tr>
<td><strong>Do</strong></td>
<td>AI Component</td>
<td>現場の実装・テスト・レビュー等で稼働し、成果物を生み出す</td>
</tr>
<tr>
<td><strong>Check</strong></td>
<td>人間</td>
<td>全件レビューではなく、抜打ち検査と Hard Gate に絞って判定</td>
</tr>
<tr>
<td><strong>Action</strong></td>
<td>Harness</td>
<td>Check のフィードバックを受け、Component の契約・指示・構成を改版する</td>
</tr>
</tbody></table></div>
<p>Do の主体は AI Component です。中身は Agent &#x2F; Sub Agent &#x2F; Skill &#x2F; Script などで構成されており、観点 2 の I&#x2F;O 契約は Component 単位で結ばれます。</p>
<p>そして、検証は “層” ではなく <strong>“閉じたループ”</strong> として設計します。Check が Action に直結し、Action が次の Plan を駆動する。<strong>一度通したら終わり、ではなく、ズレを見つけたら必ず Component が改版される</strong>ことが、この設計の肝です。</p>
<h3 id="Check-と-Action-を機能させる-3-つの工夫">Check と Action を機能させる 3 つの工夫</h3><p><strong>(1) Component 内部の検証側の独立性を、段階的に確保する</strong></p>
<p>Do フェーズでは、AI Component が内部に Reviewer 系の自動検証を持ちます。ここで Generator 側と Reviewer 側が同じ “考え方” のままだと、同じバイアスを同じように見逃します。だからといって「異なるベンダーのモデルを混ぜろ」は短絡で、コストばかり増えがちです。独立性は<strong>二択ではなく段階</strong>で考えるのが現実的です。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>レベル</th>
<th>構成</th>
<th>排除できるバイアス</th>
</tr>
</thead>
<tbody><tr>
<td>L0</td>
<td>同一セッション内で自己レビュー</td>
<td>ほぼ何も(アンカリング残存)</td>
</tr>
<tr>
<td>L1</td>
<td>同一モデル + 別セッション</td>
<td>生成時の文脈アンカリング</td>
</tr>
<tr>
<td><strong>L2</strong></td>
<td><strong>同一モデル + 別セッション + 別 Agent(役割・指示が別設計)</strong></td>
<td><strong>アンカリング + 役割固定バイアス</strong></td>
</tr>
<tr>
<td>L3</td>
<td>異なるサイズのモデル</td>
<td>モデルサイズ起因の見落としパターン</td>
</tr>
<tr>
<td>L4</td>
<td>異なるベンダー &#x2F; モデル系統</td>
<td>自己選好バイアス、モデル固有の知識偏り</td>
</tr>
</tbody></table></div>
<p>私自身の運用は <strong>L2</strong> です。Generator Agent と Reviewer Agent を<strong>別 Agent として設計</strong>し、<strong>別セッション</strong>で動かしています。同一モデル(Claude 系)制約下で取りうる独立性を、ほぼ取り切っている構成と整理しています。</p>
<p>L3 &#x2F; L4 に踏み込むかどうかは、「<strong>何のバイアスを排除したいか</strong>」という狙いで決めるべきです。自己選好バイアスを潰したい、異なる知識ベースで補完したい、といった具体的な狙いがあれば L4 の運用負荷も元が取れます。一方、狙いが言語化できないまま「ベンダーを混ぜた方が良さそうだから」で構成すると、運用負荷だけが増えます。</p>
<p>ポイントは、<strong>検証の独立性は “あるかないか” ではなく “どこまで取るか”</strong> という設計判断だ、ということです。</p>
<p><strong>(2) Component の自動検証をキャリブレーションする(Action の中身)</strong></p>
<p>Component の自動検証も間違えます。誤検知(false positive)と見逃し(false negative)は必ず発生する前提です。Action フェーズで Harness が回しているのは、まさにこの自動検証ロジックの調整です。私はキャリブレーション自体は行いますが、<strong>月次のような定期活動にはしていません</strong>。代わりに、Check で「実際に問題だった」「実際は問題なかった」とのズレを<strong>発見した時点でその場の PDCA を回す</strong>運用にしています。</p>
<p>定期キャリブレーションは、運用規模が大きくなれば必要ですが、現状の Component 規模では「発見ドリブン」の方が反応が速く、過剰なメタ運用コストを抱え込まずに済んでいます。<strong>プロセスを先に整えるより、ズレを見つけたら即直す方が、現場の運用スタイルに馴染む</strong>というのが今の感触です。</p>
<p><strong>(3) Check は “重要ポイント” にだけ人間が介入する</strong></p>
<p>Component の自動検証を通過した成果物に対して、人間は全件ではなく<strong>抜打ちで検査</strong>します。代わりに、Hard Gate(後述)では必ず人間が判断する。<strong>人間の時間を、判断の難易度が高い領域に集中させる</strong>のがポイントです。</p>
<h3 id="なぜこのループが-“本丸”-なのか">なぜこのループが “本丸” なのか</h3><p>エンジニアなら誰もが経験していると思いますが、品質保証で最も苦心している領域は何でしょうか。</p>
<ul>
<li>単体テスト CI:規模が大きくなるほどメンテナンス負荷が比例して増え、維持に苦労する</li>
<li>レビュー全般(コード・ドキュメント・設計):関連ドキュメントが多くてすべてを見切れず、レビュアーに負荷が集中し、時間制約と品質維持のジレンマに苦心する</li>
</ul>
<p>これらは「人間の能力不足」ではなく<strong>物量と時間の制約</strong>で起きています。</p>
<p>AI Component が大量検証を担い、Harness が改善ループを回し、人間は判断の難所に集中する。この PDCA ループ全体で <strong>人間が物量で苦心していた品質保証を、現実的な負荷で回せる状態にする</strong>。これが AI 駆動開発の中心的な価値だと考えています。コードを速く書けることは、副次的な効果に過ぎません。</p>
<h2 id="観点-4-承認ゲートの強度配置-——-“失敗の許容”-を言語化する">観点 4:承認ゲートの強度配置 —— “失敗の許容” を言語化する</h2><p>すべての工程に強い人間承認を置けば速度が犠牲になり、すべて省けば信頼が犠牲になる。</p>
<p>このトレードオフを、<strong>ゲートの強度配置</strong>として明示的に設計します。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>ゲート種別</th>
<th>配置例</th>
<th>内容</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Hard Gate</strong>(必須承認)</td>
<td>要件確定、本番リリース、セキュリティ設計、スキーマ変更</td>
<td>人間が必ず明示承認。失敗を許容しない</td>
</tr>
<tr>
<td><strong>Soft Gate</strong>(サンプリング承認)</td>
<td>実装、テストケース、リファクタ</td>
<td>Component の自動検証を通過した後、人間が抜打ち検査</td>
</tr>
<tr>
<td><strong>Audit Gate</strong>(事後監査)</td>
<td>ドキュメント、コメント整備、軽微な修正</td>
<td>ログのみ残し、定期的に監査。事後修正を許容</td>
</tr>
</tbody></table></div>
<p>このゲート設計は、案件特性に合わせてカスタマイズします。金融系の本番マイグレーションと、社内ツールのコメント修正に、同じ強度のゲートを置く必要はありません。</p>
<blockquote>
<p><strong>エンジニアへの実装的含意</strong>:CI パイプラインや GitHub Actions の <code>required_reviewers</code> 設定、Branch Protection Rule に、このゲート強度を写像します。Hard Gate &#x3D; required review + status check 必須、Soft Gate &#x3D; auto-merge 可、Audit Gate &#x3D; post-merge audit job。<strong>ゲートはドキュメントではなく、リポジトリ設定として実体化</strong>することが重要です。</p>
</blockquote>
<h2 id="観点-5-AI-Agent-Manager-という職能を、組織にどう据えるか">観点 5:AI Agent Manager という職能を、組織にどう据えるか</h2><p>ここまで読んでいただいた方は、ある違和感を持つかもしれません。「これだけのことを、誰が設計・運用するのか?」</p>
<p>その答えとして <strong>AI Agent Manager</strong> という職能があります。2026年5月時点で、この呼称は AI 業界で一般用語になりつつあり、業界レポートや海外の求人にも登場するようになりました。ここで言いたいのは「新しい名前を作ろう」ではなく、<strong>この職能を組織のどこに、どう据えるかが、AI 駆動開発の成否を決める</strong>ということです。</p>
<p>従来のエンジニアリング組織には、この役割を正面から引き受ける職能がありませんでした。SRE が近い性格を持ちますが、対象が違います。SRE が “サービスの信頼性” を司るのに対し、AI Agent Manager は <strong>“Agent の契約と信頼性”</strong> を司ります。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>主体</th>
<th>責務</th>
</tr>
</thead>
<tbody><tr>
<td>AI Agent</td>
<td>契約に従って稼働する(責任主体ではない)</td>
</tr>
<tr>
<td><strong>AI Agent Manager</strong></td>
<td><strong>Agent の I&#x2F;O 契約定義、契約遵守の保証、ポートフォリオ管理、改善サイクル統括</strong></td>
</tr>
<tr>
<td>AI 運用者(エンジニア)</td>
<td>契約範囲内での成果物の評価、日々の利用 PDCA</td>
</tr>
<tr>
<td>プロジェクト責任者</td>
<td>プロジェクト全体の最終責任</td>
</tr>
</tbody></table></div>
<p>求められる素養は、SRE 的な信頼性思考、プロダクトマネージャ的な契約定義能力、そして AI の得意&#x2F;不得意を実装レベルで把握する技術理解、です。</p>
<p>正直に言うと、<strong>この職能を一人で担える人材は、今の市場にほぼいません</strong>。育てるしかない、というのが現実的な認識です。</p>
<h2 id="現場での実装例-AI-Harness-Agent-の運用">現場での実装例:AI Harness Agent の運用</h2><p>ここまで述べた 5 つの観点のうち、特に <strong>AI Component を中心に置く部分</strong> をどう実装しているか、私自身の運用例の一部としてご紹介します。エンジニアリングプロセス全体の AI 駆動化を網羅した実装例ではなく、その<strong>構成要素となる仕組み</strong>の話と捉えてください。</p>
<p>私は、<strong>AI Component(AI Agent &#x2F; Skill &#x2F; MCP &#x2F; Script 等)の作成・改善・運用を一気通貫で管理する “AI Harness Agent”</strong> を運用しています。”Component を作る Agent” であり、いわば AI 駆動開発のためのメタ Agent です。</p>
<h3 id="アーキテクチャ-Harness-Planner-Generator-Reviewer">アーキテクチャ:Harness + Planner &#x2F; Generator &#x2F; Reviewer</h3><p>Harness Agent 自身が、配下に 3 つの専門 Agent を束ねるチーム構造になっています。</p>
<pre class="mermaid" data-mermaid="d2a3d3a946be11c0726e797ddc5044021ddde4e2972d03cb046d9c45a0785371">flowchart TD
    User["👤 ユーザー(私)<br/>要件提示 / UAT / キャリブレーション"]
    Harness["🧭 Harness<br/>タスク制御・確認・承認の中枢"]
    Planner["📐 Planner<br/>仕様検討・合意"]
    Generator["⚙️ Generator<br/>実装"]
    Reviewer["🔍 Reviewer<br/>レビュー"]
    Component["📦 AI Component<br/>(要求事項 / 仕様 / 改版履歴を構成管理)"]

    User -- "① 要求" --> Harness
    Harness -- "② 仕様策定指示" --> Planner
    Planner <-. "③ 仕様合意(対話)" .-> User
    Planner -- "④ 仕様" --> Harness
    Harness -- "⑤ 実装指示" --> Generator
    Generator -- "⑥ 成果物" --> Harness
    Harness -- "⑦ レビュー指示" --> Reviewer
    Reviewer -- "⑧ レビュー結果" --> Harness
    Harness -- "⑨ AI Component を提供" --> User
    Harness -- "確定版を登録" --> Component

    classDef user fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#1f2937
    classDef harness fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1f2937
    classDef agent fill:#e0e7ff,stroke:#4f46e5,stroke-width:1px,color:#1f2937
    classDef artifact fill:#dcfce7,stroke:#16a34a,stroke-width:1px,color:#1f2937
    linkStyle default color:#1f2937

    class User user
    class Harness harness
    class Planner,Generator,Reviewer agent
    class Component artifact</pre>

<p>各 Agent の責務は次の通りです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>Agent</th>
<th>役割</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Harness</strong></td>
<td>ユーザー要求を解釈し、配下 Agent への作業分配・確認・承認を行う中枢。AI Component の最終提供者</td>
</tr>
<tr>
<td><strong>Planner</strong></td>
<td>要求を仕様に落とし、ユーザーと合意を取る</td>
</tr>
<tr>
<td><strong>Generator</strong></td>
<td>合意済み仕様に基づき AI Component を実装する</td>
</tr>
<tr>
<td><strong>Reviewer</strong></td>
<td>Generator の成果物をレビューする(PDCA の Do 内で自動検証を担う)</td>
</tr>
</tbody></table></div>
<p>そして、人間側の私の役割は 3 つ:<strong>要件を出すユーザー</strong>、<strong>UAT 担当</strong>、そして<strong>キャリブレーター</strong>です。</p>
<h3 id="Component-の構成管理-――-PDCA-を回すための前提">Component の構成管理 ―― PDCA を回すための前提</h3><p>Harness Agent が生成する各 AI Component には、<strong>要求事項・仕様・改版履歴</strong>などが構成管理として紐付いています。「動くもの」だけを成果物にしない、というのが設計上の意思決定です。</p>
<p>これがあるからこそ、後述のキャリブレーションや、運用チームへの引き渡しが成立します。仕様が残っていない Component は、改善の根拠を失います。</p>
<h3 id="ライフサイクル-発生-→-運用引き渡し-→-改善">ライフサイクル:発生 → 運用引き渡し → 改善</h3><p>Component のライフサイクルは次のように進みます。</p>
<ol>
<li><strong>発生</strong>:ユーザー要求が起点</li>
<li><strong>生成</strong>:Harness 経由で Planner → Generator → Reviewer が動く</li>
<li><strong>運用引き渡し</strong>:運用チームに渡され、日々の運用と改善が始まる</li>
<li><strong>改善サポート</strong>:私は引き続き、各社 AI 製品(Agent &#x2F; Model)の仕様変化に追随した<strong>難度の高いキャリブレーション</strong>を担当する</li>
<li><strong>運用終了</strong>:正直に言うと、ここは現状<strong>明示的なステータス管理をしていません</strong></li>
</ol>
<p>ライフサイクルの中で、<strong>発生と改善のサイクル</strong>に重みを置いた設計になっています。”終わらせ方” は、現時点では運用上の課題として残しています。</p>
<blockquote>
<p>補足:運用終了の管理を後回しにできているのは、Component の数がまだ “認知可能な規模” だからです。スケールが進めば、Deprecated&#x2F;Sunset 状態の明示が避けられなくなる、という認識は持っています。次に取り組むべき宿題です。</p>
</blockquote>
<h3 id="なぜ自作の-Harness-を選んだのか">なぜ自作の Harness を選んだのか</h3><p>Claude Code や OSS で良いのでは？ という疑問が湧くと思います。</p>
<p>実際、Claude Code それ自体がタスク単位の Harness としては非常に優秀です。Sub Agent・MCP・Skill・Script を駆使し、結果を確認しながら成果物を返す ―― これは本稿で述べた Harness の振る舞いそのものです。</p>
<p>加えて、AI Component の構成管理やプロセス制御を網羅的にサポートする OSS も存在します。代表例として、get-shit-done (現在はOpen GSD) のようなプロジェクトは、より多機能な実装を提供しています。</p>
<p>それでも私が自作 Harness を併用している理由は、カスタム性の容易さとアウトプットの再現性の高さを同時に握れる点にあります。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>観点</th>
<th>Claude Code 単体</th>
<th>多機能 OSS(例: Open GSD)</th>
<th>自作 Harness</th>
</tr>
</thead>
<tbody><tr>
<td>守備範囲</td>
<td>タスク単位の制御</td>
<td>PJ ワークフロー全体</td>
<td>Component ライフサイクル(軽量版)</td>
</tr>
<tr>
<td>構成管理</td>
<td>セッション単位</td>
<td>体系的に提供される</td>
<td>PJ 必要分だけ自前で持つ</td>
</tr>
<tr>
<td>導入コスト</td>
<td>ほぼゼロ</td>
<td>学習・運用形式の習得が先行</td>
<td>自作の手間はあるが PJ ごとに最適化</td>
</tr>
<tr>
<td>カスタム性</td>
<td>プロンプト依存</td>
<td>OSS のモデルに従う</td>
<td><strong>行動方針・Script 挙動を自分で定義</strong></td>
</tr>
<tr>
<td>アウトプット再現性</td>
<td>セッションごとに揺れる</td>
<td>高い(ただし OSS の枠内)</td>
<td><strong>行動方針と固定挙動の合わせ込みで安定</strong></td>
</tr>
</tbody></table></div>
<p>ポイントは、<strong>Agent &#x2F; Harness の行動方針</strong> と <strong>Script に任せる固定的な挙動</strong> を自分の言葉で定義できていることです。これがあるから、</p>
<ul>
<li>誰が呼んでも同じ使い方ができる</li>
<li>アウトプットの揺れが小さくなり、結果が安定する</li>
<li>それでいて、必要なときに自分でカスタム可能なまま</li>
</ul>
<p>という三立ちが取れています。AI ツールを場当たり的に使うと、自由度を上げるほどアウトプットが揺れがちですが、<strong>行動方針と固定挙動を分けて定義する</strong>ことで、揺れの原因を構造側で抑え、自由度はそのまま握れます。</p>
<p>残るコストは AI 製品のアップデート追随ですが、これは苦になっていません。むしろ、新しいモデル・新しい機能・精度改善を現場で触りながらキャッチアップできるおまけが付いてくるので、追随コストと学習機会がセットで返ってくる感覚です。</p>
<h3 id="失敗談-導入の時間が捻出できない、という壁">失敗談:導入の時間が捻出できない、という壁</h3><p>最後に、現場でリアルに刺さった失敗談を1つ。</p>
<p>Harness Agent を運用チームに引き渡すフェーズで、想定外に苦戦したのが<strong>導入そのもの</strong>でした。</p>
<p>前提として必要なのは、それほど複雑なことではありません:</p>
<ul>
<li>Claude Code が動く環境</li>
<li>MCP サーバの導入</li>
<li>社内プロキシの設定</li>
<li>必要な環境変数の定義</li>
</ul>
<p>エンジニアにとっては「半日もあれば終わる作業」のはずです。しかし現場で起きたのは、こういう声でした。</p>
<blockquote>
<p>「やることは分かっている。でも、その半日を捻出できない」</p>
</blockquote>
<p>レポジトリをクローンすれば、導入作業自体も Claude Code がほぼ自動でやってくれる ―― <strong>そういう状態を作ってあるのに、その入口に立つ時間すら取れない</strong>。皮肉なのは、<strong>忙しい現場ほど AI 活用でなんとかしたいと願っている</strong>ことです。導入できれば楽になることが見えている。でも、その「楽になる前の小さな段差」を越える時間が、最も忙しい人にこそ存在しない。</p>
<p>ここから学んだ教訓は2つです。</p>
<ol>
<li><strong>導入工数を技術的にゼロに近づけても、組織的なゼロにはならない</strong>。導入のためのアサインを、上長を巻き込んで明示的に確保する仕組みまでがセットで初めて “導入可能” になる。</li>
<li><strong>AI 駆動開発の最大のボトルネックは、技術ではなく “最初の半日” を組織として誰が確保するか</strong>である。これは Harness の設計では解けない。組織側の問題として、別の手段(経営層からのトップダウン、専任の導入支援担当の派遣など)で解決する必要があります。</li>
</ol>
<p>これは弊社が技術的に詰める部分ではなく、現場と一緒に時間を確保しに行く泥臭い動きが必要、という気づきでした。</p>
<h2 id="おわりに">おわりに</h2><p>AI 駆動開発の議論は、ともすればモデル選定やプロンプト技法に流れがちです。しかし、現場で本当に効くのは、<strong>人と AI の協働チームをどう設計するか</strong>という、チームデザインの話です。</p>
<p>最後にもう一度、5 つの観点を整理しておきます。</p>
<ol>
<li><strong>プロセスの骨格は対象の複雑さで使い分ける。中身を変える</strong></li>
<li><strong>AI Agent をマイクロサービスとして扱う(I&#x2F;O 契約モデル)</strong></li>
<li><strong>検証は “層” ではなく PDCA サイクルで組む(Plan &#x2F; Do &#x2F; Check &#x2F; Action)</strong></li>
<li><strong>承認ゲートの強度配置(Hard &#x2F; Soft &#x2F; Audit)</strong></li>
<li><strong>AI Agent Manager という職能を組織にどう据えるか</strong></li>
</ol>
<h3 id="これからについて">これからについて</h3><p>これからのエンジニアリングプロセスの発展として、私が広げていきたいのは次の2つのカバレッジです。</p>
<ul>
<li><strong>仕組みとして動作する AI</strong>:工程の中に組み込まれ、契約に従って自律的に動く Component 群</li>
<li><strong>ガイドとして動作するハーネス AI</strong>:人と Component の協働を中継し、判断の場面で適切な情報を差し出す上位 Agent</li>
</ul>
<p>プロセスを丸ごと差し替えるのではなく、<strong>既存プロセスの中で、この二つが担う範囲を着実に広げていく</strong>。これが、現場が壊れずに持続する変化の形だと感じています。</p>
<p>エンジニアリング組織で AI 導入を進めている方、PoC からプロダクション運用への移行で詰まっている方の参考になれば幸いです。</p>
]]></content>
    <summary type="html">本稿は、AI駆動開発をエンジニアリング組織に導入・運用してきた経験を踏まえ、「AI Agent をどうチームメンバーとして扱うか」という設計論を、現場目線で整理したものです。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="Claude" scheme="https://future-architect.github.io/tags/Claude/"/>
    <category term="ソフトウェア" scheme="https://future-architect.github.io/tags/%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2/"/>
  </entry>
  <entry>
    <title>AWS MCP Server がGAに - Claude Codeから検証: IAMガードレール設計</title>
    <link href="https://future-architect.github.io/articles/20260525a/"/>
    <id>https://future-architect.github.io/articles/20260525a/</id>
    <published>2026-05-24T15:00:00.000Z</published>
    <updated>2026-05-24T15:00:00.000Z</updated>
    <author><name>棚井龍之介</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260525a/thumbnail-v4.png" alt="thumbnail-v4.png" width="1200" height="630">

<p>こんにちは、棚井龍之介です。</p>
<p>最近、Claude Code を含めた「AI の利用環境」は、インターネット利用環境に次ぐ福利厚生のひとつだと実感する日々を送っています。</p>
<p>2026年5月6日、AWSが <strong>AWS MCP Server</strong> の一般提供（GA）を開始しました。AIコーディングエージェントから AWS サービスへ、IAM ベースのガードレール・CloudWatch メトリクス・CloudTrail ロギングを伴って安全にアクセスできる、マネージドな Model Context Protocol (MCP) エンドポイントです。</p>
<p>本記事では Claude Code から AWS MCP Server をセットアップし、提供される 11 ツール・IAM ガードレール・CloudTrail 監査までを実際に動かしながらまとめます。</p>
<h2 id="TL-DR">TL;DR</h2><ul>
<li><strong>接続</strong>: Claude Code → <code>mcp-proxy-for-aws</code>（uvx 経由のローカル stdio プロキシ）→ AWS マネージドエンドポイント</li>
<li><strong>ツール</strong>：公式アナウンスで強調される主要 4 つに加え、<code>recommend</code> &#x2F; <code>list_regions</code> &#x2F; <code>suggest_aws_commands</code> 等を含む <strong>全 11 個</strong>（知識系 6 + API 系 5）が提供される（公式リファレンス）</li>
<li><strong>認証</strong>: doc 系はサーバー側では認証不要（curl で実証可）だが、公式プロキシが全リクエストに SigV4 署名するため Claude Code 経由では AWS クレデンシャル必須</li>
<li><strong>IAM ガードレール 2 軸</strong>: context key（<code>aws:ViaAWSMCPService</code> &#x2F; <code>aws:CalledViaAWSMCP</code>）で「ヒトと AI エージェント経由」を分離する経路別 Deny に加え、専用ロール（<code>ReadOnlyAccess</code> + Inline Deny）への AssumeRole で実行コンテキスト全体を包括的にキャップする方式が組み合わせ可能</li>
<li><strong>CloudTrail 監査</strong>: MCP 経由は <code>userIdentity.invokedBy = aws-mcp.amazonaws.com</code> で識別できる。一方 <code>sourceIPAddress</code> が固定値になるため、<code>aws:SourceIp</code> 制限を併用する設計は要再検討</li>
</ul>
<h2 id="AWS-MCP-Server-とは">AWS MCP Server とは</h2><p>AWS MCP Server は、AWS 公式が提供する Agent Toolkit for AWS スイートの中核コンポーネントです。Agent Toolkit for AWS は3つの要素で構成されます。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>構成要素</th>
<th>役割</th>
</tr>
</thead>
<tbody><tr>
<td><strong>AWS MCP Server</strong></td>
<td>マネージドな MCP エンドポイント（ツール単位の認可：API系は SigV4 必須、doc系は SigV4 不要）</td>
</tr>
<tr>
<td><strong>Agent Skills</strong></td>
<td>AWS タスクごとに用意された手順・リファレンス（必要に応じてエージェントが取得）</td>
</tr>
<tr>
<td><strong>Agent Plugins</strong></td>
<td>複数エディタ（Claude Code、Codex、Cursor、Kiro 等）への一括導入パッケージ</td>
</tr>
</tbody></table></div>
<p>ローカル運用は不要で、AWS 側がスケーラビリティ・自動アップデート・監査ログを管理します。</p>
<h3 id="提供リージョン・料金・クォータ">提供リージョン・料金・クォータ</h3><ul>
<li>対応リージョンは、本記事執筆時点（2026年5月11日現在）で 米国東部（バージニア北部）と 欧州（フランクフルト）の2リージョンに限られる</li>
<li>AWS MCP Server 自体に追加料金は発生しない。エージェントが作成・利用した AWS リソースおよびデータ転送料金のみが課金される</li>
<li>公式クォータ（AWS MCP Server Quotas）：1 アカウント・1 リージョンあたり、リクエスト数は <strong>3 RPS（リクエスト&#x2F;秒）</strong>、同時接続は最大 27（引き上げ不可）、同時セッションは最大 180（引き上げ申請可）。<code>run_script</code> の作業ディレクトリ等の ephemeral storage は 8 時間で削除される</li>
</ul>
<h2 id="前提環境の確認">前提環境の確認</h2><p>セットアップ前に以下が揃っているか確認します。</p>
<figure class="highlight console"><input type="checkbox" id="code-wrap-own1w2-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-own1w2-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">uv（必須：mcp-proxy-for-aws を uvx で実行するため）</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">uv --version</span></span><br><span class="line">uv 0.11.11</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Claude Code</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">claude --version</span></span><br><span class="line">2.1.133 (Claude Code)</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">AWS CLI と認証情報</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">aws --version</span></span><br><span class="line">aws-cli/2.34.44 Python/3.14.4 Darwin/24.5.0</span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">aws sts get-caller-identity</span></span><br><span class="line">&#123;</span><br><span class="line">    &quot;UserId&quot;: &quot;ABCD****************&quot;,</span><br><span class="line">    &quot;Account&quot;: &quot;123***********&quot;,</span><br><span class="line">    &quot;Arn&quot;: &quot;arn:aws:iam::123***********:user/agent-user&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>uv が未インストールなら 公式インストールガイド を参照してください。</p>
<h2 id="Claude-Code-への-AWS-MCP-Server-セットアップ">Claude Code への AWS MCP Server セットアップ</h2><p>公式が示すセットアップ方法は2系統あります。</p>
<h3 id="A-プラグイン経由（対話セッション内）">A. プラグイン経由（対話セッション内）</h3><p>Claude Code を起動した状態で、対話プロンプトに以下を入力します。</p>
<figure class="highlight txt"><table><tr><td class="code"><pre><span class="line">/plugin marketplace add aws/agent-toolkit-for-aws</span><br><span class="line">/plugin install aws-core@agent-toolkit-for-aws</span><br><span class="line">/reload-plugins</span><br></pre></td></tr></table></figure>

<p><code>aws-core</code> プラグインは AWS MCP Server の登録に加え、CDK &#x2F; CloudFormation &#x2F; コンテナ &#x2F; ストレージ &#x2F; オブザーバビリティ向けの Agent Skills もまとめて入ります。</p>
<h3 id="B-claude-mcp-add-json-で-AWS-MCP-Server-だけ登録（CLI-から）">B. <code>claude mcp add-json</code> で AWS MCP Server だけ登録（CLI から）</h3><p>スクリプト化したい場合や、最小構成で試したい場合はこちらの方法を使います。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">claude mcp add-json aws-mcp --scope user <span class="string">&#x27;&#123;</span></span><br><span class="line"><span class="string">  &quot;command&quot;: &quot;uvx&quot;,</span></span><br><span class="line"><span class="string">  &quot;args&quot;: [</span></span><br><span class="line"><span class="string">    &quot;mcp-proxy-for-aws@latest&quot;,</span></span><br><span class="line"><span class="string">    &quot;https://aws-mcp.us-east-1.api.aws/mcp&quot;,</span></span><br><span class="line"><span class="string">    &quot;--profile&quot;, &quot;my-aws-profile&quot;,</span></span><br><span class="line"><span class="string">    &quot;--metadata&quot;, &quot;AWS_REGION=ap-northeast-1&quot;</span></span><br><span class="line"><span class="string">  ]</span></span><br><span class="line"><span class="string">&#125;&#x27;</span></span><br></pre></td></tr></table></figure>

<p>ポイント:</p>
<ul>
<li>第1引数の URL（<code>https://aws-mcp.us-east-1.api.aws/mcp</code>）が AWS MCP Server のエンドポイントを指す。今回の登録例は米国リージョン側で、欧州リージョンを使う場合は URL の <code>us-east-1</code> 部分を <code>eu-central-1</code> に置き換える</li>
<li><code>--profile</code> で AWS プロファイルを明示できる</li>
<li><code>--metadata AWS_REGION=…</code> は、API 呼び出しのデフォルトリージョンを AWS MCP Server（エンドポイント）側に伝えるためのオプションである。エンドポイントのリージョン（<code>us-east-1</code>）と、実際に操作したい AWS リソースのリージョン（<code>ap-northeast-1</code>）は別の概念として扱う</li>
<li>意図しない書き込みを防ぎたい場合は <code>args</code> に <code>&quot;--read-only&quot;</code> を加えておくと、<code>readOnlyHint: True</code> が明示されていないツールがすべて無効化される</li>
</ul>
<p>A・B どちらの方法でも裏側で起動するプロセスは同じ（<code>uvx mcp-proxy-for-aws@latest</code>）で、A は Agent Skills も同梱される代わりに引数を細かく指定できない、という違いです。</p>
<h3 id="接続確認">接続確認</h3><figure class="highlight console"><input type="checkbox" id="code-wrap-own1w2-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-own1w2-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">claude mcp list</span></span><br><span class="line">Checking MCP server health…</span><br><span class="line"></span><br><span class="line">aws-mcp: uvx mcp-proxy-for-aws@latest https://aws-mcp.us-east-1.api.aws/mcp --profile my-aws-profile --metadata AWS_REGION=ap-northeast-1 - ✓ Connected</span><br></pre></td></tr></table></figure>

<h2 id="提供される11のツール">提供される11のツール</h2><p>ローンチブログでは <code>call_aws</code> &#x2F; <code>search_documentation</code> &#x2F; <code>read_documentation</code> &#x2F; <code>run_script</code> の4つが強調されます。実際に AWS MCP Server が公開するツールは <strong>11個</strong> あります。</p>
<p>確認方法は以下の2系統があります。</p>
<ol>
<li>クライアント側：Claude Code 経由で AWS MCP Server に <code>tools/list</code> を投げると、<code>mcp__aws-mcp__aws___*</code> 名前空間に紐づくツール定義として全11個が列挙される</li>
<li>公式ドキュメント側：Understanding the MCP Server tools（AWS Agent Toolkit User Guide）に全11ツールのリファレンスが記載されている</li>
</ol>
<p><code>tools/list</code> の取得結果と公式ドキュメントの記載は完全に一致します。公式ガイド側ではツールが用途別に2カテゴリに分類されています。</p>
<h3 id="AWS-Knowledge-Tools（知識・ドキュメント系：6個）">AWS Knowledge Tools（知識・ドキュメント系：6個）</h3><div class="scroll"><table>
<thead>
<tr>
<th>#</th>
<th>ツール名</th>
<th>役割</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td><code>search_documentation</code></td>
<td>AWS 公式ドキュメント検索</td>
</tr>
<tr>
<td>2</td>
<td><code>read_documentation</code></td>
<td>URL を指定してドキュメント本文取得</td>
</tr>
<tr>
<td>3</td>
<td><code>recommend</code></td>
<td>ドキュメントページの関連推薦</td>
</tr>
<tr>
<td>4</td>
<td><code>list_regions</code></td>
<td>全 AWS リージョン一覧</td>
</tr>
<tr>
<td>5</td>
<td><code>get_regional_availability</code></td>
<td>サービス・API・CFn の地域別可用性チェック</td>
</tr>
<tr>
<td>6</td>
<td><code>retrieve_skill</code></td>
<td>Agent Skill（特定領域の実行手順）を取得</td>
</tr>
</tbody></table></div>
<h3 id="AWS-API-Tools（API実行系：5個）">AWS API Tools（API実行系：5個）</h3><div class="scroll"><table>
<thead>
<tr>
<th>#</th>
<th>ツール名</th>
<th>役割</th>
<th>エンドポイント側の SigV4</th>
</tr>
</thead>
<tbody><tr>
<td>7</td>
<td><code>call_aws</code></td>
<td>AWS CLI コマンドを実行（15,000以上のAPI）</td>
<td>SigV4 必須</td>
</tr>
<tr>
<td>8</td>
<td><code>run_script</code></td>
<td>サンドボックス Python 実行</td>
<td>SigV4 必須</td>
</tr>
<tr>
<td>9</td>
<td><code>suggest_aws_commands</code></td>
<td>自然言語クエリから AWS CLI コマンドを提案</td>
<td>SigV4 不要</td>
</tr>
<tr>
<td>10</td>
<td><code>get_presigned_url</code></td>
<td>S3 用の署名付き URL を生成（大ファイル転送用）</td>
<td>SigV4 必須</td>
</tr>
<tr>
<td>11</td>
<td><code>get_tasks</code></td>
<td><code>call_aws</code> &#x2F; <code>run_script</code> 経由の長時間タスクをポーリング</td>
<td>SigV4 必須</td>
</tr>
</tbody></table></div>
<h2 id="実践1：ナレッジカットオフ後の-AWS-サービスを質問する">実践1：ナレッジカットオフ後の AWS サービスを質問する</h2><p>LLM のナレッジカットオフ以降にリリースされた AWS サービスについて、<code>search_documentation</code> と <code>read_documentation</code> を組み合わせると、エージェント自身が公式ドキュメントから最新情報を取得できます。</p>
<p>題材は AWS News Blog の GA アナウンスで取り上げられている <strong>Amazon S3 Vectors</strong> を採用し、公式デモと同じ題材が Claude Code 環境でも同様に再現できるかを確認します。Claude Code の対話セッション中に普通に質問するだけで、必要に応じてツールが呼ばれます。ここでは再現性のために <code>claude -p</code>（非対話モード）で明示的にツールを指定して呼び出します。</p>
<figure class="highlight console"><input type="checkbox" id="code-wrap-own1w2-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-own1w2-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">claude --allowedTools <span class="string">&quot;mcp__aws-mcp__aws___search_documentation,mcp__aws-mcp__aws___read_documentation&quot;</span> \</span></span><br><span class="line"><span class="language-bash">    -p <span class="string">&quot;search_documentation で &#x27;Amazon S3 Vectors&#x27; を検索し、最初の3件のタイトルとURLを箇条書きで報告してください。&quot;</span></span></span><br></pre></td></tr></table></figure>

<p>実行結果:</p>
<figure class="highlight md"><input type="checkbox" id="code-wrap-own1w2-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-own1w2-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="bullet">-</span> Amazon S3 Vectors — https://aws.amazon.com/s3/features/vectors/</span><br><span class="line"><span class="bullet">-</span> Introducing Amazon S3 Vectors: First cloud storage with native vector support</span><br><span class="line">  at scale (preview) | AWS News Blog</span><br><span class="line">  — https://aws.amazon.com/blogs/aws/introducing-amazon-s3-vectors-first-cloud-storage-with-native-vector-support-at-scale/</span><br><span class="line"><span class="bullet">-</span> Api-S3vectors-2025-07-15 — https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-s3vectors-2025-07-15.html</span><br></pre></td></tr></table></figure>

<p>続けて <code>read_documentation</code> で本文を取得すれば、Web 検索を介さずダイレクトに公式ドキュメントを読み込めます。今回は <code>https://aws.amazon.com/s3/features/vectors/</code> を指定し、</p>
<blockquote>
<p>Amazon S3 Vectors は、ベクトルデータをネイティブに保存・クエリできる初のクラウドオブジェクトストレージで、AI エージェント、AI 推論、セマンティック検索向けに最適化されています。ベクトルのアップロード・保存・クエリのコストを最大 90% 削減でき、…</p>
</blockquote>
<p>といった要約を得られました。WebFetch との違いは、<code>read_documentation</code> が AWS のドキュメントページを取得して AI 向けに markdown 形式に変換して返す点と、<code>search_documentation</code> が AWS ドキュメント全体（API リファレンス・ベストプラクティス・サービスガイド・Skills）を検索できるため、URL を事前に知らなくても目的のページに到達できる点です。</p>
<p>対話モードの Claude Code でも同じ質問を試したところ、同様のレスポンスが得られました。</p>
<img src="/images/2026/20260525a/対話モード_Claude_Code_でのレスポンス例（1）.png" alt="対話モード_Claude_Code_でのレスポンス例（1）" width="1200" height="219" loading="lazy">

<img src="/images/2026/20260525a/対話モード_Claude_Code_でのレスポンス例（2）.png" alt="対話モード_Claude_Code_でのレスポンス例（2）" width="1200" height="657" loading="lazy">

<h2 id="実践2：call-aws-で-AWS-API-を直接叩く">実践2：<code>call_aws</code> で AWS API を直接叩く</h2><p>対話セッション中に自然言語で質問するだけで、Claude Code は必要な API を自動選択し、<code>call_aws</code>（MCP 越しに AWS CLI を実行するツール）を複数回組み合わせて呼び出してくれます。</p>
<p>たとえば、ガバナンス監査の文脈で「誰がいつ作った EC2 か」を確認したいとき、対話セッションで次のように質問します。</p>
<blockquote>
<p>AWS MCP Server を使って、ap-northeast-1 にある EC2 インスタンスを稼働中・停止中で分けて、それぞれの起動時刻と、作成した IAM プリンシパルも一緒に一覧してください。</p>
</blockquote>
<p>「AWS MCP Server を使って」と添えるのは、Claude Code が他の経路（Bash で <code>aws cli</code> を直接叩く等）ではなく AWS MCP Server 経由で実行するように誘導するためです。実際に Claude Code へ投げた結果が次のスクリーンショットです。</p>
<img src="/images/2026/20260525a/claude-code-interactive-call-aws_copy.png" alt="claude-code-interactive-call-aws_copy.png" width="1200" height="983" loading="lazy">

<p>この調査（誰がいつどのリソースを作ったかの追跡）は、手動で aws cli を叩くと以下のような複数ステップが必要です。</p>
<ol>
<li><code>ec2:DescribeInstances</code> でインスタンス一覧と状態・起動時刻を取得する</li>
<li>取得した各 InstanceId について、CloudTrail で <code>LookupEvents</code> を <code>EventName=RunInstances</code> + <code>ResourceName=&lt;instance-id&gt;</code> で叩く</li>
<li>返ってきた event の <code>userIdentity</code> から作成者を抽出する</li>
<li>結果をテーブルに整形する</li>
</ol>
<p>ユーザーは API 名も CLI オプションもレスポンス JSON のパスも書いていませんが、Claude Code が <code>call_aws</code> ツールを使ってこの 4 ステップを内部的に実行してくれます。自然言語で運用上知りたいことを投げるだけで、複数 API のオーケストレーション・JSON 解析・整形までを自動でやってくれるのが対話モードの利点です。</p>
<p>本記事ではこの後の手順で読者の手元で同じ結果が再現できるよう、<code>-p</code>（非対話）モードで同じ質問を投げる形でデモします。対話モードと同じ自然言語のプロンプトで構いません。</p>
<figure class="highlight console"><input type="checkbox" id="code-wrap-own1w2-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-own1w2-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">claude --allowedTools <span class="string">&quot;mcp__aws-mcp__aws___call_aws,mcp__aws-mcp__aws___run_script,ToolSearch&quot;</span> \</span></span><br><span class="line"><span class="language-bash">    -p <span class="string">&quot;AWS MCP Server を使って、ap-northeast-1 にある EC2 インスタンスを稼働中・停止中で分けて、それぞれの起動時刻と、作成した IAM プリンシパルも一緒に一覧してください。&quot;</span></span></span><br></pre></td></tr></table></figure>

<figure class="highlight txt"><input type="checkbox" id="code-wrap-own1w2-6" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-own1w2-6" title="コードの折り返しを切り替える"></label><figcaption><span>実行結果</span></figcaption><table><tr><td class="code"><pre><span class="line">稼働中（running）— 1 台</span><br><span class="line"></span><br><span class="line">┌───────────────────┬─────────────────────┬──────────────┬─────────────────────┬────────────────────────────────────┐</span><br><span class="line">│       Name        │     InstanceId      │ InstanceType │  LaunchTime (UTC)   │       作成 IAM プリンシパル        │</span><br><span class="line">├───────────────────┼─────────────────────┼──────────────┼─────────────────────┼────────────────────────────────────┤</span><br><span class="line">│ dev-app-server-01 │ i-XXXXXXXXXXXXXXXXX │ t3a.small    │ 2026-05-01 00:49:30 │ 不明（CloudTrail 90 日保持期間外） │</span><br><span class="line">└───────────────────┴─────────────────────┴──────────────┴─────────────────────┴────────────────────────────────────┘</span><br><span class="line"></span><br><span class="line">停止中（stopped）— x 台（うち抜粋4件）</span><br><span class="line"></span><br><span class="line">┌────────────────────┬─────────────────────┬──────────────┬─────────────────────┬────────────────────────────────────┐</span><br><span class="line">│        Name        │     InstanceId      │ InstanceType │  LaunchTime (UTC)   │       作成 IAM プリンシパル        │</span><br><span class="line">├────────────────────┼─────────────────────┼──────────────┼─────────────────────┼────────────────────────────────────┤</span><br><span class="line">│ dev-ubuntu-test-01 │ i-XXXXXXXXXXXXXXXXX │ t2.micro     │ 2026-05-08 06:59:07 │ IAMUser: user-a                    │</span><br><span class="line">├────────────────────┼─────────────────────┼──────────────┼─────────────────────┼────────────────────────────────────┤</span><br><span class="line">│ dev-ubuntu-test-02 │ i-XXXXXXXXXXXXXXXXX │ t2.micro     │ 2026-05-08 06:28:54 │ IAMUser: user-a                    │</span><br><span class="line">├────────────────────┼─────────────────────┼──────────────┼─────────────────────┼────────────────────────────────────┤</span><br><span class="line">│ dev-win-2025       │ i-XXXXXXXXXXXXXXXXX │ t3.micro     │ 2026-04-27 01:36:40 │ IAMUser: user-b                    │</span><br><span class="line">├────────────────────┼─────────────────────┼──────────────┼─────────────────────┼────────────────────────────────────┤</span><br><span class="line">│ dev-rhel-01        │ i-XXXXXXXXXXXXXXXXX │ t2.micro     │ 2026-03-19 01:30:49 │ 不明（CloudTrail 90 日保持期間外） │</span><br><span class="line">└────────────────────┴─────────────────────┴──────────────┴─────────────────────┴────────────────────────────────────┘</span><br><span class="line">（残り x-4 件は省略）</span><br><span class="line"></span><br><span class="line">...</span><br></pre></td></tr></table></figure>

<p>ローカルの AWS CLI を経由しているわけではなく、AWS MCP Server 側で API 呼び出しが組み立てられて実行されます。CloudTrail event の <code>userIdentity.invokedBy</code> フィールドに <code>aws-mcp.amazonaws.com</code> が記録されるため、ヒトのコンソール操作・直接 CLI 操作と区別できます（後述「監査・ログ」セクションで event を確認します）。</p>
<h2 id="IAM-ポリシーで-AI-エージェントの行動範囲を絞る">IAM ポリシーで AI エージェントの行動範囲を絞る</h2><p>後述の CloudTrail event で確認できるとおり、AWS MCP Server 経由の API 呼び出しは呼び出し元の IAM プリンシパルそのものとして AWS に到達します。AWS MCP Server が独自の AWS 内 ID（サービス専用の身分）として API を呼ぶわけではなく、ユーザーのアクセスキーで SigV4 署名された <strong>普通の API 呼び出しを中継</strong> しているだけです。したがって、通常の IAM 設計（ユーザー・グループ・ロールの権限）がそのまま MCP 経由の挙動を縛ります。「最小権限のユーザに MCP を解禁する」という方針であれば、特別な条件キーは不要で、見慣れた IAM ポリシー設計でそのまま対応できます。</p>
<p>実際のリクエストの流れを整理すると次のようになります。MCP クライアントは SigV4 で署名したリクエストを AWS マネージド MCP サーバーに送り、サーバー側で SigV4 を検証したうえで context key（<code>aws:ViaAWSMCPService</code> &#x2F; <code>aws:CalledViaAWSMCP</code>）を付与してから下流の AWS サービスに API 呼び出しを転送します。下流の AWS サービス側は通常の IAM ポリシー評価の一部として、この context key を条件式で参照できます。</p>
<img src="/images/2026/20260525a/Figure-1-2：AWS_マネージド_MCP_サーバーが_SigV4_認証と_context_key_付与で下流の_AWS_API_呼び出しを制御するシーケンス.png" alt="Figure-1-2：AWS マネージド MCP サーバーが SigV4 認証と context key 付与で下流の AWS API 呼び出しを制御するシーケンス" width="1200" height="989" loading="lazy">

<p>出典：AWS Security Blog: Understanding IAM for Managed AWS MCP Servers</p>
<p>組織の規模・統制レベルに応じて、以下の A（SCP）または B（IAM グループ）のパターンを使い分けます。さらに、専用ロールへの AssumeRole でエージェントが扱うクレデンシャル全体を ReadOnly でキャップする方法（補足）も組み合わせ可能です。</p>
<h3 id="A-SCP（Service-Control-Policy）">A. SCP（Service Control Policy）</h3><p>Organizations を使っているなら、本番アカウント・OU に SCP として適用するのが定番です。SCP は root を含む全プリンシパルに作用するため、誰がどんな経路で叩いても本番への書き込み系操作を一括で禁止できます。本番アカウント全体への横断的なガードレールに使います。</p>
<p>ポリシー条件には MCP 経由を識別する 2 つの IAM context key が使えます（SCP に限らず、IAM Group ポリシーや AssumeRole の Trust policy など、すべての IAM ポリシーで利用可能です）。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>context key</th>
<th>型</th>
<th>用途</th>
</tr>
</thead>
<tbody><tr>
<td><code>aws:ViaAWSMCPService</code></td>
<td>Bool</td>
<td>AWS マネージド MCP 経由かどうかの真偽値判定</td>
</tr>
<tr>
<td><code>aws:CalledViaAWSMCP</code></td>
<td>string（single-valued）</td>
<td>どの MCP サーバー経由かを判定（値は MCP の AWS 内識別子：<code>aws-mcp.amazonaws.com</code>、<code>eks-mcp.amazonaws.com</code>、<code>ecs-mcp.amazonaws.com</code> 等。AWS の用語では「サービスプリンシパル名」）</td>
</tr>
</tbody></table></div>
<p>これらを使い分けることで「普段は管理者権限のままでよいが、AI エージェント経由で叩く瞬間だけ特定の破壊的操作を止めたい」「汎用 MCP は禁止して EKS&#x2F;ECS 専用 MCP は許可したい」といった粒度を表現できます。</p>
<p>たとえば「特定アカウント・OU では AWS MCP Server 経由の操作を全面禁止する」というポリシーを敷きたい場合は、Bool 型の <code>aws:ViaAWSMCPService</code> を条件にしてすべてのアクションを Deny する SCP を適用します。</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2012-10-17&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Statement&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;Sid&quot;</span><span class="punctuation">:</span> <span class="string">&quot;DenyAllActionsViaMCP&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Deny&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Condition&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;Bool&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;aws:ViaAWSMCPService&quot;</span><span class="punctuation">:</span> <span class="string">&quot;true&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<p>「破壊的操作だけ MCP 経由で禁止したい」場合は <code>Action</code> を <code>ec2:TerminateInstances</code> &#x2F; <code>rds:DeleteDBInstance</code> &#x2F; <code>s3:DeleteBucket</code> 等に絞り込みます。MCP の種類で分けたい場合は string 型の <code>aws:CalledViaAWSMCP</code> で MCP のサービスプリンシパル名を判定します（具体的なポリシー例は AWS Security Blog: Understanding IAM for Managed AWS MCP Servers を参照）。</p>
<h3 id="B-IAM-グループに-ReadOnly-NG操作の-Deny-をアタッチ">B. IAM グループに ReadOnly + NG操作の Deny をアタッチ</h3><p>シンプルな方法で、多くの組織ではこれだけで十分です。新規に <code>mcp-users</code> のような IAM グループを作って使用メンバーを入れる方法でも、AWS MCP Server を使う人がすでに所属している既存グループに権限を追加する方法でも運用できます。</p>
<p>読み取り権限のベースとして AWS マネージドポリシー <code>ReadOnlyAccess</code>（または <code>SecurityAudit</code>）をグループにアタッチし、削除・変更系で組織として実行させたくない NG 操作だけを customer-managed policy として別途アタッチします。NG 操作の典型例：</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2012-10-17&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Statement&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;Sid&quot;</span><span class="punctuation">:</span> <span class="string">&quot;DenyDangerousActions&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Deny&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;ec2:TerminateInstances&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;rds:DeleteDBInstance&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;iam:Delete*&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;s3:DeleteBucket&quot;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<p>なお、グループ単位だと粒度が荒くて困る場面（例：GA したばかりの MCP を動作検証メンバー数名にだけ解禁したい）では、同じポリシーを IAM ユーザー／ロールに直接アタッチして段階展開する形も使えます。</p>
<h3 id="補足：AssumeRole-で包括的な-ReadOnly-キャップ">補足：AssumeRole で包括的な ReadOnly キャップ</h3><p><code>aws:ViaAWSMCPService</code> を使った前項の例は「特定サービスごとのガードレール」（経路別 Deny）に向きます。一方、「経路に関わらずエージェントが使うクレデンシャル自体を ReadOnly に縛りたい」というように複数サービスにまたがる包括的な制限を入れたい場合は、IAM の context key ではなく <strong>MCP 専用ロール + AssumeRole</strong> という別レイヤーで構成できます。</p>
<p>AWS 公式の設計指針は AWS Security Blog: Secure AI agent access patterns to AWS resources using MCP を参照してください（サンプル実装は <code>boto3.assume_role()</code> の <code>PolicyArns</code> で渡す session policy 方式が中心です）。本セクションでは、AssumeRole ベースの人間アクセスで広く使われてきた多層防御パターンを MCP 用ロールに当てはめます。そのパターンとは、<strong>「Group identity policy で AssumeRole 権限を付与・剥奪 + Trust policy の <code>sts:RoleSessionName = $&#123;aws:username&#125;</code> で監査詐称防止 + 専用ロールに <code>ReadOnlyAccess</code> を静的アタッチ + 機密 read 系は Inline Deny で除外」</strong> です。</p>
<p>仕組みの概略:</p>
<ol>
<li>MCP 用の専用 IAM ロール（例：<code>MCPAgentRole</code>）を作成し、<code>ReadOnlyAccess</code>（AWS マネージドポリシー）と機密 read 系の Inline Deny を直接アタッチする</li>
<li>ロールの Trust policy で <code>sts:RoleSessionName = $&#123;aws:username&#125;</code> を強制し、<code>sts:AssumeRole</code> 権限は MCP 利用メンバーが所属する IAM Group の identity policy で付与する</li>
<li><code>mcp-proxy-for-aws</code> の <code>--profile</code> に、そのロールを assume するプロファイル（<code>role_arn</code> + <code>source_profile</code> を指定）を設定する</li>
<li>SDK が AssumeRole を実行し、その session の権限が <code>ReadOnlyAccess</code> + 追加 Deny に絞られる</li>
</ol>
<p>このアプローチは MCP 固有の context key を使わず、エージェントの実行コンテキスト全体を ReadOnly でキャップする思想です。「経路（MCP 経由 &#x2F; 直接 CLI）ごとの使い分け」ではなく「エージェントが使うクレデンシャル全体への制限」として機能します。</p>
<p>全体像は次のとおりです。図中 (a)〜(c) は <strong>AssumeRole が成り立つために必要な 3 つの要素</strong>（呼び出し元の権限・ロールの受け入れ条件・ロールの実行権限）に対応します。</p>
<pre class="mermaid" data-mermaid="8ea7ad8766a57044e1f26e29a08b96a7f87960501592758c35317dc3428681cd">flowchart LR
    subgraph local["開発者ローカル"]
        direction TB
        user["IAM User<br>（例：alice）"]
        proxy["mcp-proxy-for-aws"]
    end

    subgraph aws["AWS account"]
        direction TB
        role["MCPAgentRole<br><br><b>(b) Trust policy</b><br>Principal = &lt;account-id&gt;:root<br>sts:RoleSessionName = #36;{aws:username}<br>→ session_name 偽装を IAM レベルで阻止<br><br><b>(c) Permissions</b><br>・ReadOnlyAccess（AWS managed policy）<br>・Inline Deny（機密 read / 破壊操作）"]
        endpoint["AWS MCP Server endpoint"]
        svc["AWS service：S3 / EC2 / IAM / ...<br>✓ ReadOnly 系 API は通る<br>✗ 機密 read（GetSecretValue 等）は Deny<br>✗ 書き込み系は そもそも Allow されていない"]
    end

    user -->|"(a) Group の Identity policy で sts:AssumeRole 権限<br>（AssumeMCPAgentRolePermission）<br>+<br>AssumeRole 呼び出し<br>（role_session_name = alice）"| role
    role -->|"一時クレデンシャル<br>（ReadOnly にキャップ済み）"| proxy
    proxy -->|"SigV4 署名"| endpoint
    endpoint --> svc</pre>

<p>同じ構成を「時系列」の視点で表現したシーケンス図が次です。AssumeRole 評価 → クレデンシャル発行 → SigV4 → MCP 中継 → Permissions 評価 という IAM 評価の発火順序を、上のフロー図とは別の切り口から見られます：</p>
<pre class="mermaid" data-mermaid="a9821e774e6a7d26749d259878ab39b89be1eefa0e2710c07a31ad21b0cea8b1">sequenceDiagram
    autonumber
    participant U as IAM User: alice<br>(+ profile my-mcp)
    participant Px as mcp-proxy-for-aws
    participant Sts as AWS STS
    participant Mcp as AWS MCP Server
    participant Svc as AWS service

    U->>Px: profile 経由で起動
    Px->>Sts: sts:AssumeRole<br>(role_arn = MCPAgentRole,<br>role_session_name = alice)

    rect rgba(255, 230, 100, 0.35)
        Note over Sts: (b) Trust policy 評価<br>Principal & RoleSessionName 検証
    end

    alt Trust policy OK
        Sts-->>Px: 一時クレデンシャル<br>(ReadOnly + Inline Deny でキャップ済み)
        Px->>Mcp: SigV4 署名付き API 呼び出し
        Mcp->>Svc: 中継<br>+ aws:ViaAWSMCPService=true 付与

        rect rgba(255, 230, 100, 0.35)
            Note over Svc: (c) Permissions 評価<br>ReadOnlyAccess + Inline Deny
        end

        alt ReadOnly 系で Deny に該当しない
            Svc-->>Px: ✓ 成功
        else 機密 read / 書き込み
            Svc-->>Px: ✗ AccessDenied
        end
    else Principal/RoleSessionName 不一致
        Sts-->>Px: ✗ AccessDenied（AssumeRole 時点で拒否）
    end</pre>

<p>フロー図は「どの要素がどこに繋がっているか」という構造を、シーケンス図は「どの順番で何が評価されるか」という時系列を表します。IAM の評価点は <strong>黄色のハイライト</strong> で示した 2 段階で、(b) Trust policy で落ちれば (c) Permissions の評価には進みません。Group メンバーシップの追加・削除は <code>iam:AddUserToGroup</code> &#x2F; <code>iam:RemoveUserFromGroup</code> として CloudTrail に記録されるので、MCP アクセス権の付与・剥奪も監査ログから追えます。</p>
<p>整理すると、AWS MCP Server 向けの IAM 制御は次の使い分けになります：</p>
<div class="scroll"><table>
<thead>
<tr>
<th>要件</th>
<th>想定される構成</th>
</tr>
</thead>
<tbody><tr>
<td>「特定サービスの危険な操作だけ MCP 経由で禁止」（経路別ガードレール）</td>
<td><code>aws:ViaAWSMCPService</code> + サービス特化 Deny（前項のパターン）</td>
</tr>
<tr>
<td>「エージェントが使うクレデンシャル全体を ReadOnly に縛る」（包括キャップ）</td>
<td>AssumeRole + 専用ロール（本セクションのパターン）</td>
</tr>
</tbody></table></div>
<h2 id="監査・ログ（CloudTrail-CloudWatch）">監査・ログ（CloudTrail &#x2F; CloudWatch）</h2><p>AWS MCP Server 経由の API 呼び出しはすべて CloudTrail に記録されます。実際に <code>call_aws</code> で発行した <code>sts:GetCallerIdentity</code> の event を <code>ap-northeast-1</code> の CloudTrail から取得した抜粋：</p>
<figure class="highlight json"><input type="checkbox" id="code-wrap-own1w2-7" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-own1w2-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;eventTime&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2026-05-08T08:45:04Z&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;eventSource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;sts.amazonaws.com&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;eventName&quot;</span><span class="punctuation">:</span> <span class="string">&quot;GetCallerIdentity&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;awsRegion&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ap-northeast-1&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;userIdentity&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;IAMUser&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;arn&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:iam::123456789012:user/agent-user&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;userName&quot;</span><span class="punctuation">:</span> <span class="string">&quot;agent-user&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;invokedBy&quot;</span><span class="punctuation">:</span> <span class="string">&quot;aws-mcp.amazonaws.com&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;sourceIPAddress&quot;</span><span class="punctuation">:</span> <span class="string">&quot;aws-mcp.amazonaws.com&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;userAgent&quot;</span><span class="punctuation">:</span> <span class="string">&quot;aws-mcp.amazonaws.com&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<p>CloudTrail で MCP 経由かどうかを判別する際に使うフィールドは以下です（直接 CLI 経由との比較）。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>フィールド</th>
<th>直接 CLI 経由</th>
<th>MCP 経由</th>
</tr>
</thead>
<tbody><tr>
<td><code>userIdentity.arn</code></td>
<td>実 IAM プリンシパル</td>
<td>同じ（MCP は本人として動作）</td>
</tr>
<tr>
<td><code>userIdentity.invokedBy</code></td>
<td>付かない</td>
<td><code>aws-mcp.amazonaws.com</code></td>
</tr>
<tr>
<td><code>sourceIPAddress</code></td>
<td>クライアントの実IPアドレス</td>
<td><code>aws-mcp.amazonaws.com</code>（実IPは記録されない）</td>
</tr>
<tr>
<td><code>userAgent</code></td>
<td><code>aws-cli/...</code> などクライアント実装文字列</td>
<td><code>aws-mcp.amazonaws.com</code></td>
</tr>
</tbody></table></div>
<p>ポイント：MCP 経由の操作は CloudTrail から「誰の IAM プリンシパルが・MCP サービス経由で・何の API を」叩いたかが追えます。一方、クライアントの IP アドレスや具体的なツール（Claude Code &#x2F; Codex &#x2F; Cursor 等）の区別は CloudTrail には現れません。</p>
<p><code>sourceIPAddress</code> が <code>aws-mcp.amazonaws.com</code> 固定値になる挙動は、ガードレール設計上重要な制約になります。IAM ポリシーで <code>aws:SourceIp</code> を条件として「社内ネットワークからのみアクセス可」と縛っていた場合、MCP 経由の呼び出しはこの条件にマッチせず、許可ステートメント側で落ちる（または明示 Deny に当たる）可能性があります。MCP 経由を許可したい場合は、<code>aws:SourceIp</code> 条件を <code>aws:ViaAWSMCPService</code> での例外と組み合わせて再設計するか、SCP レベルで除外を入れる必要があります。</p>
<p>なお、IAM ポリシーの condition key として登場する <code>aws:ViaAWSMCPService</code> &#x2F; <code>aws:CalledViaAWSMCP</code> は、ポリシー評価時に MCP 経由かどうかを判定する論理値・文字列値であり、CloudTrail event の中に同名のフィールドが現れるわけではありません。ログ分析側では <code>userIdentity.invokedBy = &quot;aws-mcp.amazonaws.com&quot;</code> をフィルタ条件として使います。</p>
<p>CloudWatch Logs Insights から「MCP 経由の呼び出しだけ」を抽出する場合のクエリ例：</p>
<figure class="highlight text"><input type="checkbox" id="code-wrap-own1w2-8" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-own1w2-8" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">fields eventTime, eventSource, eventName, userIdentity.arn, userIdentity.invokedBy</span><br><span class="line">| filter userIdentity.invokedBy = &quot;aws-mcp.amazonaws.com&quot;</span><br><span class="line">| sort eventTime desc</span><br><span class="line">| limit 50</span><br></pre></td></tr></table></figure>

<p>CloudWatch メトリクスでも MCP 経由の呼び出し回数・レイテンシ・エラー率が可視化されます。</p>
<h2 id="まとめ">まとめ</h2><p>AWS MCP Server を Claude Code に組み込む手順、11 ツールの動作、IAM 設計、CloudTrail 監査までを実際に動かして整理しました。要点：</p>
<ul>
<li><strong>接続と認証</strong>：<code>mcp-proxy-for-aws</code>（uvx 経由のローカル stdio プロキシ）が MCP プロトコルの全往復に SigV4 を強制するため、Claude Code 経由では AWS クレデンシャル必須</li>
<li><strong>認可モデル</strong>：MCP 固有の IAM action はなく、通常の IAM &#x2F; SCP &#x2F; マネージドポリシー（<code>ReadOnlyAccess</code> 等）でそのまま権限制御できる</li>
<li><strong>11 ツール</strong>：知識系 6（<code>search_documentation</code> 等）と API 系 5（<code>call_aws</code> 等）の 2 系統</li>
<li><strong>IAM ガードレール 2 軸</strong>：経路別 Deny は <code>aws:ViaAWSMCPService</code> 等の context key、包括キャップは専用ロール（<code>ReadOnlyAccess</code> + Inline Deny 直接アタッチ）への AssumeRole で実現。Trust policy で <code>sts:RoleSessionName = $&#123;aws:username&#125;</code> を強制すれば CloudTrail の個人追跡性も担保</li>
<li><strong>CloudTrail 監査</strong>：MCP 経由の API 呼び出しは <code>userIdentity.invokedBy = aws-mcp.amazonaws.com</code> でフィルタ可能（<code>sourceIPAddress</code> 固定値の挙動は <code>aws:SourceIp</code> 制限との併用時に要注意）</li>
</ul>
<p>最初の一歩としては、<code>--read-only</code> フラグ付き・サンドボックスアカウントで <code>call_aws</code> から始めると手軽に試せます。AWS Security Blog で VPC endpoint 対応（two-stage authorization）等の機能拡充も予告されているため、ガバナンス要件が厳しい環境では機能が揃ってからの本格採用も選択肢になります。</p>
<h2 id="参考">参考</h2><ul>
<li>AWS What’s New: AWS MCP Server is now generally available</li>
<li>AWS News Blog: The AWS MCP Server is now generally available</li>
<li>AWS MCP Server (User Guide)</li>
<li>Understanding the MCP Server tools</li>
<li>Quotas for AWS MCP Server</li>
<li>AWS Security Blog: Understanding IAM for Managed AWS MCP Servers</li>
<li>AWS Security Blog: Secure AI agent access patterns to AWS resources using MCP</li>
<li>GitHub: aws&#x2F;agent-toolkit-for-aws</li>
</ul>
]]></content>
    <summary type="html">Claude Code から AWS MCP Server をセットアップし、提供される 11 ツール・IAM ガードレール・CloudTrail 監査までを実際に動かしながらまとめます。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="AWS" scheme="https://future-architect.github.io/tags/AWS/"/>
    <category term="Claude" scheme="https://future-architect.github.io/tags/Claude/"/>
    <category term="IAM" scheme="https://future-architect.github.io/tags/IAM/"/>
    <category term="MCP" scheme="https://future-architect.github.io/tags/MCP/"/>
  </entry>
  <entry>
    <title>Garmin のデータは宝の山だった ── Python + Claude でランニング分析ツールを自作した</title>
    <link href="https://future-architect.github.io/articles/20260515a/"/>
    <id>https://future-architect.github.io/articles/20260515a/</id>
    <published>2026-05-14T15:00:00.000Z</published>
    <updated>2026-05-14T15:00:00.000Z</updated>
    <author><name>山下量之</name></author>
    <content type="html"><![CDATA[<h2 id="はじめに">はじめに</h2><p>ランニングにハマって、Garmin デバイスを使って走っている。走り終わったあと、Garmin Connect のアプリを開いてデータを眺めるのが楽しい。</p>
<img fetchpriority="high" src="/images/2026/20260515a/image.png" alt="image.png" width="1083" height="1871">

<p>ただ、毎日眺めているうちに、どこか使い切れていない感覚が残る。Garminは膨大な数字を出してくれるのに、「結局どこが悪かったのか」には踏み込んでくれない。</p>
<p>直近の土浦かすみがうらマラソンがまさにそれだった。前半をゆっくり入りすぎて17km関門をギリギリ通過、焦って残り15kmを急激に上げたら腹痛が来て後半は完全に崩れた。ペース配分の問題なのか、スタミナなのか、フォームなのか。感覚ではなんとなくわかるが、数値で裏付けられない。</p>
<p>アプリに表示されるのは「完走おめでとう！」という通知と、距離・タイム・ペースのサマリーくらい。より詳細な分析を見るには Garmin Connect+ というサブスクリプションが必要らしい。内容を見る限り、AIによる分析やアドバイス、トレーニングガイダンスなどは用意されているが、レース単位でどこが問題だったかを掘り下げるようなフィードバックまでは踏み込まない印象だった。</p>
<p>そこで思った。<strong>手元にはすでにデータがある。それなら自分で作ったほうが早い。</strong></p>
<h2 id="Garmin-が記録しているデータの話">Garmin が記録しているデータの話</h2><p>まず、Garmin がどれだけのデータを記録しているかを知ってほしい。これがこのプロジェクトの出発点だ。</p>
<p>Garminはアクティビティを.fitというバイナリ形式で保存している。Garmin Connect の「アクティビティ」画面からダウンロードできるこのファイルに、以下のすべての指標が詰まっている。デバイスでマラソンを走ると、<strong>1秒ごと</strong>に次の指標が同時に記録される。</p>
<h3 id="1秒毎レコード（record-メッセージ）—-本体内蔵">1秒毎レコード（<code>record</code> メッセージ）— 本体内蔵</h3><div class="scroll"><table>
<thead>
<tr>
<th>カテゴリ</th>
<th>FIT フィールド名</th>
<th>単位</th>
<th>説明</th>
</tr>
</thead>
<tbody><tr>
<td>GPS</td>
<td><code>position_lat</code> &#x2F; <code>position_long</code></td>
<td>semicircle → 度</td>
<td>緯度・経度（独自単位 → <code>× 180/2³¹</code> で変換）</td>
</tr>
<tr>
<td>距離</td>
<td><code>distance</code></td>
<td>m</td>
<td>累積走行距離</td>
</tr>
<tr>
<td>速度</td>
<td><code>speed</code> &#x2F; <code>enhanced_speed</code></td>
<td>m&#x2F;s</td>
<td>瞬間速度（新デバイスは enhanced 優先）</td>
</tr>
<tr>
<td>標高</td>
<td><code>altitude</code> &#x2F; <code>enhanced_altitude</code></td>
<td>m</td>
<td>GPS + 気圧補正標高</td>
</tr>
<tr>
<td>心拍</td>
<td><code>heart_rate</code></td>
<td>bpm</td>
<td>光学心拍センサー値</td>
</tr>
<tr>
<td>ケイデンス</td>
<td><code>cadence</code></td>
<td>片足 spm（× 2 が実値）</td>
<td>歩数&#x2F;分（片足）</td>
</tr>
<tr>
<td>ケイデンス小数部</td>
<td><code>fractional_cadence</code></td>
<td>—</td>
<td>cadence の小数部（精度向上用）</td>
</tr>
<tr>
<td>上下動</td>
<td><code>vertical_oscillation</code></td>
<td>mm</td>
<td>体の上下動幅</td>
</tr>
<tr>
<td>接地時間</td>
<td><code>stance_time</code></td>
<td>ms</td>
<td>足が地面に接触している時間</td>
</tr>
<tr>
<td>垂直比</td>
<td><code>vertical_ratio</code></td>
<td>%</td>
<td>上下動 ÷ ストライド長</td>
</tr>
<tr>
<td>ストライド長</td>
<td><code>step_length</code></td>
<td>mm</td>
<td>1 歩の長さ</td>
</tr>
<tr>
<td>パワー</td>
<td><code>power</code></td>
<td>W</td>
<td>ランニングパワー（瞬間値）</td>
</tr>
<tr>
<td>累積パワー</td>
<td><code>accumulated_power</code></td>
<td>W</td>
<td>累積パワー合計</td>
</tr>
<tr>
<td>温度</td>
<td><code>temperature</code></td>
<td>℃</td>
<td>気温（デバイス周辺温度）</td>
</tr>
<tr>
<td>カロリー</td>
<td><code>calories</code></td>
<td>kcal</td>
<td>累積消費カロリー</td>
</tr>
</tbody></table></div>
<h3 id="セッション・ラップ集計（session-lap-メッセージ）">セッション・ラップ集計（<code>session</code> &#x2F; <code>lap</code> メッセージ）</h3><p>FIT ファイルにはレース全体のサマリー（<code>session</code>）と、ラップごとの集計（<code>lap</code>）も格納されている。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>カテゴリ</th>
<th>FIT フィールド名</th>
<th>単位</th>
<th>説明</th>
</tr>
</thead>
<tbody><tr>
<td><strong>距離・時間</strong></td>
<td><code>total_distance</code></td>
<td>m</td>
<td>総走行距離</td>
</tr>
<tr>
<td></td>
<td><code>total_elapsed_time</code></td>
<td>s</td>
<td>総経過時間（停止時間込み）</td>
</tr>
<tr>
<td></td>
<td><code>total_timer_time</code></td>
<td>s</td>
<td>アクティブ計測時間（停止除く）</td>
</tr>
<tr>
<td></td>
<td><code>total_moving_time</code></td>
<td>s</td>
<td>実際の移動時間</td>
</tr>
<tr>
<td><strong>速度</strong></td>
<td><code>avg_speed</code> &#x2F; <code>max_speed</code></td>
<td>m&#x2F;s</td>
<td>平均・最高速度</td>
</tr>
<tr>
<td></td>
<td><code>enhanced_avg_speed</code> &#x2F; <code>enhanced_max_speed</code></td>
<td>m&#x2F;s</td>
<td>精度向上版</td>
</tr>
<tr>
<td><strong>心拍</strong></td>
<td><code>avg_heart_rate</code> &#x2F; <code>max_heart_rate</code></td>
<td>bpm</td>
<td>平均・最高心拍</td>
</tr>
<tr>
<td></td>
<td><code>min_heart_rate</code></td>
<td>bpm</td>
<td>最低心拍</td>
</tr>
<tr>
<td></td>
<td><code>time_in_hr_zone[0–4]</code></td>
<td>s</td>
<td>ゾーン 1〜5 の滞在秒数（配列）</td>
</tr>
<tr>
<td><strong>ケイデンス</strong></td>
<td><code>avg_running_cadence</code> &#x2F; <code>max_running_cadence</code></td>
<td>片足 spm</td>
<td>平均・最高ケイデンス</td>
</tr>
<tr>
<td><strong>フォーム</strong></td>
<td><code>avg_vertical_oscillation</code></td>
<td>mm</td>
<td>平均上下動</td>
</tr>
<tr>
<td></td>
<td><code>avg_stance_time</code></td>
<td>ms</td>
<td>平均接地時間</td>
</tr>
<tr>
<td></td>
<td><code>avg_stance_time_balance</code></td>
<td>%</td>
<td>平均接地バランス（HRM 接続時）</td>
</tr>
<tr>
<td></td>
<td><code>avg_vertical_ratio</code></td>
<td>%</td>
<td>平均垂直比</td>
</tr>
<tr>
<td></td>
<td><code>avg_step_length</code></td>
<td>mm</td>
<td>平均ストライド長</td>
</tr>
<tr>
<td></td>
<td><code>total_strides</code></td>
<td>回</td>
<td>総ストライド数</td>
</tr>
<tr>
<td><strong>パワー</strong></td>
<td><code>avg_power</code> &#x2F; <code>max_power</code></td>
<td>W</td>
<td>平均・最高パワー</td>
</tr>
<tr>
<td></td>
<td><code>normalized_power</code></td>
<td>W</td>
<td>正規化パワー（NP）</td>
</tr>
<tr>
<td></td>
<td><code>time_in_power_zone</code></td>
<td>s</td>
<td>パワーゾーン別滞在時間（配列）</td>
</tr>
<tr>
<td><strong>カロリー</strong></td>
<td><code>total_calories</code></td>
<td>kcal</td>
<td>総消費カロリー</td>
</tr>
<tr>
<td></td>
<td><code>total_fat_calories</code></td>
<td>kcal</td>
<td>脂肪由来カロリー</td>
</tr>
<tr>
<td><strong>標高</strong></td>
<td><code>total_ascent</code> &#x2F; <code>total_descent</code></td>
<td>m</td>
<td>累積獲得・下降標高</td>
</tr>
<tr>
<td></td>
<td><code>avg_altitude</code> &#x2F; <code>max_altitude</code> &#x2F; <code>min_altitude</code></td>
<td>m</td>
<td>平均・最高・最低標高</td>
</tr>
<tr>
<td><strong>トレーニング効果</strong></td>
<td><code>total_training_effect</code></td>
<td>1–5</td>
<td>有酸素トレーニング効果</td>
</tr>
<tr>
<td></td>
<td><code>total_anaerobic_training_effect</code></td>
<td>1–5</td>
<td>無酸素トレーニング効果</td>
</tr>
<tr>
<td></td>
<td><code>training_load_peak</code></td>
<td>—</td>
<td>トレーニング負荷スコア</td>
</tr>
<tr>
<td><strong>気温</strong></td>
<td><code>avg_temperature</code> &#x2F; <code>max_temperature</code></td>
<td>℃</td>
<td>平均・最高気温</td>
</tr>
<tr>
<td><strong>メタ</strong></td>
<td><code>sport</code> &#x2F; <code>sub_sport</code></td>
<td>—</td>
<td>種目（running &#x2F; generic 等）</td>
</tr>
<tr>
<td></td>
<td><code>start_time</code></td>
<td>—</td>
<td>アクティビティ開始時刻</td>
</tr>
</tbody></table></div>
<p>フルマラソンを走ると、1秒1行 × 15指標以上のタイムシリーズが生成される。4時間台なら約1万5千行、6〜7時間なら約2万5千行以上。数値の総量は軽く30万件を超える。さらにラップ集計・セッションサマリーが加わる。走者の体の状態を1秒単位で切り取った、まさにデータの宝箱だ。</p>
<p>そして、それらを統計したものが Garmin Connect で見られる。以下は同じレースの統計画面だ。</p>
<img src="/images/2026/20260515a/image_2.png" alt="image.png" width="1068" height="1020" loading="lazy">

<p>一般的な指標に加えて、ランニングダイナミクスのセクションには、ケイデンス・上下動・接地時間といったフォーム指標もある。</p>
<img src="/images/2026/20260515a/image_3.png" alt="image.png" width="461" height="784" loading="lazy">

<p>特に面白いのがこの「フォーム」系の指標だ。</p>
<ul>
<li>ケイデンス：1 分間に何歩踏んでいるか。一般的に 170〜180 歩&#x2F;分<br>が効率的とされている</li>
<li>上下動：体が上下に動く幅。前に進むために使えるエネルギーが、上下動に無駄遣いさ<br>れていないかがわかる</li>
<li>垂直比：「前進方向のストライド長に対して上下動がどれだけあるか」の比率。これが<br>高いほど非効率</li>
<li>接地時間：足が地面についている時間。短いほどバネのような反発力が使えている</li>
<li>ランニングパワー：自転車でいうパワーメーターと同じ概念。出力の安定性や疲労によ<br>る低下が数値でわかる</li>
</ul>
<p>ただ、それを見ても「良いのか悪いのか」「どう直せばいいのか」まではわからない。それがこのプロジェクトを作った動機だ。</p>
<h2 id="作ったものとできること">作ったものとできること</h2><p>Pythonで.fitを読み込み、AIで分析し、HTMLレポートとして出力するCLIツールにした。Garmin から入手できる <code>.fit</code> ファイルを渡すと、<strong>ブラウザで見られる HTML レポート</strong>が生成される。</p>
<h3 id="使い方はシンプル">使い方はシンプル</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">python3 cli.py analyze-race \</span><br><span class="line">  --fit ./data/race.fit \</span><br><span class="line">  --race-name <span class="string">&quot;土浦かすみがうらマラソン2026&quot;</span></span><br></pre></td></tr></table></figure>

<p>コマンド1つでレースデータを渡すと、ブラウザで見られるレポートが生成される。</p>
<h3 id="レポートの中身">レポートの中身</h3><h4 id="サマリーバー">サマリーバー</h4><p>ページ上部に、レース名とともに全体の数字がずらっと並ぶ。右上にはスプリット判定バッジ（ポジティブ&#x2F;ネガティブ&#x2F;イーブン）が自動で付く。<br><img src="/images/2026/20260515a/image_4.png" alt="image.png" width="1200" height="243" loading="lazy"></p>
<p>距離・タイム・全体ペース・平均心拍・最大心拍・平均パワー・正規化パワー・獲得標高・消費カロリーが一覧できる。</p>
<h4 id="Plotly-によるインタラクティブなグラフ群">Plotly によるインタラクティブなグラフ群</h4><p>以下のグラフが Plotly で生成される。ブラウザ上でホバーすると詳細な数値が表示され、ズームや範囲選択もできる。</p>
<img src="/images/2026/20260515a/image_5.png" alt="image.png" width="1176" height="1561" loading="lazy">
<img src="/images/2026/20260515a/image_6.png" alt="image.png" width="1168" height="735" loading="lazy">

<p><strong>グラフ</strong>（平均ラインと色分け付き）</p>
<ul>
<li><strong>心拍数・ペース時系列チャート</strong>（2 軸で同時表示）</li>
<li><strong>心拍ゾーン分布 横棒グラフ</strong>（Zone1〜5の時間・割合）</li>
<li><strong>フォーム前後半比較グラフ</strong>（ケイデンス・上下動・接地時間の変化）</li>
<li><strong>標高プロファイル</strong>（GAP オーバーレイ付き）</li>
<li><strong>ラップ別パワーグラフ</strong>（平均パワーと正規化パワーの比較）</li>
</ul>
<h4 id="AI-コーチングレポート（実例）">AI コーチングレポート（実例）</h4><p>ほしかったのはグラフではなく、これだ。<br>Claude が生成した日本語のコーチングレポートが表示される。以下が実際のレポートの全文だ。</p>
<h4 id="1-レース総評">1. レース総評</h4><p><strong>フルマラソン完走：42.672 km &#x2F; 6時間06分31秒（ネットタイム） &#x2F; 平均ペース 8:35&#x2F;km</strong></p>
<p>まず、フルマラソンを最後まで走り切ったことを評価します。6時間台での完走は、42.195kmという距離に対して身体が最後まで「動き続けた」ことの証明です。</p>
<p>しかしデータは、<strong>心肺系には余裕があったにもかかわらず、脚の筋持久力が先に限界を迎えた</strong>ことを明確に示しています。心拍ドリフトはわずか-3.1 bpmと心肺系は安定していた一方、ペースは前半8:13&#x2F;kmから後半8:56&#x2F;kmへ+43秒&#x2F;km（8.7%）失速し、パワー出力も30〜40W低下しました。</p>
<blockquote>
<p><strong>今回のレースを一言で：「心肺に余力を残して脚が止まったレース」</strong></p>
</blockquote>
<h4 id="2-ペース戦略の分析">2. ペース戦略の分析</h4><h5 id="スプリット評価">スプリット評価</h5><div class="scroll"><table>
<thead>
<tr>
<th></th>
<th>平均ペース</th>
<th>平均HR</th>
<th>平均パワー（推定）</th>
</tr>
</thead>
<tbody><tr>
<td>前半（1–21km）</td>
<td><strong>8:13&#x2F;km</strong></td>
<td>152.8 bpm</td>
<td>~220W台</td>
</tr>
<tr>
<td>後半（22–42km）</td>
<td><strong>8:56&#x2F;km</strong></td>
<td>149.7 bpm</td>
<td>~170–190W台</td>
</tr>
<tr>
<td>差</td>
<td><strong>+43秒&#x2F;km</strong></td>
<td>-3.1 bpm</td>
<td>-30〜40W</td>
</tr>
</tbody></table></div>
<p><strong>ポジティブスプリット（後半失速型）</strong> です。後半の心拍が下がっているのにペースも下がっている点が重要で、これは「心肺が追い込めなくなった」のではなく「脚が動かなくなった」ことを意味します。</p>
<h5 id="ペース変動の深刻さ">ペース変動の深刻さ</h5><p><strong>ペース変動係数（CV）28.01%</strong> — これはマラソンとして極めて高い数値です。理想は5%以下、10%でも「不安定」と評価されるレベルに対し、約6倍です。</p>
<p>具体的なラップ推移を見る。</p>
<ul>
<li><strong>走行区間</strong>: Lap 2（7:39）、Lap 4（7:32）、Lap 9（7:28）、Lap 12（7:24）→ 7分半前後</li>
<li><strong>歩行&#x2F;停止区間</strong>: Lap 5（10:07）、Lap 10（9:11）、Lap 29（<strong>16:07</strong>）、Lap 35（12:20）</li>
</ul>
<p>2〜3kmごとに9分&#x2F;km以上のラップが出現しており、<strong>非計画的なRun-Walk</strong>のパターンです。計画的なジェフ・ギャロウェイ方式であれば均等間隔になるはずですが、疲労に応じて歩行が入っている形です。</p>
<h5 id="Lap-16の異常サージ-—-レースのターニングポイント">Lap 16の異常サージ — レースのターニングポイント</h5><p><strong>Lap 16: 5:53&#x2F;km（HR 176, パワー277W）</strong> は、直前のLap 15（9:26&#x2F;km）から突然3分30秒以上の急加速です。下り坂か折り返し地点での興奮によるものと推測されますが、VO2max強度のスパイクが筋グリコーゲンの急速な枯渇を招き、直後のLap 17で9:57&#x2F;kmに急減速。<strong>このサージ以降、ペースの安定性がさらに悪化</strong>しています。</p>
<h5 id="GAP（グレード調整ペース）">GAP（グレード調整ペース）</h5><p>GAP 8:21&#x2F;km（実ペース8:35&#x2F;km、差-14秒&#x2F;km）。累積標高167mのコースに対して大きなペースロスなく対応できており、コース起伏への適応自体は問題ありません。</p>
<h4 id="3-心拍数とエネルギーシステムの分析">3. 心拍数とエネルギーシステムの分析</h4><h5 id="ゾーン分布">ゾーン分布</h5><div class="scroll"><table>
<thead>
<tr>
<th>ゾーン</th>
<th>時間</th>
<th>割合</th>
<th>意味</th>
</tr>
</thead>
<tbody><tr>
<td>Z1（50–60%: 104–124 bpm）</td>
<td>19分27秒</td>
<td>5.3%</td>
<td>回復・歩行区間</td>
</tr>
<tr>
<td>Z2（60–70%: 124–145 bpm）</td>
<td>1時間10分</td>
<td>19.1%</td>
<td>軽い有酸素</td>
</tr>
<tr>
<td>Z3（70–80%: 145–166 bpm）</td>
<td>4時間02分</td>
<td><strong>66.1%</strong></td>
<td>有酸素の中核帯</td>
</tr>
<tr>
<td>Z4（80–90%: 166–186 bpm）</td>
<td>26分38秒</td>
<td>7.3%</td>
<td>閾値付近</td>
</tr>
<tr>
<td>Z5（90–100%: 186–207 bpm）</td>
<td>8分04秒</td>
<td>2.2%</td>
<td>無酸素域</td>
</tr>
</tbody></table></div>
<p><strong>心拍ベースでは理想的な分布</strong>です。Z3に66%、Z2+Z3で85%はマラソンとして適切な有酸素強度帯です。心肺系のコントロールは良好と評価できます。</p>
<h5 id="心拍ドリフトと疲労パターン">心拍ドリフトと疲労パターン</h5><ul>
<li><strong>心拍ドリフト: -3.1 bpm（前半152.8→後半149.7）</strong> — 通常、マラソン後半は脱水や体温上昇で心拍が上がる（カーディアックドリフト）のが一般的ですが、本レースでは逆に低下しています。これは<strong>後半にペースが落ちて（歩行区間が増えて）心拍が上がりきらなかった</strong>ことを示します。</li>
<li><strong>ペース-心拍相関: r &#x3D; -0.461（逆相関）</strong> — 通常はペースが速い&#x3D;心拍が高いで正の相関になるはずが、負の相関です。これは「歩行で心拍が下がりきる前に走行を再開→高い心拍のまま走り始める」というRun-Walkの非効率なパターンを反映しています。歩行区間での回復が不十分なまま走行を再開することで、心拍の制御が乱れています。</li>
</ul>
<h5 id="重要な示唆">重要な示唆</h5><p>心拍Z3に66%滞在できる有酸素ベースがあるにもかかわらず完走タイムが6時間を超えている事実は、<strong>制限因子が心肺系ではなく筋骨格系にある</strong>ことを強く示唆しています。</p>
<h4 id="4-ランニングフォームの評価">4. ランニングフォームの評価</h4><h5 id="フォームスコア-65点（「改善の余地あり」）">フォームスコア: 65点（「改善の余地あり」）</h5><div class="scroll"><table>
<thead>
<tr>
<th>指標</th>
<th>値</th>
<th>評価</th>
<th>前半→後半</th>
</tr>
</thead>
<tbody><tr>
<td>ケイデンス</td>
<td><strong>150 spm</strong></td>
<td>大幅に不足（-20点）</td>
<td>154→148 spm</td>
</tr>
<tr>
<td>上下動</td>
<td><strong>69.7 mm</strong></td>
<td>良好</td>
<td>70.7→68.5 mm</td>
</tr>
<tr>
<td>垂直比</td>
<td><strong>9.55%</strong></td>
<td>高い（-15点）</td>
<td>9.47→9.66%</td>
</tr>
<tr>
<td>接地時間</td>
<td><strong>268.8 ms</strong></td>
<td>許容範囲</td>
<td>268.2→269.8 ms</td>
</tr>
<tr>
<td>ストライド長</td>
<td><strong>74.6 cm</strong></td>
<td>極端に短い</td>
<td>—</td>
</tr>
</tbody></table></div>
<h5 id="ケイデンス（150-spm）—-最大の改善ポイント">ケイデンス（150 spm）— 最大の改善ポイント</h5><p>推奨値170〜180 spmに対して20 spm以上不足しています。後半はさらに148 spmまで低下。低ケイデンスは1歩あたりの衝撃荷重を増大させ、筋疲労を加速させます。</p>
<h5 id="垂直比（9-55-）—-エネルギー漏れの指標">垂直比（9.55%）— エネルギー漏れの指標</h5><p>推奨値6〜8%に対して高く、<strong>推進（前方移動）に対して上下動のコストが大きい</strong>ことを意味します。上下動の絶対値（69.7mm）は良好なのに垂直比が高いのは、ストライドが極端に短い（74.6cm）ことが原因です。つまり「1歩で前に進む距離が短いのに、上下には同じだけ跳ねている」状態です。</p>
<h5 id="ストライド長（74-6-cm）">ストライド長（74.6 cm）</h5><p>一般的なマラソンランナーの100〜120cmと比較して極端に短い値です。<strong>低ケイデンス × 短ストライド</strong> がペース8:35&#x2F;kmの直接的な算術的原因です（150歩&#x2F;分 × 0.746m &#x3D; 111.9m&#x2F;分 ≒ 8:56&#x2F;km相当）。</p>
<h5 id="フォームの後半変化">フォームの後半変化</h5><p>後半の垂直比9.47%→9.66%への悪化は、筋疲労により骨盤の安定性が低下し、推進効率がさらに落ちたことを示しています。ケイデンスも6 spm低下しており、シャッフル（すり足）気味の走りへ移行した可能性があります。</p>
<h4 id="5-ランニングパワーの分析">5. ランニングパワーの分析</h4><h5 id="基本データ">基本データ</h5><div class="scroll"><table>
<thead>
<tr>
<th>指標</th>
<th>値</th>
</tr>
</thead>
<tbody><tr>
<td>平均パワー</td>
<td>188W</td>
</tr>
<tr>
<td>ノーマライズドパワー（NP）</td>
<td>219W</td>
</tr>
<tr>
<td>最大パワー</td>
<td>464W</td>
</tr>
<tr>
<td>変動指数（VI &#x3D; NP&#x2F;Avg）</td>
<td><strong>1.165</strong></td>
</tr>
<tr>
<td>パワー&#x2F;心拍デカップリング</td>
<td><strong>-7.2%</strong></td>
</tr>
<tr>
<td>総消費カロリー</td>
<td>2,963 kcal（69.4 kcal&#x2F;km）</td>
</tr>
</tbody></table></div>
<h5 id="変動指数（VI）1-165-—-ペーシングの不安定さを数値化">変動指数（VI）1.165 — ペーシングの不安定さを数値化</h5><p>理想は1.05以下。1.165は「同じ平均パワーを一定出力で走った場合と比べ、16.5%多くの代謝コストがかかった」ことを意味します。ラップ別で見る。</p>
<ul>
<li>Lap 16: avg 277W &#x2F; NP 334W（スパイク）</li>
<li>Lap 17: avg 168W &#x2F; NP 293W（スパイク直後の急変動）</li>
<li>Lap 29: avg 89W &#x2F; NP 173W（長時間歩行&#x2F;停止）</li>
</ul>
<p>このような急激なパワー変動の繰り返しが、エネルギーコストを大幅に押し上げています。</p>
<h5 id="パワー-心拍デカップリング-7-2">パワー&#x2F;心拍デカップリング -7.2%</h5><p>後半で同じ心拍数に対するパワー出力が7.2%低下。これは<strong>筋疲労による効率劣化</strong>の明確な指標です。5%以内が良好とされる中で、7.2%は有酸素効率が後半で明らかに悪化したことを示します。</p>
<h5 id="パワーゾーン設定について">パワーゾーン設定について</h5><p>Z5（VO2max以上）に83.2%という分布は現実的ではありません。ウォッチのFTP&#x2F;CP設定がデフォルト値のままか、実力に対して低すぎる可能性が高いです。<strong>パワーの絶対値よりもラップ間の相対変化</strong>（前半220W台→後半170W台への低下）を重視して解釈すべきです。</p>
<h4 id="6-次のレースへの具体的アドバイス">6. 次のレースへの具体的アドバイス</h4><h5 id="アドバイス1-ケイデンスを段階的に引き上げる">アドバイス1: ケイデンスを段階的に引き上げる</h5><p><strong>目標: 現在150 spm → まず160 spm、最終的に170 spm</strong></p>
<ul>
<li>週2回、メトロノームアプリ（160 BPM設定）に合わせて20〜30分のイージーランを行う</li>
<li>最初の4週間は160 spmに慣れることに集中し、その後4週間で165 spm、さらに170 spmへ</li>
<li>ケイデンスを上げる際、ストライドを意識的に伸ばそうとしないこと。ケイデンスが上がれば自然にペースは速くなる</li>
<li><strong>練習メニュー</strong>: 100m流し（ストライド）× 6本を週2回。腕振りを小さく速くすることで脚の回転を誘導する</li>
</ul>
<h5 id="アドバイス2-筋持久力トレーニングを最優先で導入する">アドバイス2: 筋持久力トレーニングを最優先で導入する</h5><p><strong>目標: 30km以降のペース低下を現在の+43秒&#x2F;kmから+20秒&#x2F;km以内に</strong></p>
<ul>
<li>心肺系は十分なベースがあるため（Z3に66%滞在）、ボトルネックは脚の筋持久力</li>
<li><strong>週1回のロングラン</strong>: 現在の走力では25km〜30kmを8:30〜9:00&#x2F;kmペースで。歩かずに走り続けることを最優先</li>
<li><strong>週1回のテンポ走</strong>: 7:00〜7:30&#x2F;kmで30〜40分の持続走。現在の走行区間ペース（7:30前後）を「楽に」感じられるようにする</li>
<li><strong>補強運動</strong>: スクワット（自重）30回×3セット、カーフレイズ20回×3セット、ランジウォーク20歩×3セットを週3回</li>
</ul>
<h5 id="アドバイス3-レース中の歩行戦略を「計画的」に変える">アドバイス3: レース中の歩行戦略を「計画的」に変える</h5><p><strong>目標: ペースCV 28% → 15%以下（計画的Run-Walkなら10%前後が可能）</strong></p>
<ul>
<li>現状の非計画的な歩行（疲れたら歩く）を、計画的なRun-Walk方式に切り替える</li>
<li><strong>具体的プラン</strong>: 「4分走って1分歩く」を序盤から一貫して実行。歩行区間でも完全に止まらず、6:30&#x2F;km程度の早歩きを維持</li>
<li>走行区間のペースを7:30&#x2F;km、歩行区間を10:00&#x2F;kmとすると、加重平均で約8:00&#x2F;kmとなり、ネガティブスプリットが狙える</li>
<li><strong>練習で試す</strong>: 月1回の20km走で必ずRun-Walk方式を試し、最適な走行:歩行比率を見つける</li>
</ul>
<h5 id="アドバイス4-Lap-16型のサージを絶対に出さない">アドバイス4: Lap 16型のサージを絶対に出さない</h5><p><strong>目標: レース中の最速ラップと最遅ラップ（歩行除く）の差を2分&#x2F;km以内に</strong></p>
<ul>
<li>Lap 16の5:53&#x2F;km（HR 176, 277W）は、直前ラップから3分30秒&#x2F;kmの急加速。このVO2max強度のスパイクが筋グリコーゲンの急速な枯渇を招き、以降のペース崩壊を加速させた可能性が高い</li>
<li><strong>レースルール</strong>: 「どんなに下りでも、どんなに気持ちよくても、走行区間のペースは7:00&#x2F;km以上にしない」と決める</li>
<li><strong>練習方法</strong>: ペース走の際にGPSウォッチのペースアラート機能を使い、設定ペース±30秒&#x2F;kmを超えたら振動で通知するよう設定</li>
</ul>
<h5 id="アドバイス5-垂直比を改善するドリルを導入する">アドバイス5: 垂直比を改善するドリルを導入する</h5><p><strong>目標: 垂直比9.55% → 8.5%以下</strong></p>
<ul>
<li>垂直比が高い原因は「ストライドが短いのに上下動は同じ」こと。骨盤の前傾を意識し、重心を前方に送る感覚を養う</li>
<li><strong>Aスキップ</strong>: 20m × 6本を週2回。膝を高く引き上げて前方への推進を意識</li>
<li><strong>壁ドリル</strong>: 壁に手をつき、片脚ずつ膝を引き上げる動作を30回×左右。股関節の屈曲力を鍛える</li>
<li><strong>緩い下り坂（2〜3%勾配）での流し</strong>: 100m × 4本。重力を利用して前方への推進感覚を身体に覚えさせる</li>
</ul>
<h5 id="アドバイス6-パワーメーターの校正とFTP設定を行う">アドバイス6: パワーメーターの校正とFTP設定を行う</h5><p><strong>目標: パワーゾーンが実力を正しく反映する状態にする</strong></p>
<ul>
<li>現在のパワーゾーン設定ではZ5に83%となり、トレーニング指標として機能していない</li>
<li><strong>FTPテスト</strong>: 30分間の全力走（キロ7分を維持できるペース）を行い、その平均パワーの95%をFTPとして設定</li>
<li>おそらくFTP 200〜210W前後になると推測。これを基準にゾーンを再設定すれば、Z2〜Z3中心の分布になるはず</li>
<li>校正後、毎回のランでNP&#x2F;VI をチェックする習慣をつけ、VI 1.05以下を目指す</li>
</ul>
<h5 id="アドバイス7-レース当日の補給戦略を見直す">アドバイス7: レース当日の補給戦略を見直す</h5><p><strong>目標: 総消費2,963 kcalに対して、レース中に600〜800 kcal以上を補給</strong></p>
<ul>
<li>6時間超のレースでは筋グリコーゲン枯渇が不可避。Lap 29（16:07&#x2F;km）やLap 35（12:20&#x2F;km）の大減速はエネルギー切れの可能性</li>
<li><strong>具体策</strong>: 30分ごとにジェル1本（約100 kcal）を摂取。6時間で12本、計1,200 kcal</li>
<li>エイドステーションでの長時間停止を避け、歩きながら補給する練習をロングランで行う</li>
</ul>
<p><strong>まとめ</strong>: あなたの最大の強みは<strong>心肺系の安定性</strong>（Z3に66%滞在、ドリフト-3.1 bpm）です。心肺はすでにサブ5.5を狙えるベースがあります。課題は明確で、<strong>ケイデンス改善（150→170 spm）と筋持久力の強化</strong>の2点に集中すれば、次回のフルマラソンで大幅なタイム短縮が期待できます。まずは3ヶ月間、ケイデンスドリルと筋力補強を継続してください。次のレースでは5時間30分以内を現実的な目標として設定できます。</p>
<p>数値を根拠に「なぜ後半失速したのか」「次に何をすべきか」が具体的に出てくる。「次はこの練習をしよう」と思えるのは、暗闇を走る初心者ランナーにとって、 <strong>何よりの「羅針盤」</strong> になりそうだ。</p>
<hr>
<p>ここからは技術的な実装の話をする。</p>
<h2 id="システム構成">システム構成</h2><figure class="highlight txt"><input type="checkbox" id="code-wrap-mczzbw-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-mczzbw-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">FIT ファイル（バイナリ）</span><br><span class="line">  └─ parser.py</span><br><span class="line">       ├─ records_df   ... 1秒毎タイムシリーズ（2万行超）</span><br><span class="line">       ├─ laps_df      ... ラップ毎集計</span><br><span class="line">       └─ session      ... レース全体サマリー</span><br><span class="line">            │</span><br><span class="line">            ├─ pace_analyzer.py      ペース・スプリット・CV</span><br><span class="line">            ├─ hr_analyzer.py        HR ゾーン・ドリフト・相関</span><br><span class="line">            ├─ form_analyzer.py      ケイデンス・フォームスコア</span><br><span class="line">            ├─ elevation_analyzer.py 標高・GAP</span><br><span class="line">            └─ power_analyzer.py     パワー・VI・デカップリング</span><br><span class="line">                 │</span><br><span class="line">                 └─ agent.py  ── 2パス Claude（claude-opus-4-6）</span><br><span class="line">                       │           Pass1: データアナリスト</span><br><span class="line">                       │           Pass2: エキスパートコーチ</span><br><span class="line">                       │</span><br><span class="line">                       └─ html_report.py  ── Plotly HTML 出力</span><br></pre></td></tr></table></figure>

<p>依存ライブラリ: <code>fitparse</code>, <code>pandas</code>, <code>plotly</code>, <code>click</code></p>
<h2 id="FIT-ファイルのパース">FIT ファイルのパース</h2><p>Garmin デバイスが保存するデータ形式は <code>.fit</code> はバイナリフォーマットのため、テキストエディタでは読めない。<code>fitparse</code> というライブラリを使えば、これを数行で読み込める。このファイルは Garmin Connect の「アクティビティ」画面から手動でダウンロードできる（エクスポート → オリジナル形式）。</p>
<p><code>fitparse</code> というライブラリを使えば、これを数行で読み込める。</p>
<figure class="highlight python"><input type="checkbox" id="code-wrap-mczzbw-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-mczzbw-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> fitparse <span class="keyword">import</span> FitFile</span><br><span class="line"><span class="keyword">import</span> pandas <span class="keyword">as</span> pd</span><br><span class="line"></span><br><span class="line">fitfile = FitFile(<span class="string">&quot;race.fit&quot;</span>)</span><br><span class="line"></span><br><span class="line">records = []</span><br><span class="line"><span class="keyword">for</span> msg <span class="keyword">in</span> fitfile.get_messages(<span class="string">&quot;record&quot;</span>):  <span class="comment"># 1秒毎のデータ</span></span><br><span class="line">    row = &#123;d.name: d.value <span class="keyword">for</span> d <span class="keyword">in</span> msg&#125;</span><br><span class="line">    records.append(row)</span><br><span class="line"></span><br><span class="line">records_df = pd.DataFrame(records)</span><br></pre></td></tr></table></figure>

<p>ただし、<strong>Garmin 固有の仕様にいくつかハマりポイントがある。</strong></p>
<h3 id="GPS-座標はセミサークル単位">GPS 座標はセミサークル単位</h3><p>Garmin は GPS 座標を「度（degrees）」ではなく「セミサークル」という独自単位で保存している。そのまま使うと全く違う座標になってしまう。</p>
<figure class="highlight python"><input type="checkbox" id="code-wrap-mczzbw-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-mczzbw-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># 変換が必要</span></span><br><span class="line">SEMICIRCLE_TO_DEG = <span class="number">180</span> / <span class="number">2</span>**<span class="number">31</span></span><br><span class="line"></span><br><span class="line">records_df[<span class="string">&quot;lat&quot;</span>] = records_df[<span class="string">&quot;position_lat&quot;</span>] * SEMICIRCLE_TO_DEG</span><br><span class="line">records_df[<span class="string">&quot;lon&quot;</span>] = records_df[<span class="string">&quot;position_long&quot;</span>] * SEMICIRCLE_TO_DEG</span><br></pre></td></tr></table></figure>

<h3 id="ケイデンスは片足-SPM">ケイデンスは片足 SPM</h3><p>Garmin のケイデンスデータは「両足」ではなく「片足」の歩数で記録されている。実際のケイデンスを得るには 2 倍が必要だ。</p>
<figure class="highlight python"><input type="checkbox" id="code-wrap-mczzbw-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-mczzbw-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># セッションデータの場合</span></span><br><span class="line">cadence_single = session.get(<span class="string">&quot;avg_running_cadence&quot;</span>)  <span class="comment"># 例: 75</span></span><br><span class="line">cadence_total = cadence_single * <span class="number">2</span>  <span class="comment"># 150 spm が実測値</span></span><br></pre></td></tr></table></figure>

<h3 id="enhanced-speed-と-speed-の使い分け">enhanced_speed と speed の使い分け</h3><p>新しいデバイスでは <code>speed</code> と <code>enhanced_speed</code> の両方のカラムが存在することがある。精度の高い <code>enhanced_speed</code> を優先して使う必要がある。</p>
<figure class="highlight python"><input type="checkbox" id="code-wrap-mczzbw-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-mczzbw-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">speed_col = <span class="string">&quot;enhanced_speed&quot;</span> <span class="keyword">if</span> <span class="string">&quot;enhanced_speed&quot;</span> <span class="keyword">in</span> records_df.columns <span class="keyword">else</span> <span class="string">&quot;speed&quot;</span></span><br><span class="line">records_df[<span class="string">&quot;pace_sec_per_km&quot;</span>] = records_df[speed_col].apply(</span><br><span class="line">    <span class="keyword">lambda</span> s: <span class="number">1000</span> / s <span class="keyword">if</span> s <span class="keyword">and</span> s &gt; <span class="number">0</span> <span class="keyword">else</span> <span class="literal">None</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure>

<h2 id="5-種類の分析ツール">5 種類の分析ツール</h2><p>各ツールは <code>RaceData</code>（DataFrameのラッパー）を受け取り、JSON を返す純粋関数として実装している。</p>
<h3 id="1-ペース分析（pace-analyzer-py）">1. ペース分析（<code>pace_analyzer.py</code>）</h3><p>ラップ毎ペース・前後半スプリット判定・ペース変動係数（CV）を計算する。</p>
<figure class="highlight python"><input type="checkbox" id="code-wrap-mczzbw-6" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-mczzbw-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># ペース変動係数: レースがどれだけ均一に走れたかの指標</span></span><br><span class="line"><span class="comment"># マラソンでは 5% 以下が理想</span></span><br><span class="line">p_series = records_df[<span class="string">&quot;pace_min_per_km&quot;</span>].dropna()</span><br><span class="line">p_series = p_series[p_series &lt; <span class="number">20</span>]  <span class="comment"># 停止など外れ値除去</span></span><br><span class="line">pace_cv = p_series.std() / p_series.mean() * <span class="number">100</span></span><br></pre></td></tr></table></figure>

<p>先ほどの実例レポートでは CV が <strong>28.01%</strong> と算出された。理想の 5% 以下どころか 6 倍近い値で、「非常にペースが不安定なレースだった」という事実を数値として示している。</p>
<h3 id="2-心拍数分析（hr-analyzer-py）">2. 心拍数分析（<code>hr_analyzer.py</code>）</h3><p>Garmin の 5 ゾーン基準（最大心拍の 50&#x2F;60&#x2F;70&#x2F;80&#x2F;90&#x2F;100%）に従ってゾーン分布を算出する。加えて、前後半の平均心拍差（心拍ドリフト）とペースとの相関係数も計算する。</p>
<figure class="highlight python"><input type="checkbox" id="code-wrap-mczzbw-7" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-mczzbw-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">HR_ZONE_BOUNDS = [<span class="number">0.50</span>, <span class="number">0.60</span>, <span class="number">0.70</span>, <span class="number">0.80</span>, <span class="number">0.90</span>, <span class="number">1.00</span>]</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i, name <span class="keyword">in</span> <span class="built_in">enumerate</span>(HR_ZONE_NAMES):</span><br><span class="line">    lo = HR_ZONE_BOUNDS[i] * max_hr</span><br><span class="line">    hi = HR_ZONE_BOUNDS[i + <span class="number">1</span>] * max_hr</span><br><span class="line">    count = ((valid &gt;= lo) &amp; (valid &lt; hi)).<span class="built_in">sum</span>()</span><br><span class="line">    zones[name] = &#123;<span class="string">&quot;seconds&quot;</span>: <span class="built_in">int</span>(count), <span class="string">&quot;percent&quot;</span>: <span class="built_in">round</span>(count / total * <span class="number">100</span>, <span class="number">1</span>)&#125;</span><br></pre></td></tr></table></figure>

<h3 id="3-フォーム分析（form-analyzer-py）">3. フォーム分析（<code>form_analyzer.py</code>）</h3><p>ケイデンス・上下動・接地時間・垂直比を前後半で比較する。さらに、独自の <strong>フォームスコア（100 点満点）</strong> を算出して「どこをどう改善すべきか」を定量化している。閾値はランニングエコノミー研究で一般的に参照される市民ランナー向けの目安値（ケイデンス 170〜180 spm、垂直比 8% 以下、接地時間 250ms 以下が効率的とされる）をもとに設定した。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">score = <span class="number">100</span></span><br><span class="line"><span class="keyword">if</span> cadence_total &lt; <span class="number">160</span>:</span><br><span class="line">    score -= <span class="number">20</span>  <span class="comment"># ケイデンス不足</span></span><br><span class="line"><span class="keyword">elif</span> cadence_total &lt; <span class="number">170</span>:</span><br><span class="line">    score -= <span class="number">10</span>  <span class="comment"># ケイデンスやや低い</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> vert_ratio &gt; <span class="number">9.5</span>:</span><br><span class="line">    score -= <span class="number">15</span>  <span class="comment"># 垂直比が高い（推進効率が悪い）</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> stance_time &gt; <span class="number">300</span>:</span><br><span class="line">    score -= <span class="number">15</span>  <span class="comment"># 接地時間が長い</span></span><br></pre></td></tr></table></figure>

<h3 id="4-高低差・GAP-分析（elevation-analyzer-py）">4. 高低差・GAP 分析（<code>elevation_analyzer.py</code>）</h3><p>コースの起伏を補正した「グレード調整ペース（Grade Adjusted Pace）」を計算する。「実際のペースが遅かったのはコースのせいか、実力のせいか」を分離できる。</p>
<h3 id="5-ランニングパワー分析（power-analyzer-py）">5. ランニングパワー分析（<code>power_analyzer.py</code>）</h3><p>変動指数（Variability Index &#x3D; 正規化パワー ÷ 平均パワー）とパワー&#x2F;心拍デカップリングを計算する。VI が 1.05 以上だと、ペースの不均一によるエネルギーコストの増加を意味する。</p>
<h2 id="Claude-Code-CLI-をスクリプトから呼び出す">Claude Code CLI をスクリプトから呼び出す</h2><p>AIの呼び出しはAPIではなくCLIを使った。</p>
<p><strong>最初の実装では <code>Claude API</code> Python SDK を直接使っていた。</strong></p>
<figure class="highlight python"><input type="checkbox" id="code-wrap-mczzbw-8" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-mczzbw-8" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># 最初の実装（APIキー方式）</span></span><br><span class="line"><span class="keyword">import</span> anthropic</span><br><span class="line">client = anthropic.Anthropic(api_key=os.environ[<span class="string">&quot;ANTHROPIC_API_KEY&quot;</span>])</span><br><span class="line">response = client.messages.create(model=<span class="string">&quot;claude-opus-4-6&quot;</span>, ...)</span><br></pre></td></tr></table></figure>

<p>しかし問題があった。5 つの分析ツールの出力（合計で数千トークン）をプロンプトに詰め込むため、フルマラソン 1 本を分析するたびに無視できない API 料金が発生する。しかも Claude Code のサブスクには既に加入していた。二重コストだ。</p>
<p><strong>そこで思いついたのが、Claude Code CLI（<code>claude</code> コマンド）を <code>subprocess</code> で呼び出す方式に切り替えることだった。</strong></p>
<figure class="highlight python"><input type="checkbox" id="code-wrap-mczzbw-9" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-mczzbw-9" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> subprocess</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">_run_claude</span>(<span class="params">prompt: <span class="built_in">str</span>, model: <span class="built_in">str</span></span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">    <span class="comment"># ANTHROPIC_API_KEY を環境変数から除外することで</span></span><br><span class="line">    <span class="comment"># API キー課金ではなくサブスク認証を使わせる</span></span><br><span class="line">    env = &#123;k: v <span class="keyword">for</span> k, v <span class="keyword">in</span> os.environ.items() <span class="keyword">if</span> k != <span class="string">&quot;ANTHROPIC_API_KEY&quot;</span>&#125;</span><br><span class="line"></span><br><span class="line">    result = subprocess.run(</span><br><span class="line">        [<span class="string">&quot;claude&quot;</span>, <span class="string">&quot;-p&quot;</span>, prompt, <span class="string">&quot;--output-format&quot;</span>, <span class="string">&quot;text&quot;</span>, <span class="string">&quot;--model&quot;</span>, model],</span><br><span class="line">        capture_output=<span class="literal">True</span>,</span><br><span class="line">        text=<span class="literal">True</span>,</span><br><span class="line">        check=<span class="literal">True</span>,</span><br><span class="line">        timeout=<span class="number">300</span>,</span><br><span class="line">        env=env,</span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">return</span> result.stdout.strip()</span><br></pre></td></tr></table></figure>

<p>ポイントは <code>ANTHROPIC_API_KEY</code> を環境変数から除いて <code>claude</code> CLI を呼ぶこと。こうすると CLI はサブスクリプション経由の認証を使うため、API の従量課金が発生しない。</p>
<p>Claude Code CLIはパイプやスクリプトからの呼び出しを前提に設計されており、公式ドキュメントにも以下のような使用例が掲載されている。</p>
<figure class="highlight bash"><input type="checkbox" id="code-wrap-mczzbw-10" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-mczzbw-10" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="built_in">tail</span> -f app.log | claude -p <span class="string">&quot;alert me if you see anomalies&quot;</span></span><br><span class="line">git diff main | claude -p <span class="string">&quot;review for security issues&quot;</span></span><br></pre></td></tr></table></figure>

<p>subprocessからの呼び出しはこの延長線上にある。<br>個人利用の範囲であれば、Claude Codeサブスク加入者は追加費用なしで使える。</p>
<h2 id="2-パス-AI-コーチング設計">2 パス AI コーチング設計</h2><p>最初は 1 回の API 呼び出しで「データ解析 + コーチング文章の生成」をまとめてやらせていた。しかし品質が安定しなかった。データの読み取りに気を取られて文章が散漫になったり、逆に文章を意識しすぎて数値の読み違いが起きたりする。</p>
<p>そこで <strong>2 パスに分離した。</strong></p>
<h3 id="Pass-1-データアナリスト">Pass 1: データアナリスト</h3><p>5 ツールの生 JSON をまとめて渡し、「コーチングレポートを書く前段階として重要な知見を構造化して抽出してください」とだけ指示する。文章生成はしない。</p>
<figure class="highlight txt"><input type="checkbox" id="code-wrap-mczzbw-11" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-mczzbw-11" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">## 出力形式（必ずこの構造で）</span><br><span class="line"></span><br><span class="line">### 🔴 問題点・要改善指標</span><br><span class="line">- 数値根拠と共に箇条書き</span><br><span class="line"></span><br><span class="line">### 🟢 優れている点・強み</span><br><span class="line">- 数値根拠と共に箇条書き</span><br><span class="line"></span><br><span class="line">### 📊 前後半で大きく変化した指標</span><br><span class="line">- 変化量と方向性を数値で</span><br><span class="line"></span><br><span class="line">### ❓ データから読み取れる根本原因の仮説</span><br><span class="line">- なぜそのパターンが生じたか、競技経験に基づく推論</span><br></pre></td></tr></table></figure>

<h3 id="Pass-2-エキスパートコーチ">Pass 2: エキスパートコーチ</h3><p>Pass 1 の出力（構造化された知見）と生データの両方を渡し、「実践的な日本語コーチングレポートを作成してください」と指示する。</p>
<figure class="highlight python"><input type="checkbox" id="code-wrap-mczzbw-12" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-mczzbw-12" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">pass2_prompt = (</span><br><span class="line">    <span class="string">f&quot;<span class="subst">&#123;_COACH_PROMPT&#125;</span>\n\n&quot;</span></span><br><span class="line">    <span class="string">&quot;## Pass 1: データアナリストによる知見\n\n&quot;</span></span><br><span class="line">    <span class="string">f&quot;<span class="subst">&#123;analyst_findings&#125;</span>\n\n&quot;</span>  <span class="comment"># Pass 1 の出力</span></span><br><span class="line">    <span class="string">f&quot;<span class="subst">&#123;data_section&#125;</span>&quot;</span>           <span class="comment"># 生データも念のため渡す</span></span><br><span class="line">)</span><br><span class="line">coaching_comment = _run_claude(pass2_prompt, MODEL)</span><br></pre></td></tr></table></figure>

<p>この分離によって、Pass 2 のコーチは「既に整理された知見」を受け取った上で文章を書ける。結果として「数値を積極的に引用した、明日から何をすればいいかがわかるレポート」が安定して生成されるようになった。</p>
<h2 id="まとめと今後">まとめと今後</h2><p>ソースコードはGitHubで公開しています。</p>
<p>garmin-analyst</p>
<p>技術スタックは Python + fitparse + pandas + Plotly + Claude CLI だけ。特別なインフラも不要で、ローカルで完結する。</p>
<p>このツールを作る前と後では、レースの振り返り方が変わった。「なんとなく後半きつかった」が「CV 28%・心拍ドリフト +12bpm・接地時間後半+18ms」という数値に変わり、次の練習で何を優先すべきかが具体的になる。土浦のレースも、ペース急変→腹痛という因果がデータ上ではっきり見えた。</p>
<p>FIT ファイルのパースや Garmin 独自単位の変換、2 パス設計といった地味な実装の積み重ねが、実用レベルのツールを作る上での本質だった。</p>
<h3 id="今後やりたいこと">今後やりたいこと</h3><p>現状はレース1本ずつの単発分析だが、次のステップとして <strong>複数レースの蓄積・横断分析</strong> を実装したい。レースごとの HTML レポートを SQLite などに集約し、「前回と比べてケイデンスは改善されたか」「ペース CV の推移はどうか」をグラフで追えるようにすることで、単なるレビューツールから<strong>成長トラッキングツール</strong>へと発展できる。</p>
<p>また現在の AI コーチングは1回のレースデータしか参照できないが、過去レースの傾向を踏まえた「あなたの慢性的な課題はここです」という長期視点のアドバイスも面白そうだ。ランナーの個人データが蓄積されるほど分析の解像度が上がる、という構造はこのツールの本質的な強みになりうる。</p>
<p>同じようにGarminのデータをこねくり回している方や、FITファイルのパースで詰まった経験がある方はコメントで教えてください。あと「こういう分析も欲しい」というアイデアも歓迎です。</p>
]]></content>
    <summary type="html">ランニングにハマって、Garmin デバイスを使って走っている。走り終わったあと、Garmin Connect のアプリを開いてデータを眺めるのが楽しい。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="Claude" scheme="https://future-architect.github.io/tags/Claude/"/>
    <category term="Python" scheme="https://future-architect.github.io/tags/Python/"/>
    <category term="スポーツ" scheme="https://future-architect.github.io/tags/%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%84/"/>
  </entry>
  <entry>
    <title>技術書は「読む」から「呼び出す」へ：ACM経由のO'Reilly Online Learning契約とMCP連携の実践</title>
    <link href="https://future-architect.github.io/articles/20260513a/"/>
    <id>https://future-architect.github.io/articles/20260513a/</id>
    <published>2026-05-12T15:00:00.000Z</published>
    <updated>2026-05-12T15:00:00.000Z</updated>
    <author><name>棚井龍之介</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260513a/top.png" alt="" width="1200" height="670">

<h2 id="はじめに">はじめに</h2><p>こんにちは。FutureVulsの棚井です。</p>
<p>2026年4月16日、アリゾナ州スコッツデールで開催された <strong>VulnCon 2026</strong> に登壇しました。タイトルは <code>The CVE Blind Spot: Defeating &quot;Hidden EOLs&quot; with Code Diet</code> で、CVEの限界、Hidden EOL、Code Diet、uzomuzo-oss をテーマに発表しました。登壇の詳細は FutureVuls Blogの登壇報告記事 をご覧ください。</p>
<p>現地で CVE Program、FIRST、NVD、各種SCAツールのコアメンバーと直接議論できた経験を経て、自分の学習投資の量と質を、もう一段階引き上げる必要があると考えるようになりました。</p>
<p>帰国後すぐに、本腰を入れて技術書サブスクの導入を検討し始めました。色々比較した結果、いわゆる「オライリーサブスク」 ── 正確には <strong>ACM (Association for Computing Machinery) Professional Membership + Skills Bundle Add-On 経由での O’Reilly Online Learning 契約</strong> に行き着きました。</p>
<p>直契約だと年 ¥77,000〜91,000 かかる O’Reilly Online Learning が、ACM経由なら <strong>年¥24,980（月¥2,082換算）</strong> で済みます。直契約比で約 <strong>¥58,000の節約（実に 70%オフ）</strong> です。</p>
<p>オライリー本を年に数冊買うだけで元が取れる金額のため、自己投資の選択肢として、これは見逃せないと判断しました。</p>
<p>本記事では、私が2026年4月に実際に加入・運用してみた経験をベースに、以下の内容を網羅します。</p>
<ul>
<li>ACM経由ルートの価格構造</li>
<li>加入手順の完走ガイド</li>
<li>加入後の設定とアクセス範囲</li>
<li>Claude Code から O’Reilly MCP Server を使う</li>
</ul>
<p>想定読者は、<strong>自己投資としての技術書サブスクを検討している社内外のエンジニア</strong> です。所要時間としては、加入手続き自体は <strong>約1時間</strong> で完了します（私の実測）。</p>
<blockquote>
<p>本記事は私個人の調査と利用体験に基づくものであり、フューチャー株式会社の公式見解や契約方針を示すものではありません。本記事で扱う ACM 経由の利用は、ACM 個人会員に紐づく Skills Bundle Add-On を通じたものです。O’Reilly Online Learning の法人プラン（Enterprise）とは別契約ですので、業務目的での導入を検討される場合は、別途 O’Reilly 法人向けの規約・契約条件をご確認ください。なお、ACM Skills Bundle 経由のO’Reilly Online Learningは個人プランのため、<strong>チームでの共有や複数人での利用は規約違反となります</strong>。利用にあたっては各社の利用規約をご自身でご確認のうえ、自己責任にてご契約ください。</p>
</blockquote>
<h2 id="ACM経由ルートの全体像と価格構造">ACM経由ルートの全体像と価格構造</h2><h3 id="なぜ「ACM経由」が安いのか">なぜ「ACM経由」が安いのか</h3><p>ACM (Association for Computing Machinery) は1947年設立、計算機科学分野で世界最大規模の国際学会です。論文誌『Communications of the ACM』の発行元と言えば、ピンと来る方も多いかもしれません。</p>
<p>そのACMの会員特典のひとつに <strong>Skills Bundle Add-On</strong> というオプションがあり、これを追加すると <strong>O’Reilly Online Learning が会員特典として利用できる</strong> ようになります。ACM が学会員向けの教育プログラムとして O’Reilly Online Learning を組み込んでいる構造のため、結果として ACM 経由のセット契約のほうが、O’Reilly を直接契約するより年額の支払いが低くなります。</p>
<h3 id="4プラン比較">4プラン比較</h3><p>私が検討した主要4プランの実費比較がこちらです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>プラン</th>
<th>USD&#x2F;年</th>
<th>JPY換算</th>
<th>月単価</th>
</tr>
</thead>
<tbody><tr>
<td>O’Reilly 直契約（個人月額の年換算）</td>
<td>$588</td>
<td>約¥91,000</td>
<td>約¥7,600</td>
</tr>
<tr>
<td>O’Reilly 直契約（年額一括15%オフ）</td>
<td>$499</td>
<td>約¥77,000</td>
<td>約¥6,400</td>
</tr>
<tr>
<td><strong>ACM Pro + Skills Bundle（初年度25%オフ）</strong></td>
<td><strong>$150</strong></td>
<td><strong>¥24,980（実測）</strong></td>
<td><strong>¥2,082</strong></td>
</tr>
<tr>
<td>ACM Pro + Skills Bundle（2年目以降）</td>
<td>$174</td>
<td>約¥27,000</td>
<td>約¥2,250</td>
</tr>
</tbody></table></div>
<p><strong>ACM Pro + Skills Bundle の初年度が最安</strong> です。O’Reilly直契約の年額プランと比較すると、差額は <strong>-$349 (-¥58,000)、約70%オフ</strong> になります。</p>
<p>上記の料金は私が2026年4月時点で確認した内容です。料金体系・キャンペーン条件は予告なく変更される可能性があるため、申込前には必ず以下の公式情報をご確認ください。</p>
<ul>
<li>O’Reilly Online Learning 個人プラン料金: https://www.oreilly.com/online-learning/pricing.html</li>
<li>ACM Skills Bundle Add-On 概要: https://learning.acm.org/skills-bundle</li>
<li>ACM Skills Bundle に関するFAQ（O’Reilly関連）: https://learning.acm.org/faq/oreilly-faqs</li>
<li>ACM Membership Benefits: https://www.acm.org/membership/membership-benefits</li>
</ul>
<h3 id="25-オフキャンペーンの入り口URL">25%オフキャンペーンの入り口URL</h3><p>ACM Professional Membershipの 25%オフキャンペーンは、初年度限定のキャンペーンです。</p>
<p>「acm membership promotion」などで検索して辿り着けます。重要な点としてACM公式トップページから普通に進むとこのキャンペーンURLには遷移しません。</p>
<img src="/images/2026/20260513a/2c8ac129-bde9-4611-9690-74a7ef935020.png" alt="" width="600" height="194" loading="lazy">

<h3 id="¥24-980の内訳（為替実測値）">¥24,980の内訳（為替実測値）</h3><div class="scroll"><table>
<thead>
<tr>
<th>項目</th>
<th>USD</th>
<th>備考</th>
</tr>
</thead>
<tbody><tr>
<td>ACM Professional Membership（25%オフ）</td>
<td>$75</td>
<td>通常$99</td>
</tr>
<tr>
<td>ACM Skills Bundle Add-On</td>
<td>$75</td>
<td>O’Reilly Online Learning 利用権</td>
</tr>
<tr>
<td><strong>小計</strong></td>
<td><strong>$150</strong></td>
<td></td>
</tr>
<tr>
<td>適用為替レート</td>
<td>1 USD &#x3D; ¥166.53</td>
<td>決済時のレート（2026年4月実測。海外決済手数料を含む実効レートのため、同時期の市場レートよりやや高め）</td>
</tr>
<tr>
<td><strong>JPY実費</strong></td>
<td><strong>¥24,980</strong></td>
<td>月単価 ¥2,082</td>
</tr>
</tbody></table></div>
<h3 id="2年目以降の判断材料">2年目以降の判断材料</h3><p>冒頭の比較表の通り、2年目以降は 25%オフが適用されないため、$174（約¥27,000、月¥2,250換算）に上昇します。それでも直契約年額の約3分の1で済むため経済合理性は維持されますが、私は自動更新を OFF にして満了日に自然失効する設定を選びました。</p>
<h2 id="加入手順を完走する">加入手順を完走する</h2><p>加入手順は、大きく分けて以下の5フェーズで進みます。私はブログ向けのスクリーンショット撮影を挟みながら進めて約45分でしたが、撮影なしの実作業だけならもっと短く済むはずです。</p>
<ol>
<li>加入フォーム入力（基本情報まで） — 25%オフURLからプラン選択、基本情報入力まで</li>
<li>決済 — Payment画面で内訳確認 → 決済確定</li>
<li>ACM Web Account 作成 — ログイン用アカウントとパスワード設定</li>
<li>アクティベーション — 確認メール経由でアカウント有効化</li>
<li>O’Reilly Online Learning への初ログイン — SSOで O’Reilly ダッシュボードに到達</li>
</ol>
<p>各フェーズに注意点が点在するため、それらに沿って整理します。</p>
<h3 id="Phase-1-加入フォーム入力（基本情報まで）">Phase 1: 加入フォーム入力（基本情報まで）</h3><h4 id="Step-1-25-オフキャンペーンURLにアクセス">Step 1. 25%オフキャンペーンURLにアクセス</h4><p>前節で示した25%オフURLからフォームを開きます。ACM公式トップページから普通に進むとこのページには到達しませんので、URLは必ず控えておいてください。</p>
<figure class="highlight text"><input type="checkbox" id="code-wrap-oo6ybh-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-oo6ybh-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">https://services.acm.org/public/qj/keep_inventing/qjprofm_control.cfm?promo=DA5MAA</span><br></pre></td></tr></table></figure>

<h4 id="Step-2-プラン選択と-Skills-Bundle-Add-On-のチェック">Step 2. プラン選択と Skills Bundle Add-On のチェック</h4><p>Quick Join フォーム上で <code>ACM Professional Membership – $75</code>（25%オフ適用済み価格）と、<code>ACM Skills Bundle Add-On – $75</code> の両方を選択します。<strong>Skills Bundle のチェックを外したまま進むと、O’Reilly Online Learning へのアクセス権が付かない</strong> ので注意してください。合計が <strong>$150</strong> になっていることを確認してから次に進みます。</p>
<h4 id="Step-3-基本情報入力">Step 3. 基本情報入力</h4><p>入力項目は以下のとおりです。</p>
<ul>
<li>First Name &#x2F; Last Name</li>
<li>Email（領収書・アクティベーションメールの送信先）</li>
<li>Country&#x2F;Region: <code>Japan</code></li>
</ul>
<h3 id="Phase-2-決済">Phase 2: 決済</h3><h4 id="Step-4-Payment-Information-画面で内訳を確認">Step 4. Payment Information 画面で内訳を確認</h4><p>決済画面では、プランごとの単価と合計金額が表で表示されます。Skills Bundle 込みで小計 $150 になっていることを確認します。</p>
<p>ここで重要なのが <strong>Automatic Renewal Program のチェックボックス</strong> です。<strong>チェックを外したまま</strong> 進めると、翌年の自動更新が無効になります。私は来年あらためて契約継続を判断したいため、ここでチェックを外したまま進めました。</p>
<img src="/images/2026/20260513a/c3321321-a4dd-44ab-894f-4fd676f7ac8f.png" alt="" width="600" height="315" loading="lazy">

<h4 id="Step-5-決済確定と-Membership-Number-Order-Number-の取得">Step 5. 決済確定と Membership Number &#x2F; Order Number の取得</h4><p>決済が完了すると <code>THANK YOU FOR SHAPING THE FUTURE OF COMPUTING!</code> という画面に到達し、以下が同時に発行されます。</p>
<ul>
<li><strong>Membership Number</strong>（会員番号）</li>
<li><strong>Order Number</strong>（注文番号、領収書照会用）</li>
</ul>
<img src="/images/2026/20260513a/eb0f3b6c-6320-461a-9f45-fdd461ed18e4.png" alt="" width="600" height="208" loading="lazy">

<p>決済確定から数分後には、<code>acmhelp@acm.org</code> から領収書メール（件名 <code>ACM Online Purchase</code>）が届きます。</p>
<h3 id="Phase-3-ACM-Web-Account-作成">Phase 3: ACM Web Account 作成</h3><p>ここまでで「ACM会員」としての登録は完了していますが、<strong>ログイン用の Web Account はまだ未作成</strong> です。引き続き、Web Account の作成に進みます。</p>
<h4 id="Step-6-Create-your-own-Web-Account-リンクから作成画面へ">Step 6. Create your own Web Account リンクから作成画面へ</h4><p>決済完了後の画面から <code>Create your own Web Account</code> リンクをクリックします。<code>accounts.acm.org</code> の別画面に遷移します。</p>
<h4 id="Step-7-メールアドレス入力">Step 7. メールアドレス入力</h4><p>加入時に登録したEmailアドレスと <strong>完全一致</strong> させる必要があります。<code>is on file, but has no associated ACM Account</code> という表示が出れば正常です（&#x3D;「会員登録はされているが、ログイン用アカウントはまだ無い」状態）。</p>
<img src="/images/2026/20260513a/b48eddd4-47a9-429c-a403-7856c13a926a.png" alt="" width="1200" height="872" loading="lazy">

<h4 id="Step-8-パスワードと-Security-Question-の設定">Step 8. パスワードと Security Question の設定</h4><p>ACM Web Account のパスワードには以下の制限があります。一般的なWebサービスのパスワード仕様を前提にすると、後からログインできなくなる注意点が複数あります。</p>
<ul>
<li>使える文字: <strong>英字、数字、<code>-</code>、<code>_</code>、<code>.</code> のみ</strong></li>
<li><strong><code>!@#$%&amp;*</code> などの記号は使えない</strong></li>
<li>スペース不可</li>
<li><strong>最大26文字、超過分はサイレント切り捨て</strong></li>
</ul>
<p>最後のサイレント切り捨てが特に注意です。事前に手元で生成した30文字以上の強パスワードを貼り付けると、見た目には登録できているように見えても、末尾が無言で切られた状態で保存されてしまいます。後でログインしようとすると、自分が控えていたパスワードと一致せずに通らない、という事態になります。</p>
<p>なお、同じ画面で Security Question &#x2F; Security Answer の登録も求められます。Security Answer は大文字小文字を区別するため、回答文字列も後から正確に再現できる形で控えておく必要があります。</p>
<img src="/images/2026/20260513a/d6ac9a78-f4de-4846-bada-b716300fa838.png" alt="" width="536" height="812" loading="lazy">

<h4 id="Step-9-Username-の自動生成">Step 9. Username の自動生成</h4><p>パスワード設定が完了すると、Username が自動生成されます。</p>
<p>このUsernameは、ACM転送メール（<code>&lt;username&gt;@acm.org</code>）の宛先にもなる識別子です。</p>
<img src="/images/2026/20260513a/a35614d4-f922-46c3-b3aa-84492401edcb.png" alt="" width="400" height="251" loading="lazy">

<h3 id="Phase-4-アクティベーション">Phase 4: アクティベーション</h3><h4 id="Step-10-確認メールのアクティベーションリンクをクリック">Step 10. 確認メールのアクティベーションリンクをクリック</h4><p>ACMからアクティベーション確認メールが届きます（件名 <code>Confirm Your ACM Account</code> 等）。⚠️ このステップを飛ばすと、せっかく作った Web Account でログインできないので必ず実行してください。</p>
<h4 id="Step-11-アクティベーション完了画面の確認">Step 11. アクティベーション完了画面の確認</h4><p>リンクをクリックすると、<code>Your ACM Account, with username &lt;username&gt;, is now ready for you to use.</code> という画面に到達します。これで「ACM会員」かつ「Web Account 保有者」の状態が揃い、ACM側の作業は完了です。</p>
<h3 id="Phase-5-O’Reilly-Online-Learning-への初ログイン">Phase 5: O’Reilly Online Learning への初ログイン</h3><h4 id="Step-12-O’Reilly-SSO-専用URLにアクセス">Step 12. O’Reilly SSO 専用URLにアクセス</h4><p>ACM経由のO’Reilly Online Learning には、<strong>ACM会員専用のSSO URL</strong> からアクセスします。</p>
<figure class="highlight text"><table><tr><td class="code"><pre><span class="line">https://go.oreilly.com/acm</span><br></pre></td></tr></table></figure>

<p>このURLにアクセスすると、ACM SSO ログイン画面に自動リダイレクトされます。</p>
<h4 id="Step-13-ACM-Username-Password-でログイン">Step 13. ACM Username &#x2F; Password でログイン</h4><p>Username（または <code>&lt;username&gt;@acm.org</code>）と、Step 8 で設定したパスワードでログインします。ACM側で認証が成功すると、O’Reilly に戻されます。初回ログイン時に O’Reilly 側のアカウントが自動的にプロビジョニングされます。</p>
<h4 id="Step-14-O’Reilly-Learning-ダッシュボード到達">Step 14. O’Reilly Learning ダッシュボード到達</h4><p><code>Welcome! Let&#39;s get started.</code> の画面に到達すれば、O’Reilly Online Learning のダッシュボードが使える状態になります。</p>
<img src="/images/2026/20260513a/07-oreilly-login.png" alt="07-oreilly-login.png" width="1200" height="930" loading="lazy">

<p>ここまでで <strong>6万冊以上の技術書・動画ライブラリへのアクセス可能</strong> になりました。</p>
<p>また、<code>&lt;username&gt;@acm.org</code> での SSO ログインにより、スマホアプリからも O’Reilly 本が読み放題になります。</p>
<img src="/images/2026/20260513a/79b22982-d5f8-4492-bed6-71e3ea50c672.png" alt="" width="1200" height="1243" loading="lazy">

<h2 id="加入後の設定">加入後の設定</h2><p>加入手続きが完了したら、O’Reilly側とACM側で <strong>3つの初期設定</strong> を済ませておくと、その後の運用が楽になります。あわせて、ACM経由で <strong>使えないコンテンツの範囲</strong> も把握しておきます。</p>
<h3 id="言語設定で日本語コンテンツも検索結果に出すようにする">言語設定で日本語コンテンツも検索結果に出すようにする</h3><p>O’Reilly Online Learning の検索は、デフォルトでは英語コンテンツを優先します。日本語の邦訳書籍も検索結果に出したい場合は、<code>Settings → Language Preferences</code> で、<code>English</code> 単独から <code>English + Japanese</code> の併用に変更します。</p>
<p>これだけで、O’Reilly Japan 関連の邦訳書籍（『ソフトウェアサプライチェーンセキュリティ』邦訳版や『侵入技術入門 ―bashで学ぶ攻撃者の技法』など）も検索結果に登場するようになります。日本語キーワードでの検索もここで効くようになります。</p>
<img src="/images/2026/20260513a/d5a8af10-dc37-44ee-afec-9c45a8abb8cf.png" alt="" width="600" height="213" loading="lazy">

<h3 id="Email-Preferences-で不要な通知を切る">Email Preferences で不要な通知を切る</h3><p>ACM経由のSkills Bundleでは、O’Reilly Live events &#x2F; Live courses &#x2F; Certification prep などアクセス権の付かないコンテンツが一部存在します。これらに関する通知が届くと不要なノイズになるため、Email Preferences でオフにしておきます。</p>
<h3 id="自動更新を-OFF-にして解約忘れを防ぐ">自動更新を OFF にして解約忘れを防ぐ</h3><p>ACM Professional Membership は、初年度のみ 25%オフが適用され $150（¥24,980 &#x2F; 月¥2,082換算）で済みますが、2年目以降は $174（約¥27,000、月¥2,250換算）に上昇します。Automatic Renewal Program が ON のままだと、翌年もこの価格で自動課金され続けるため、「使わなくなっていたのに気づかず継続課金されていた」という事態を避けたい場合は OFF にしておくのが安全です。</p>
<p>私の場合は、来年あらためて契約継続の要否を判断するため、自動更新を OFF にして満了日にそのまま自然失効する設定にしました。</p>
<p>更新ON&#x2F;OFFの現在状態は、以下の画面フローで確認・変更できます。</p>
<figure class="highlight text"><input type="checkbox" id="code-wrap-oo6ybh-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-oo6ybh-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">ACM ログイン</span><br><span class="line">  └ myACM</span><br><span class="line">      └ Go to myACM</span><br><span class="line">          └ myACM Member Portal</span><br><span class="line">              └ my Wallet → &quot;Check your current settings&quot;</span><br><span class="line">                  └ ACM Wallet Information</span><br></pre></td></tr></table></figure>

<p><code>ACM Wallet Information</code> 画面で、Auto-Renew のチェックボックスを外しておけば、満了日にそのまま自然失効する形になります。</p>
<h3 id="ACM経由で利用できない範囲">ACM経由で利用できない範囲</h3><p>ACM Skills Bundle 経由で O’Reilly Online Learning を使う場合、O’Reilly 直契約と比べていくつかのコンテンツが除外されています。事前に把握しておかないと「あれが見られない」と気付くタイミングが遅れるので、ここで一度整理しておきます。</p>
<p>公式FAQ準拠で、ACM経由では「アクセスできないもの」は以下のとおりです。</p>
<ul>
<li>Live events &#x2F; Live courses: リアルタイム配信のオンラインイベント・コース</li>
<li>Certification prep materials and exams: 各種認定資格の対策コンテンツと模試</li>
<li>Superstream recordings: 過去に開催された Superstream イベントのアーカイブ</li>
<li>Interactive labs &#x2F; sandboxes: ハンズオン用の対話型ラボ環境</li>
</ul>
<p>逆にいえば、それ以外の6万冊以上の電子書籍・動画・カンファレンス録画・プレイリスト・AI Mode（Answers）・MCP連携は通常通り利用できます。</p>
<p>公式の最新範囲は以下で確認できます。</p>
<ul>
<li>ACM Skills Bundle FAQ: https://learning.acm.org/faq/oreilly-faqs</li>
</ul>
<h2 id="Claude-Code-から-O’Reilly-MCP-Server-を使う">Claude Code から O’Reilly MCP Server を使う</h2><p>加入後に便利なのが、O’Reilly 公式の MCP Server を Claude Code に接続する設定です。コーディング中の自然な流れで関連書籍を引き寄せられるようになり、技術書サブスクが「読み放題」から「呼び出し放題」に近い感触になります。</p>
<p>ただし2026年4月時点では機能が限定的で、Web版 AI Mode（Answers）と同等の自然言語Q&amp;Aは未実装です。タイトルで掲げた「呼び出す」体験のうち、現状到達できているのはキーワード検索とメタデータ取得までで、自然言語Q&amp;Aの本格的な「呼び出し」はもう一段階先の話です。現状の使いどころと、機能拡充への期待を分けて整理します。</p>
<h3 id="O’Reilly-MCP-Server-の概要">O’Reilly MCP Server の概要</h3><p>MCP (Model Context Protocol) は、LLMクライアントから外部サービスのツールやデータに統一プロトコルで接続するための仕様です。O’Reilly は2025年11月19日に公式 MCP Server を発表しました。当初は法人顧客（Enterprise）向けの限定提供でしたが、2026年4月にACM Skills Bundle加入者にも提供範囲が拡大され、ACM個人会員からも利用できるようになりました。Claude Code &#x2F; Claude Desktop &#x2F; Cursor &#x2F; VS Code など主要な MCP対応クライアントから接続できます。</p>
<p>接続情報・参考リンクは以下のとおりです。</p>
<ul>
<li>エンドポイント: <code>https://api.oreilly.com/api/content-discovery/v1/mcp/</code></li>
<li>認証: Bearer Token（O’Reilly Web UI から発行）</li>
<li>公式ドキュメント: O’Reilly MCP Server</li>
<li>O’Reilly公式プレスリリース（2025年11月19日）: https://www.oreilly.com/pub/pr/3476</li>
<li>ACM公式アナウンス「O’Reilly MCP Server Now Part of ACM Skills Bundle」: https://www.acm.org/articles/bulletins/2026/april/oreilly-mcp-server</li>
</ul>
<h3 id="Claude-Code-への登録手順">Claude Code への登録手順</h3><h4 id="Step-1-MCP-Token-の発行">Step 1. MCP Token の発行</h4><p>O’Reilly Online Learning にログインした状態で、<code>Settings → MCP Tokens</code> 画面を開きます。<code>Generate token</code> ボタンから新しいトークンを発行できます。トークン名は用途別に管理しやすいよう <code>claude-code-local</code> のような分かりやすい文字列にしておくのがおすすめです。</p>
<img src="/images/2026/20260513a/bc80d882-65cc-45fd-add4-c6a577260106.png" alt="" width="600" height="194" loading="lazy">

<p>発行されたトークン文字列は、その場でしか表示されないため、必ずその場で控えて安全に保管してください。</p>
<h4 id="Step-2-Claude-Code-への登録コマンド">Step 2. Claude Code への登録コマンド</h4><p>ターミナルから以下のコマンドを実行します。</p>
<figure class="highlight bash"><input type="checkbox" id="code-wrap-oo6ybh-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-oo6ybh-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">claude mcp add --scope user --transport http oreilly \</span><br><span class="line">  https://api.oreilly.com/api/content-discovery/v1/mcp/ \</span><br><span class="line">  --header <span class="string">&quot;Authorization: Bearer &#123;TOKEN&#125;&quot;</span></span><br></pre></td></tr></table></figure>

<p><code>&#123;TOKEN&#125;</code> の部分は Step 1 で発行したトークン文字列に置き換えます。</p>
<p>このコマンドは <strong>user スコープ</strong> で登録されるため、<code>~/.claude.json</code> に書き込まれ、全プロジェクトの Claude Code から利用可能になります。プロジェクトごとに個別登録する必要はありません。</p>
<h4 id="Step-3-接続確認">Step 3. 接続確認</h4><p>登録できたら、以下のコマンドで接続状態を確認します。</p>
<figure class="highlight console"><input type="checkbox" id="code-wrap-oo6ybh-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-oo6ybh-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">claude mcp list</span></span><br><span class="line">oreilly: https://api.oreilly.com/api/content-discovery/v1/mcp/ (HTTP) - ✓ Connected</span><br></pre></td></tr></table></figure>

<p><code>✓ Connected</code> が表示されれば接続成功です。</p>
<h3 id="提供ツール-search-oreilly-content-の仕様">提供ツール <code>search-oreilly-content</code> の仕様</h3><p>接続が完了すると、Claude Code から以下のツールが呼び出せるようになります。</p>
<ul>
<li>ツール名: <code>mcp__oreilly__search-oreilly-content</code></li>
<li>機能: 蔵書検索（書籍・動画・記事・コース等）</li>
</ul>
<p>このツールは8つのパラメータで検索条件を絞り込めます。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>パラメータ</th>
<th>型</th>
<th>主な用途</th>
</tr>
</thead>
<tbody><tr>
<td><code>query</code></td>
<td>string</td>
<td>検索キーワード（最大2,500文字、boolean構文や質問形式は不可）</td>
</tr>
<tr>
<td><code>n_items</code></td>
<td>integer</td>
<td>取得件数（デフォルト 5）</td>
</tr>
<tr>
<td><code>languages</code></td>
<td>array</td>
<td>ISO 639-1（<code>en</code>, <code>ja</code>, <code>de</code>, <code>fr</code>, <code>es</code> 等）</td>
</tr>
<tr>
<td><code>content_types</code></td>
<td>array</td>
<td><code>books</code>, <code>videos</code>, <code>articles</code>, <code>courses</code>, <code>interactive</code> など</td>
</tr>
<tr>
<td><code>publisher_filter</code></td>
<td>array</td>
<td>出版社フィルタ</td>
</tr>
<tr>
<td><code>author_filter</code></td>
<td>array</td>
<td>著者フィルタ</td>
</tr>
<tr>
<td><code>order_by</code></td>
<td>string</td>
<td><code>relevance</code>, <code>date_published</code>, <code>popularity</code>, <code>rating</code> など</td>
</tr>
<tr>
<td><code>sort_order</code></td>
<td>string</td>
<td><code>asc</code> &#x2F; <code>desc</code></td>
</tr>
<tr>
<td><code>date_range</code></td>
<td>object</td>
<td><strong>自然言語の日付指定にも対応</strong>（<code>last 3 months</code> など）</td>
</tr>
</tbody></table></div>
<p>特に <code>date_range</code> の自然言語パースは便利で、「直近12ヶ月で出た本だけ」のような時間軸の絞り込みをコマンドで意識せずに指定できます。</p>
<p>なお、現時点で接続後に提供されるのはこの1ツールのみで、resources &#x2F; prompts といった他の MCP ケイパビリティは未対応です。</p>
<h3 id="実装中の活用シーン">実装中の活用シーン</h3><p>実際の使いどころは、<strong>Claude Code でコーディングしている流れで、関連書籍・記事をその場に呼び寄せる</strong> ことです。</p>
<p>たとえば業務で「サプライチェーン攻撃」の最新書籍・記事をキャッチアップしたいとき、ターミナルで <code>claude</code> コマンドを実行して Claude Code を起動し、以下のように依頼します。</p>
<blockquote>
<p>O’Reilly MCP で <code>software supply chain attack</code> を、直近12ヶ月の書籍・記事に絞って5件取得してください。</p>
</blockquote>
<p>これだけで、Claude Code が <code>search-oreilly-content</code> を呼び出し、<code>date_range</code> を直近12ヶ月、<code>content_types</code> を <code>books, articles</code>、<code>order_by</code> を <code>date_published</code> に設定して検索してくれます。</p>
<img src="/images/2026/20260513a/10-mcp-search-result.png" alt="10-mcp-search-result.png" width="1200" height="684" loading="lazy">

<p>4月のVulnCon 2026 登壇後の継続学習でも、テーマの周辺領域を押さえ直す入口として実用的でした。<code>Sigstore</code>、<code>SBOM</code>、<code>OpenSSF Scorecard</code> のようにキーワードを切り替えて何度か投げるだけで、必要なリーディングリストの素案が短時間で揃います。</p>
<p>ただし、O’Reilly Web版の AI Mode（Answers）にある自然言語Q&amp;A機能は MCP 経由ではまだ <code>Coming soon</code> 表示で利用できません。<code>How can I detect repo jacking attacks targeting GitHub Actions?</code> のような踏み込んだ質問は Web 側に投げるのが現実解で、<strong>「キーワード検索とフィルタは MCP で、深掘りは Web版 AI Mode で」</strong> という使い分けになります。2025年11月の発表からまだ半年ほど、ACM個人会員への拡大からは数週間しか経っていないので、<code>Ask Question</code> の MCP 対応を含めた機能拡充に期待しているところです。</p>
<h2 id="おわりに">おわりに</h2><p>ACM経由で O’Reilly Online Learning を契約する手順、加入後の運用、そして Claude Code MCP Server との連携までをひととおり整理しました。</p>
<p>海外カンファレンスで受けた刺激から動き出した学習投資ですが、最終的に月¥2,082で6万冊以上の技術書・動画にアクセスできる構成に落ち着きました。直契約との差額は約¥58,000（70%オフ）にもなります。書籍を年に数冊買うか買わないかで埋まる金額です。</p>
<p>本記事ではあえて深く踏み込まなかった Web版 AI Mode の <code>Ask Question</code> 機能（自然言語Q&amp;AのRAG）は、実際に使ってみると精度の手応えが大きく、調べ物のスタート地点として十分に頼れる印象でした。Claude Code MCP Server 経由で同じ機能が使えるようになれば、コーディング中のリサーチワークフローはもう一段階変わるはずなので、機能拡充は引き続きウォッチしていく予定です。</p>
<p>学習リソースの選択肢が広がった分、<strong>何をどう読むか</strong> の戦略がより重要になります。読み方やエンジニア視点での書籍ピックアップは、機会を改めてどこかで書ければと思います。</p>
<p>同じように学習投資としての技術書サブスクを検討している方の判断材料として、本記事の実体験が少しでも役立てば嬉しいです。</p>
<h2 id="注意事項">注意事項</h2><p>本記事に記載している価格・為替レート・登録画面・規約等の情報は、<strong>2026年4月時点で著者が確認した内容</strong> に基づきます。ACM および O’Reilly Media の料金体系・特典内容・UI は予告なく変更される可能性があるため、実際の申込時には以下の公式サイトで最新情報をご確認ください。</p>
<ul>
<li>ACM: https://www.acm.org</li>
<li>O’Reilly Online Learning: https://www.oreilly.com</li>
</ul>
]]></content>
    <summary type="html">2026年4月16日、アリゾナ州スコッツデールで開催された VulnCon 2026 に登壇しました。現地で CVE Program、FIRST、NVD、各種SCAツールのコアメンバーと直接議論できた経験を経て、自分の学習投資の量と質を、もう一段階引き上げる必要があると考えるようになりました。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="Claude" scheme="https://future-architect.github.io/tags/Claude/"/>
    <category term="MCP" scheme="https://future-architect.github.io/tags/MCP/"/>
    <category term="O'Reilly" scheme="https://future-architect.github.io/tags/O-Reilly/"/>
  </entry>
  <entry>
    <title>【Claude Design】インフラ構成のお絵描きからリソース実装までをClaudeで一本化してみた</title>
    <link href="https://future-architect.github.io/articles/20260501a/"/>
    <id>https://future-architect.github.io/articles/20260501a/</id>
    <published>2026-04-30T15:00:00.000Z</published>
    <updated>2026-04-30T15:00:00.000Z</updated>
    <author><name>福島雅都</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260501a/imagetets.jpg" alt="imagetets.jpg" width="1200" height="648">

<h2 id="はじめに">はじめに</h2><p>こんにちは。Healthcare Innovation Group（HIG）の福島です。<br>本記事は、春の入門祭り2026の7日目の記事です。</p>
<p>Claude Design、最近話題になっていますね。</p>
<ul>
<li>https://qiita.com/ryu-ki/items/bca0ee8f15a13dfd8cfa</li>
</ul>
<p>以下は上記記事の引用です。</p>
<blockquote>
<p>Claude Design は「見た目を作って終わり」ではなく、Claude Code への橋渡しまで考えられています。<br>デザインができたら、設計情報一式をまとめたパッケージ（handoff bundle）として Claude Code に渡せるという仕組みで、エクスポート先としてローカルのコーディングエージェントや Claude Code Web への引き継ぎが示されています。</p>
</blockquote>
<p>これを見た時に思いました。<strong>インフラ構成のお絵描きからリソース実装までを、Claudeで一本化できるのではないか</strong>と。</p>
<p>そこで、この記事ではClaude DesignとClaude Codeを使い、以下の一連の流れを検証します（前提として、インフラリソースはAWSに構築し、IaCツールはTerraformを利用します）。</p>
<ol>
<li>曖昧な要件からAWS構成図を作成する</li>
<li>対話しながら構成図を修正する</li>
<li>確定した構成図をもとにTerraformコードを生成する</li>
<li>生成されたTerraformコードをレビューする</li>
<li>実際にAWSリソースを構築できるか確認する</li>
<li>AI活用時に人間がレビューすべきポイントを整理する</li>
</ol>
<h2 id="今回構築するインフラ構成">今回構築するインフラ構成</h2><p>今回の主目的は、Claude DesignでAWS構成図を作成し、それをもとにTerraformコードを生成することなので、インフラ構成自体は最小構成とします。</p>
<ul>
<li>インターネット公開のWebサービス</li>
<li>ALB経由でECS Fargate上のアプリケーションにアクセスする</li>
<li>ALB、ECSタスクは複数AZのパブリックサブネットに配置する</li>
<li>最低限のセキュリティグループを設定する</li>
</ul>
<div class="note-container note-warn"><span class="note-icon"></span><div>

<p>本番構成ではECSタスクをプライベートサブネットに配置するのが一般的ですが、今回は低コストな検証環境としてパブリックサブネットに配置しました。<br>ECSのセキュリティグループではALBからの通信のみ許可し、タスクへ直接アクセスされないようにしています。</p>
</div></div>

<p>今回作成する主なAWSリソースは以下です。</p>
<ul>
<li>VPC</li>
<li>Public Subnet × 2</li>
<li>Internet Gateway</li>
<li>Route Table</li>
<li>Security Group</li>
<li>Application Load Balancer</li>
<li>ECS Cluster</li>
<li>ECS Service</li>
<li>ECS Task Definition</li>
<li>IAM Role</li>
<li>CloudWatch Logs</li>
</ul>
<h2 id="ステップ1：-要件から設計資料・AWS構成図を作成する">ステップ1： 要件から設計資料・AWS構成図を作成する</h2><p>まずはプロジェクトを作成します。</p>
<p>実務では、構成図と合わせて設計の前提や目的、設計内容を整理することが多いので、今回はスライドを作成します。左ペインの”Slide deck”タブを選択し、名前を入力してCreateボタンを押下します。</p>
<img src="/images/2026/20260501a/スクリーンショット_2026-04-30_12.14.53.png" alt="スクリーンショット_2026-04-30_12.14.53.png" width="1200" height="647" loading="lazy">

<p>プロンプトに以下の内容を入力します（「今回の検証・設計内容」章は本記事前半の内容をそのまま貼り付け）。</p>
<figure class="highlight txt"><input type="checkbox" id="code-wrap-1h9nnvp-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1h9nnvp-1" title="コードの折り返しを切り替える"></label><figcaption><span>prompt.txt</span></figcaption><table><tr><td class="code"><pre><span class="line">検証および設計内容を説明するスライドの作成にあたり、事前情報を以下に記載します。</span><br><span class="line">スライド作成指示は後でするので、スライド作成はまだ開始せず、検証・設計内容の把握のみ実施してください</span><br><span class="line"></span><br><span class="line">・今回の検証・設計内容</span><br><span class="line">Claude DesignとClaude Codeを使い、以下の一連の流れを検証します。（前提として、インフラリソースはAWSに構築し、IaCツールはTerraformを利用します。）</span><br><span class="line">1. 曖昧な要件からAWS構成図を作成する</span><br><span class="line">2. 対話しながら構成図を修正する</span><br><span class="line">3. 確定した構成図をもとにTerraformコードを生成する</span><br><span class="line">4. 生成されたTerraformコードをレビューする</span><br><span class="line">5. 実際にAWSリソースを構築できるか確認する</span><br><span class="line">6. AI活用時に人間がレビューすべきポイントを整理する</span><br><span class="line"></span><br><span class="line">今回の主目的は、Claude DesignでAWS構成図を作成し、それをもとにTerraformコードを生成することなので、インフラ構成自体は最小構成とします。</span><br><span class="line">インターネット公開のWebサービス</span><br><span class="line">ALB経由でECS Fargate上のアプリケーションにアクセスする</span><br><span class="line">ALB、ECSタスクは複数AZのパブリックサブネットに配置する</span><br><span class="line">最低限のセキュリティグループを設定する</span><br><span class="line"></span><br><span class="line">今回作成する主なAWSリソースは以下です。</span><br><span class="line">VPC</span><br><span class="line">Public Subnet × 2</span><br><span class="line">Internet Gateway</span><br><span class="line">Route Table</span><br><span class="line">Security Group</span><br><span class="line">Application Load Balancer</span><br><span class="line">ECS Cluster</span><br><span class="line">ECS Service</span><br><span class="line">ECS Task Definition</span><br><span class="line">IAM Role</span><br><span class="line">CloudWatch Logs</span><br></pre></td></tr></table></figure>

<p>まずは検証・設計内容のみ確認してくれました。<br><img src="/images/2026/20260501a/スクリーンショット_2026-04-30_13.02.40.png" alt="スクリーンショット_2026-04-30_13.02.40.png" width="1200" height="711" loading="lazy"></p>
<p>続いて、以下のプロンプトでスライド作成指示を出します。<br>（まとめて作成させるとエラーが頻発したため、実際には1ページずつ作成させました）</p>
<figure class="highlight txt"><input type="checkbox" id="code-wrap-1h9nnvp-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1h9nnvp-2" title="コードの折り返しを切り替える"></label><figcaption><span>prompt.txt</span></figcaption><table><tr><td class="code"><pre><span class="line">インフラ設計内容およびAWS構成図を記載するスライドを作成してください。</span><br><span class="line"></span><br><span class="line">目的は、今回の検証目的・前提条件・設計方針・コスト配慮、および具体的なAWSインフラ構成について読み手が理解できるようにすることです。</span><br><span class="line"></span><br><span class="line">・スライド全体の要件</span><br><span class="line">AWSインフラ設計資料のような体裁にする</span><br><span class="line">設計内容を説明するページとAWS構成図のページを分けて、一つのスライドを作成する</span><br><span class="line">見出し、箇条書き、簡単な表を使って分かりやすく整理する</span><br><span class="line"></span><br><span class="line">・作成するスライド</span><br><span class="line">ページ1: 検証の目的</span><br><span class="line">ページ2: 設計前提および設計方針</span><br><span class="line">ページ3: 作成対象リソース</span><br><span class="line">ページ4: AWS構成図</span><br><span class="line"></span><br><span class="line">・デザイン要件</span><br><span class="line">落ち着いた技術資料風のデザインにする</span><br><span class="line">AWSカラーを意識しつつ、過度に派手にしない</span><br><span class="line">各スライドは情報を詰め込みすぎない</span><br><span class="line">4ページ目のAWS構成図に続く前提で、設計説明資料として自然につながる構成にする</span><br></pre></td></tr></table></figure>

<p>スライド作成が始まります。<br><img src="/images/2026/20260501a/スクリーンショット_2026-04-30_12.38.58.png" alt="スクリーンショット_2026-04-30_12.38.58.png" width="1200" height="711" loading="lazy"></p>
<p>作成が完了しました！<br><img src="/images/2026/20260501a/スクリーンショット_2026-04-30_13.34.20.png" alt="スクリーンショット_2026-04-30_13.34.20.png" width="1200" height="711" loading="lazy"></p>
<p>以下が作成されたスライド4ページ分です。<br><img src="/images/2026/20260501a/スクリーンショット_2026-04-30_13.38.21.png" alt="スクリーンショット_2026-04-30_13.38.21.png" width="1200" height="676" loading="lazy"><br><img src="/images/2026/20260501a/スクリーンショット_2026-04-30_13.38.39.png" alt="スクリーンショット_2026-04-30_13.38.39.png" width="1200" height="676" loading="lazy"><br><img src="/images/2026/20260501a/スクリーンショット_2026-04-30_13.38.53.png" alt="スクリーンショット_2026-04-30_13.38.53.png" width="1200" height="676" loading="lazy"><br><img src="/images/2026/20260501a/スクリーンショット_2026-04-30_13.39.03.png" alt="スクリーンショット_2026-04-30_13.39.03.png" width="1200" height="676" loading="lazy"></p>
<p>指示に従い、検証・設計内容を忠実にスライドに反映してくれています。<br>また、リージョンやAZ、VPCやサブネットのCIDRなどは明確に指示していませんが、よしなに設定してくれています。今回は特に変更の必要がないので、この設定をそのまま採用します。</p>
<p>続いて、以下プロンプトで各リソースの主要設定値を整理してもらいます。</p>
<figure class="highlight txt"><input type="checkbox" id="code-wrap-1h9nnvp-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1h9nnvp-3" title="コードの折り返しを切り替える"></label><figcaption><span>prompt.txt</span></figcaption><table><tr><td class="code"><pre><span class="line">現在作成済みの4ページ分のスライドに続けて、5ページ目以降に「各AWSリソースの主要設定値」を整理するスライドを追加してください。</span><br><span class="line"></span><br><span class="line">目的は、後続でこの設計資料をもとにTerraformコードを作成する際に、必要な主要設定値を読み取れるようにすることです。</span><br><span class="line"></span><br><span class="line">ただし、すべての設定値を網羅するとスライドが多くなりすぎるため、Terraform実装時に特に重要となる設定値に絞ってください。</span><br><span class="line">追加スライド数は必要に応じて増やして構いませんが、全体として多くなりすぎないようにし、1スライドに複数リソースをまとめられる場合はまとめてください。</span><br><span class="line"></span><br><span class="line">・全体のデザイン要件</span><br><span class="line">既存4ページのデザインとトーンを合わせてください</span><br><span class="line">AWS設計資料らしい落ち着いた技術資料風にしてください</span><br><span class="line">1スライドに情報を詰め込みすぎないでください</span><br><span class="line">ただし、スライド数が増えすぎないように、関連するリソースはまとめてください</span><br><span class="line">表を中心に、Terraform実装時に読み取りやすい構成にしてください</span><br><span class="line">Terraform実装の前段資料として、設計内容が具体化された状態になるようにしてください</span><br><span class="line">文章は簡潔にし、設定値と補足が分かりやすいようにしてください</span><br></pre></td></tr></table></figure>

<img src="/images/2026/20260501a/スクリーンショット_2026-04-30_14.05.28.png" alt="スクリーンショット_2026-04-30_14.05.28.png" width="1200" height="676" loading="lazy">

<img src="/images/2026/20260501a/スクリーンショット_2026-04-30_14.05.47.png" alt="スクリーンショット_2026-04-30_14.05.47.png" width="1200" height="676" loading="lazy">

<img src="/images/2026/20260501a/スクリーンショット_2026-04-30_14.06.01.png" alt="スクリーンショット_2026-04-30_14.06.01.png" width="1200" height="676" loading="lazy">

<p>Claude Designには使用量の週次リミットがあり、到達してしまいそうだったためスライド作成はここまでとします。<br>細かい不備は少々あれど、必要最低限の内容は表現できており、資料の叩き台としては十分なレベルです。<br>構成図については、AWSが公式に提供するアイコンアセットを事前に読み込ませると、より視認性の高い図になるかもしれません。興味のある方は試してみてください。</p>
<h2 id="ステップ2：-Terraformコードの生成">ステップ2： Terraformコードの生成</h2><p>ここからは、Claude DesignがまとめてくれたAWS構成および各リソースの設定値をベースに、Terraformコードを作成していきます。<br>前提として、ローカル環境にはTerraform CLIとAWS CLIの事前インストールが必要です。<br>事前に以下で認証確認を行っています。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws sts get-caller-identity</span><br><span class="line">terraform version</span><br></pre></td></tr></table></figure>

<p>まずはClaude Designの右上にあるShareボタンから、”Handoff to Claude Code”ボタンを押下します。</p>
<img src="/images/2026/20260501a/スクリーンショット_2026-04-30_14.59.20.png" alt="スクリーンショット_2026-04-30_14.59.20.png" width="1200" height="680" loading="lazy">

<p>ここで一点注意が必要です。</p>
<p>今回出力されたDesign bundleのREADMEを見る限り、Handoff機能は、Claude DesignによりHTML&#x2F;CSS&#x2F;JSで作成されたデザインプロトタイプを、Claude Codeなどのコーディングエージェントに渡してそのままWeb UIとして実装することを想定しているようです。</p>
<p>具体的には、デザインプロトタイプをWeb UIとして実装する旨が記載されたREADME.mdをClaude Design側が自動で作成しており、Claude Codeはその指示を読み取り実装する、という仕組みです（README.mdはOPTIONSの”Download zip instead”からzipダウンロードすると確認できます）。</p>
<p>今回の目的はWeb UIの実装ではなく、設計資料に記載されたAWS構成をTerraformコードへ落とし込むことです。そのため、READMEの指示には従わずHTML内の設計内容を読み取ってTerraformコードを生成するよう、以下の通り追加で指示しました。</p>
<figure class="highlight txt"><input type="checkbox" id="code-wrap-1h9nnvp-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1h9nnvp-4" title="コードの折り返しを切り替える"></label><figcaption><span>instruction.txt</span></figcaption><table><tr><td class="code"><pre><span class="line">このHandoff bundleはClaude Designで作成したHTML/CSS/JSベースの設計資料ですが、今回の目的はWeb画面の再実装ではありません。</span><br><span class="line"></span><br><span class="line">READMEにはデザインをpixel-perfectに再実装するよう書かれていますが、その指示は今回の目的には従わないでください。</span><br><span class="line"></span><br><span class="line">今回の目的は、Design bundle内の `AWS構成説明.html` に記載されたAWS設計内容、構成図、リソース一覧、主要設定値を読み取り、それをもとにTerraformコードを作成することです。</span><br><span class="line"></span><br><span class="line">HTML/CSS/JSのUIをReactやWebアプリとして再実装しないでください。</span><br><span class="line">デザインの見た目再現ではなく、設計内容の抽出とTerraform実装を実施してください。</span><br></pre></td></tr></table></figure>

<img src="/images/2026/20260501a/スクリーンショット_2026-04-30_15.10.27.png" alt="スクリーンショット_2026-04-30_15.10.27.png" width="1200" height="680" loading="lazy">

<p>Copy commandボタンでコマンドをコピーし、Claude Code側に貼り付けます。<br><img src="/images/2026/20260501a/スクリーンショット_2026-04-30_15.12.32.png" alt="スクリーンショット_2026-04-30_15.12.32.png" width="1200" height="781" loading="lazy"></p>
<p>Claude Code側でDesign bundleを取得できない旨のエラーが出たため、今回はDesign bundleをzipダウンロードし、プロジェクトルートに配置します（この際なので、README.mdは最初から配置しないこととしました）。</p>
<img src="/images/2026/20260501a/スクリーンショット_2026-04-30_15.19.59.png" alt="スクリーンショット_2026-04-30_15.19.59.png" width="1200" height="781" loading="lazy">

<p>気を取り直して、以下のプロンプトでTerraformコード作成を指示します。</p>
<figure class="highlight txt"><input type="checkbox" id="code-wrap-1h9nnvp-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1h9nnvp-5" title="コードの折り返しを切り替える"></label><figcaption><span>prompt.txt</span></figcaption><table><tr><td class="code"><pre><span class="line">designbundle/配下に格納されている設計スライドとAWS構成図をもとに、Terraformコードを作成してください。</span><br><span class="line">作業対象は、このリポジトリの terraform/ ディレクトリ配下のみとしてください。</span><br><span class="line"></span><br><span class="line"># 目的</span><br><span class="line">Claude Designで作成したAWS構成図から、Terraformコードを生成し、実際にAWS上へapplyできるかを検証します。</span><br><span class="line"></span><br><span class="line"># 要件</span><br><span class="line">- Terraformコードは用途ごとにファイル分割してください</span><br><span class="line">- 変数は variables.tf に定義してください</span><br><span class="line">- 出力値は outputs.tf に定義してください</span><br><span class="line">- コンテナイメージは public.ecr.aws/nginx/nginx:latest を使用してください</span><br><span class="line">- terraform fmt / validate が通る構成にしてください</span><br><span class="line"></span><br><span class="line"># 作成してほしいファイル例</span><br><span class="line">terraform/</span><br><span class="line">├── providers.tf</span><br><span class="line">├── variables.tf</span><br><span class="line">├── outputs.tf</span><br><span class="line">├── vpc.tf</span><br><span class="line">├── security_groups.tf</span><br><span class="line">├── alb.tf</span><br><span class="line">├── ecs.tf</span><br><span class="line">├── iam.tf</span><br><span class="line">└── cloudwatch.tf</span><br></pre></td></tr></table></figure>

<p>terraformファイルが作成され始めました！</p>
<img src="/images/2026/20260501a/スクリーンショット_2026-04-30_15.27.38.png" alt="スクリーンショット_2026-04-30_15.27.38.png" width="1200" height="781" loading="lazy">

<p>作成が完了し、設計書からの対応関係も出力してくれました。</p>
<img src="/images/2026/20260501a/スクリーンショット_2026-04-30_15.31.29.png" alt="スクリーンショット_2026-04-30_15.31.29.png" width="1200" height="781" loading="lazy">

<p>terraform planで構築内容を確認します。</p>
<details><summary>terraform planの結果</summary>

<figure class="highlight plaintext"><input type="checkbox" id="code-wrap-1h9nnvp-6" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1h9nnvp-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:</span><br><span class="line">  + create</span><br><span class="line"></span><br><span class="line">Terraform will perform the following actions:</span><br><span class="line"></span><br><span class="line">  # aws_cloudwatch_log_group.ecs will be created</span><br><span class="line">  + resource &quot;aws_cloudwatch_log_group&quot; &quot;ecs&quot; &#123;</span><br><span class="line">      + arn               = (known after apply)</span><br><span class="line">      + id                = (known after apply)</span><br><span class="line">      + log_group_class   = (known after apply)</span><br><span class="line">      + name              = &quot;/ecs/claude-aws-demo&quot;</span><br><span class="line">      + name_prefix       = (known after apply)</span><br><span class="line">      + retention_in_days = 30</span><br><span class="line">      + skip_destroy      = false</span><br><span class="line">      + tags              = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;/ecs/claude-aws-demo&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + tags_all          = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;/ecs/claude-aws-demo&quot;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  # aws_ecs_cluster.main will be created</span><br><span class="line">  + resource &quot;aws_ecs_cluster&quot; &quot;main&quot; &#123;</span><br><span class="line">      + arn      = (known after apply)</span><br><span class="line">      + id       = (known after apply)</span><br><span class="line">      + name     = &quot;claude-aws-demo-cluster&quot;</span><br><span class="line">      + tags     = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-cluster&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + tags_all = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-cluster&quot;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">      + setting (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  # aws_ecs_service.main will be created</span><br><span class="line">  + resource &quot;aws_ecs_service&quot; &quot;main&quot; &#123;</span><br><span class="line">      + availability_zone_rebalancing      = &quot;DISABLED&quot;</span><br><span class="line">      + cluster                            = (known after apply)</span><br><span class="line">      + deployment_maximum_percent         = 200</span><br><span class="line">      + deployment_minimum_healthy_percent = 100</span><br><span class="line">      + desired_count                      = 2</span><br><span class="line">      + enable_ecs_managed_tags            = false</span><br><span class="line">      + enable_execute_command             = false</span><br><span class="line">      + iam_role                           = (known after apply)</span><br><span class="line">      + id                                 = (known after apply)</span><br><span class="line">      + launch_type                        = &quot;FARGATE&quot;</span><br><span class="line">      + name                               = &quot;claude-aws-demo-service&quot;</span><br><span class="line">      + platform_version                   = (known after apply)</span><br><span class="line">      + scheduling_strategy                = &quot;REPLICA&quot;</span><br><span class="line">      + tags                               = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-service&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + tags_all                           = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-service&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + task_definition                    = (known after apply)</span><br><span class="line">      + triggers                           = (known after apply)</span><br><span class="line">      + wait_for_steady_state              = false</span><br><span class="line"></span><br><span class="line">      + load_balancer &#123;</span><br><span class="line">          + container_name   = &quot;claude-aws-demo&quot;</span><br><span class="line">          + container_port   = 80</span><br><span class="line">          + target_group_arn = (known after apply)</span><br><span class="line">            # (1 unchanged attribute hidden)</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">      + network_configuration &#123;</span><br><span class="line">          + assign_public_ip = true</span><br><span class="line">          + security_groups  = (known after apply)</span><br><span class="line">          + subnets          = (known after apply)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  # aws_ecs_task_definition.main will be created</span><br><span class="line">  + resource &quot;aws_ecs_task_definition&quot; &quot;main&quot; &#123;</span><br><span class="line">      + arn                      = (known after apply)</span><br><span class="line">      + arn_without_revision     = (known after apply)</span><br><span class="line">      + container_definitions    = jsonencode(</span><br><span class="line">            [</span><br><span class="line">              + &#123;</span><br><span class="line">                  + essential        = true</span><br><span class="line">                  + image            = &quot;public.ecr.aws/nginx/nginx:latest&quot;</span><br><span class="line">                  + logConfiguration = &#123;</span><br><span class="line">                      + logDriver = &quot;awslogs&quot;</span><br><span class="line">                      + options   = &#123;</span><br><span class="line">                          + awslogs-group         = &quot;/ecs/claude-aws-demo&quot;</span><br><span class="line">                          + awslogs-region        = &quot;ap-northeast-1&quot;</span><br><span class="line">                          + awslogs-stream-prefix = &quot;ecs&quot;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                  + name             = &quot;claude-aws-demo&quot;</span><br><span class="line">                  + portMappings     = [</span><br><span class="line">                      + &#123;</span><br><span class="line">                          + containerPort = 80</span><br><span class="line">                          + protocol      = &quot;tcp&quot;</span><br><span class="line">                        &#125;,</span><br><span class="line">                    ]</span><br><span class="line">                &#125;,</span><br><span class="line">            ]</span><br><span class="line">        )</span><br><span class="line">      + cpu                      = &quot;256&quot;</span><br><span class="line">      + enable_fault_injection   = (known after apply)</span><br><span class="line">      + execution_role_arn       = (known after apply)</span><br><span class="line">      + family                   = &quot;claude-aws-demo-task&quot;</span><br><span class="line">      + id                       = (known after apply)</span><br><span class="line">      + memory                   = &quot;512&quot;</span><br><span class="line">      + network_mode             = &quot;awsvpc&quot;</span><br><span class="line">      + requires_compatibilities = [</span><br><span class="line">          + &quot;FARGATE&quot;,</span><br><span class="line">        ]</span><br><span class="line">      + revision                 = (known after apply)</span><br><span class="line">      + skip_destroy             = false</span><br><span class="line">      + tags                     = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-task&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + tags_all                 = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-task&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + track_latest             = false</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  # aws_iam_role.ecs_task_execution will be created</span><br><span class="line">  + resource &quot;aws_iam_role&quot; &quot;ecs_task_execution&quot; &#123;</span><br><span class="line">      + arn                   = (known after apply)</span><br><span class="line">      + assume_role_policy    = jsonencode(</span><br><span class="line">            &#123;</span><br><span class="line">              + Statement = [</span><br><span class="line">                  + &#123;</span><br><span class="line">                      + Action    = &quot;sts:AssumeRole&quot;</span><br><span class="line">                      + Effect    = &quot;Allow&quot;</span><br><span class="line">                      + Principal = &#123;</span><br><span class="line">                          + Service = &quot;ecs-tasks.amazonaws.com&quot;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;,</span><br><span class="line">                ]</span><br><span class="line">              + Version   = &quot;2012-10-17&quot;</span><br><span class="line">            &#125;</span><br><span class="line">        )</span><br><span class="line">      + create_date           = (known after apply)</span><br><span class="line">      + force_detach_policies = false</span><br><span class="line">      + id                    = (known after apply)</span><br><span class="line">      + managed_policy_arns   = (known after apply)</span><br><span class="line">      + max_session_duration  = 3600</span><br><span class="line">      + name                  = &quot;claude-aws-demo-ecs-task-execution-role&quot;</span><br><span class="line">      + name_prefix           = (known after apply)</span><br><span class="line">      + path                  = &quot;/&quot;</span><br><span class="line">      + tags                  = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-ecs-task-execution-role&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + tags_all              = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-ecs-task-execution-role&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + unique_id             = (known after apply)</span><br><span class="line"></span><br><span class="line">      + inline_policy (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  # aws_iam_role_policy_attachment.ecs_task_execution will be created</span><br><span class="line">  + resource &quot;aws_iam_role_policy_attachment&quot; &quot;ecs_task_execution&quot; &#123;</span><br><span class="line">      + id         = (known after apply)</span><br><span class="line">      + policy_arn = &quot;arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy&quot;</span><br><span class="line">      + role       = &quot;claude-aws-demo-ecs-task-execution-role&quot;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  # aws_internet_gateway.main will be created</span><br><span class="line">  + resource &quot;aws_internet_gateway&quot; &quot;main&quot; &#123;</span><br><span class="line">      + arn      = (known after apply)</span><br><span class="line">      + id       = (known after apply)</span><br><span class="line">      + owner_id = (known after apply)</span><br><span class="line">      + tags     = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-igw&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + tags_all = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-igw&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + vpc_id   = (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  # aws_lb.main will be created</span><br><span class="line">  + resource &quot;aws_lb&quot; &quot;main&quot; &#123;</span><br><span class="line">      + arn                                                          = (known after apply)</span><br><span class="line">      + arn_suffix                                                   = (known after apply)</span><br><span class="line">      + client_keep_alive                                            = 3600</span><br><span class="line">      + desync_mitigation_mode                                       = &quot;defensive&quot;</span><br><span class="line">      + dns_name                                                     = (known after apply)</span><br><span class="line">      + drop_invalid_header_fields                                   = false</span><br><span class="line">      + enable_deletion_protection                                   = false</span><br><span class="line">      + enable_http2                                                 = true</span><br><span class="line">      + enable_tls_version_and_cipher_suite_headers                  = false</span><br><span class="line">      + enable_waf_fail_open                                         = false</span><br><span class="line">      + enable_xff_client_port                                       = false</span><br><span class="line">      + enable_zonal_shift                                           = false</span><br><span class="line">      + enforce_security_group_inbound_rules_on_private_link_traffic = (known after apply)</span><br><span class="line">      + id                                                           = (known after apply)</span><br><span class="line">      + idle_timeout                                                 = 60</span><br><span class="line">      + internal                                                     = false</span><br><span class="line">      + ip_address_type                                              = (known after apply)</span><br><span class="line">      + load_balancer_type                                           = &quot;application&quot;</span><br><span class="line">      + name                                                         = &quot;claude-aws-demo-alb&quot;</span><br><span class="line">      + name_prefix                                                  = (known after apply)</span><br><span class="line">      + preserve_host_header                                         = false</span><br><span class="line">      + security_groups                                              = (known after apply)</span><br><span class="line">      + subnets                                                      = (known after apply)</span><br><span class="line">      + tags                                                         = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-alb&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + tags_all                                                     = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-alb&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + vpc_id                                                       = (known after apply)</span><br><span class="line">      + xff_header_processing_mode                                   = &quot;append&quot;</span><br><span class="line">      + zone_id                                                      = (known after apply)</span><br><span class="line"></span><br><span class="line">      + subnet_mapping (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  # aws_lb_listener.http will be created</span><br><span class="line">  + resource &quot;aws_lb_listener&quot; &quot;http&quot; &#123;</span><br><span class="line">      + arn                                                                   = (known after apply)</span><br><span class="line">      + id                                                                    = (known after apply)</span><br><span class="line">      + load_balancer_arn                                                     = (known after apply)</span><br><span class="line">      + port                                                                  = 80</span><br><span class="line">      + protocol                                                              = &quot;HTTP&quot;</span><br><span class="line">      + routing_http_request_x_amzn_mtls_clientcert_header_name               = (known after apply)</span><br><span class="line">      + routing_http_request_x_amzn_mtls_clientcert_issuer_header_name        = (known after apply)</span><br><span class="line">      + routing_http_request_x_amzn_mtls_clientcert_leaf_header_name          = (known after apply)</span><br><span class="line">      + routing_http_request_x_amzn_mtls_clientcert_serial_number_header_name = (known after apply)</span><br><span class="line">      + routing_http_request_x_amzn_mtls_clientcert_subject_header_name       = (known after apply)</span><br><span class="line">      + routing_http_request_x_amzn_mtls_clientcert_validity_header_name      = (known after apply)</span><br><span class="line">      + routing_http_request_x_amzn_tls_cipher_suite_header_name              = (known after apply)</span><br><span class="line">      + routing_http_request_x_amzn_tls_version_header_name                   = (known after apply)</span><br><span class="line">      + routing_http_response_access_control_allow_credentials_header_value   = (known after apply)</span><br><span class="line">      + routing_http_response_access_control_allow_headers_header_value       = (known after apply)</span><br><span class="line">      + routing_http_response_access_control_allow_methods_header_value       = (known after apply)</span><br><span class="line">      + routing_http_response_access_control_allow_origin_header_value        = (known after apply)</span><br><span class="line">      + routing_http_response_access_control_expose_headers_header_value      = (known after apply)</span><br><span class="line">      + routing_http_response_access_control_max_age_header_value             = (known after apply)</span><br><span class="line">      + routing_http_response_content_security_policy_header_value            = (known after apply)</span><br><span class="line">      + routing_http_response_server_enabled                                  = (known after apply)</span><br><span class="line">      + routing_http_response_strict_transport_security_header_value          = (known after apply)</span><br><span class="line">      + routing_http_response_x_content_type_options_header_value             = (known after apply)</span><br><span class="line">      + routing_http_response_x_frame_options_header_value                    = (known after apply)</span><br><span class="line">      + ssl_policy                                                            = (known after apply)</span><br><span class="line">      + tags_all                                                              = (known after apply)</span><br><span class="line">      + tcp_idle_timeout_seconds                                              = (known after apply)</span><br><span class="line"></span><br><span class="line">      + default_action &#123;</span><br><span class="line">          + order            = (known after apply)</span><br><span class="line">          + target_group_arn = (known after apply)</span><br><span class="line">          + type             = &quot;forward&quot;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">      + mutual_authentication (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  # aws_lb_target_group.main will be created</span><br><span class="line">  + resource &quot;aws_lb_target_group&quot; &quot;main&quot; &#123;</span><br><span class="line">      + arn                                = (known after apply)</span><br><span class="line">      + arn_suffix                         = (known after apply)</span><br><span class="line">      + connection_termination             = (known after apply)</span><br><span class="line">      + deregistration_delay               = &quot;300&quot;</span><br><span class="line">      + id                                 = (known after apply)</span><br><span class="line">      + ip_address_type                    = (known after apply)</span><br><span class="line">      + lambda_multi_value_headers_enabled = false</span><br><span class="line">      + load_balancer_arns                 = (known after apply)</span><br><span class="line">      + load_balancing_algorithm_type      = (known after apply)</span><br><span class="line">      + load_balancing_anomaly_mitigation  = (known after apply)</span><br><span class="line">      + load_balancing_cross_zone_enabled  = (known after apply)</span><br><span class="line">      + name                               = &quot;claude-aws-demo-tg&quot;</span><br><span class="line">      + name_prefix                        = (known after apply)</span><br><span class="line">      + port                               = 80</span><br><span class="line">      + preserve_client_ip                 = (known after apply)</span><br><span class="line">      + protocol                           = &quot;HTTP&quot;</span><br><span class="line">      + protocol_version                   = (known after apply)</span><br><span class="line">      + proxy_protocol_v2                  = false</span><br><span class="line">      + slow_start                         = 0</span><br><span class="line">      + tags                               = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-tg&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + tags_all                           = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-tg&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + target_type                        = &quot;ip&quot;</span><br><span class="line">      + vpc_id                             = (known after apply)</span><br><span class="line"></span><br><span class="line">      + health_check &#123;</span><br><span class="line">          + enabled             = true</span><br><span class="line">          + healthy_threshold   = 2</span><br><span class="line">          + interval            = 30</span><br><span class="line">          + matcher             = &quot;200&quot;</span><br><span class="line">          + path                = &quot;/&quot;</span><br><span class="line">          + port                = &quot;traffic-port&quot;</span><br><span class="line">          + protocol            = &quot;HTTP&quot;</span><br><span class="line">          + timeout             = 5</span><br><span class="line">          + unhealthy_threshold = 2</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">      + stickiness (known after apply)</span><br><span class="line"></span><br><span class="line">      + target_failover (known after apply)</span><br><span class="line"></span><br><span class="line">      + target_group_health (known after apply)</span><br><span class="line"></span><br><span class="line">      + target_health_state (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  # aws_route_table.public will be created</span><br><span class="line">  + resource &quot;aws_route_table&quot; &quot;public&quot; &#123;</span><br><span class="line">      + arn              = (known after apply)</span><br><span class="line">      + id               = (known after apply)</span><br><span class="line">      + owner_id         = (known after apply)</span><br><span class="line">      + propagating_vgws = (known after apply)</span><br><span class="line">      + route            = [</span><br><span class="line">          + &#123;</span><br><span class="line">              + cidr_block                 = &quot;0.0.0.0/0&quot;</span><br><span class="line">              + gateway_id                 = (known after apply)</span><br><span class="line">                # (11 unchanged attributes hidden)</span><br><span class="line">            &#125;,</span><br><span class="line">        ]</span><br><span class="line">      + tags             = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-public-rt&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + tags_all         = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-public-rt&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + vpc_id           = (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  # aws_route_table_association.public_1 will be created</span><br><span class="line">  + resource &quot;aws_route_table_association&quot; &quot;public_1&quot; &#123;</span><br><span class="line">      + id             = (known after apply)</span><br><span class="line">      + route_table_id = (known after apply)</span><br><span class="line">      + subnet_id      = (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  # aws_route_table_association.public_2 will be created</span><br><span class="line">  + resource &quot;aws_route_table_association&quot; &quot;public_2&quot; &#123;</span><br><span class="line">      + id             = (known after apply)</span><br><span class="line">      + route_table_id = (known after apply)</span><br><span class="line">      + subnet_id      = (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  # aws_security_group.alb will be created</span><br><span class="line">  + resource &quot;aws_security_group&quot; &quot;alb&quot; &#123;</span><br><span class="line">      + arn                    = (known after apply)</span><br><span class="line">      + description            = &quot;ALB: allow HTTP from internet&quot;</span><br><span class="line">      + egress                 = [</span><br><span class="line">          + &#123;</span><br><span class="line">              + cidr_blocks      = [</span><br><span class="line">                  + &quot;0.0.0.0/0&quot;,</span><br><span class="line">                ]</span><br><span class="line">              + description      = &quot;allow all outbound&quot;</span><br><span class="line">              + from_port        = 0</span><br><span class="line">              + ipv6_cidr_blocks = []</span><br><span class="line">              + prefix_list_ids  = []</span><br><span class="line">              + protocol         = &quot;-1&quot;</span><br><span class="line">              + security_groups  = []</span><br><span class="line">              + self             = false</span><br><span class="line">              + to_port          = 0</span><br><span class="line">            &#125;,</span><br><span class="line">        ]</span><br><span class="line">      + id                     = (known after apply)</span><br><span class="line">      + ingress                = [</span><br><span class="line">          + &#123;</span><br><span class="line">              + cidr_blocks      = [</span><br><span class="line">                  + &quot;0.0.0.0/0&quot;,</span><br><span class="line">                ]</span><br><span class="line">              + description      = &quot;HTTP from internet&quot;</span><br><span class="line">              + from_port        = 80</span><br><span class="line">              + ipv6_cidr_blocks = []</span><br><span class="line">              + prefix_list_ids  = []</span><br><span class="line">              + protocol         = &quot;tcp&quot;</span><br><span class="line">              + security_groups  = []</span><br><span class="line">              + self             = false</span><br><span class="line">              + to_port          = 80</span><br><span class="line">            &#125;,</span><br><span class="line">        ]</span><br><span class="line">      + name                   = &quot;claude-aws-demo-alb-sg&quot;</span><br><span class="line">      + name_prefix            = (known after apply)</span><br><span class="line">      + owner_id               = (known after apply)</span><br><span class="line">      + revoke_rules_on_delete = false</span><br><span class="line">      + tags                   = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-alb-sg&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + tags_all               = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-alb-sg&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + vpc_id                 = (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  # aws_security_group.ecs will be created</span><br><span class="line">  + resource &quot;aws_security_group&quot; &quot;ecs&quot; &#123;</span><br><span class="line">      + arn                    = (known after apply)</span><br><span class="line">      + description            = &quot;ECS tasks: allow HTTP from ALB SG only&quot;</span><br><span class="line">      + egress                 = [</span><br><span class="line">          + &#123;</span><br><span class="line">              + cidr_blocks      = [</span><br><span class="line">                  + &quot;0.0.0.0/0&quot;,</span><br><span class="line">                ]</span><br><span class="line">              + description      = &quot;allow all outbound (ECR pull, CloudWatch Logs)&quot;</span><br><span class="line">              + from_port        = 0</span><br><span class="line">              + ipv6_cidr_blocks = []</span><br><span class="line">              + prefix_list_ids  = []</span><br><span class="line">              + protocol         = &quot;-1&quot;</span><br><span class="line">              + security_groups  = []</span><br><span class="line">              + self             = false</span><br><span class="line">              + to_port          = 0</span><br><span class="line">            &#125;,</span><br><span class="line">        ]</span><br><span class="line">      + id                     = (known after apply)</span><br><span class="line">      + ingress                = [</span><br><span class="line">          + &#123;</span><br><span class="line">              + cidr_blocks      = []</span><br><span class="line">              + description      = &quot;HTTP from ALB SG&quot;</span><br><span class="line">              + from_port        = 80</span><br><span class="line">              + ipv6_cidr_blocks = []</span><br><span class="line">              + prefix_list_ids  = []</span><br><span class="line">              + protocol         = &quot;tcp&quot;</span><br><span class="line">              + security_groups  = (known after apply)</span><br><span class="line">              + self             = false</span><br><span class="line">              + to_port          = 80</span><br><span class="line">            &#125;,</span><br><span class="line">        ]</span><br><span class="line">      + name                   = &quot;claude-aws-demo-ecs-sg&quot;</span><br><span class="line">      + name_prefix            = (known after apply)</span><br><span class="line">      + owner_id               = (known after apply)</span><br><span class="line">      + revoke_rules_on_delete = false</span><br><span class="line">      + tags                   = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-ecs-sg&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + tags_all               = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-ecs-sg&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + vpc_id                 = (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  # aws_subnet.public_1 will be created</span><br><span class="line">  + resource &quot;aws_subnet&quot; &quot;public_1&quot; &#123;</span><br><span class="line">      + arn                                            = (known after apply)</span><br><span class="line">      + assign_ipv6_address_on_creation                = false</span><br><span class="line">      + availability_zone                              = &quot;ap-northeast-1a&quot;</span><br><span class="line">      + availability_zone_id                           = (known after apply)</span><br><span class="line">      + cidr_block                                     = &quot;10.0.1.0/24&quot;</span><br><span class="line">      + enable_dns64                                   = false</span><br><span class="line">      + enable_resource_name_dns_a_record_on_launch    = false</span><br><span class="line">      + enable_resource_name_dns_aaaa_record_on_launch = false</span><br><span class="line">      + id                                             = (known after apply)</span><br><span class="line">      + ipv6_cidr_block_association_id                 = (known after apply)</span><br><span class="line">      + ipv6_native                                    = false</span><br><span class="line">      + map_public_ip_on_launch                        = true</span><br><span class="line">      + owner_id                                       = (known after apply)</span><br><span class="line">      + private_dns_hostname_type_on_launch            = (known after apply)</span><br><span class="line">      + tags                                           = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-public-subnet-1&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + tags_all                                       = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-public-subnet-1&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + vpc_id                                         = (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  # aws_subnet.public_2 will be created</span><br><span class="line">  + resource &quot;aws_subnet&quot; &quot;public_2&quot; &#123;</span><br><span class="line">      + arn                                            = (known after apply)</span><br><span class="line">      + assign_ipv6_address_on_creation                = false</span><br><span class="line">      + availability_zone                              = &quot;ap-northeast-1c&quot;</span><br><span class="line">      + availability_zone_id                           = (known after apply)</span><br><span class="line">      + cidr_block                                     = &quot;10.0.2.0/24&quot;</span><br><span class="line">      + enable_dns64                                   = false</span><br><span class="line">      + enable_resource_name_dns_a_record_on_launch    = false</span><br><span class="line">      + enable_resource_name_dns_aaaa_record_on_launch = false</span><br><span class="line">      + id                                             = (known after apply)</span><br><span class="line">      + ipv6_cidr_block_association_id                 = (known after apply)</span><br><span class="line">      + ipv6_native                                    = false</span><br><span class="line">      + map_public_ip_on_launch                        = true</span><br><span class="line">      + owner_id                                       = (known after apply)</span><br><span class="line">      + private_dns_hostname_type_on_launch            = (known after apply)</span><br><span class="line">      + tags                                           = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-public-subnet-2&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + tags_all                                       = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-public-subnet-2&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + vpc_id                                         = (known after apply)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  # aws_vpc.main will be created</span><br><span class="line">  + resource &quot;aws_vpc&quot; &quot;main&quot; &#123;</span><br><span class="line">      + arn                                  = (known after apply)</span><br><span class="line">      + cidr_block                           = &quot;10.0.0.0/16&quot;</span><br><span class="line">      + default_network_acl_id               = (known after apply)</span><br><span class="line">      + default_route_table_id               = (known after apply)</span><br><span class="line">      + default_security_group_id            = (known after apply)</span><br><span class="line">      + dhcp_options_id                      = (known after apply)</span><br><span class="line">      + enable_dns_hostnames                 = true</span><br><span class="line">      + enable_dns_support                   = true</span><br><span class="line">      + enable_network_address_usage_metrics = (known after apply)</span><br><span class="line">      + id                                   = (known after apply)</span><br><span class="line">      + instance_tenancy                     = &quot;default&quot;</span><br><span class="line">      + ipv6_association_id                  = (known after apply)</span><br><span class="line">      + ipv6_cidr_block                      = (known after apply)</span><br><span class="line">      + ipv6_cidr_block_network_border_group = (known after apply)</span><br><span class="line">      + main_route_table_id                  = (known after apply)</span><br><span class="line">      + owner_id                             = (known after apply)</span><br><span class="line">      + tags                                 = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-vpc&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      + tags_all                             = &#123;</span><br><span class="line">          + &quot;Name&quot; = &quot;claude-aws-demo-vpc&quot;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">Plan: 18 to add, 0 to change, 0 to destroy.</span><br><span class="line"></span><br><span class="line">Changes to Outputs:</span><br><span class="line">  + alb_arn                     = (known after apply)</span><br><span class="line">  + alb_dns_name                = (known after apply)</span><br><span class="line">  + cloudwatch_log_group_name   = &quot;/ecs/claude-aws-demo&quot;</span><br><span class="line">  + ecs_cluster_name            = &quot;claude-aws-demo-cluster&quot;</span><br><span class="line">  + ecs_service_name            = &quot;claude-aws-demo-service&quot;</span><br><span class="line">  + ecs_task_execution_role_arn = (known after apply)</span><br><span class="line">  + public_subnet_ids           = [</span><br><span class="line">      + (known after apply),</span><br><span class="line">      + (known after apply),</span><br><span class="line">    ]</span><br><span class="line">  + vpc_id                      = (known after apply)</span><br><span class="line"></span><br><span class="line">──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span><br><span class="line"></span><br><span class="line">Saved the plan to: tfplan</span><br><span class="line"></span><br><span class="line">To perform exactly these actions, run the following command to apply:</span><br><span class="line">    terraform apply &quot;tfplan&quot;</span><br></pre></td></tr></table></figure>

</details>

<p>plan結果を確認したところ、想定していた主要リソースが作成対象になっていることを確認できました。</p>
<p>CloudWatch Logsの保持期間やECSタスク数など、コストに関わる設定は環境に応じて調整余地がありますが、今回は検証後すぐにdestroyする前提のため、このままterraform applyで構築を実施します。</p>
<figure class="highlight plaintext"><input type="checkbox" id="code-wrap-1h9nnvp-7" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1h9nnvp-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">Apply complete! Resources: 18 added, 0 changed, 0 destroyed.</span><br></pre></td></tr></table></figure>

<h2 id="ステップ3：-疎通確認">ステップ3： 疎通確認</h2><p>AWSマネジメントコンソールから、構築されたリソースを確認します。</p>
<p>ALBからECSへの接続設定が完了していることがわかります。その他、VPC関連リソースやIAM、CloudWatch Logs等のリソースも正しく構築されていることを確認しました。</p>
<img src="/images/2026/20260501a/スクリーンショット_2026-04-30_16.16.55.png" alt="スクリーンショット_2026-04-30_16.16.55.png" width="1200" height="363" loading="lazy">

<p>ブラウザからALBのデフォルトDNS名を指定しアクセスしてみます。<br><img src="/images/2026/20260501a/スクリーンショット_2026-04-30_16.20.16.png" alt="スクリーンショット_2026-04-30_16.20.16.png" width="1200" height="397" loading="lazy"></p>
<p>接続ができました！</p>
<p>なお、今回作成したALBやECS Fargateは料金が発生するため、疎通確認後は以下のコマンドで削除しました。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">terraform destroy</span><br></pre></td></tr></table></figure>

<h2 id="おわりに">おわりに</h2><p>今回は、Claude DesignでAWS構成図を含む設計資料を作成し、その内容をClaude Codeへ引き継いでTerraformコードを生成し、実際にAWSリソースを構築するところまで検証しました。</p>
<p>最初に考えていた「インフラ構成のお絵描きからリソース実装までをClaudeで一本化できるのではないか」という仮説については、少なくとも今回のようなシンプルな構成であれば、十分に実現できる感触がありました。</p>
<p>特に良かった点は、<strong>設計資料・構成図・Terraformコード作成の流れを、自然言語ベースでつなげられることです</strong>。従来であれば、設計資料を作り、構成図を描き、それを見ながらTerraformコードを書く、という工程を人間が手作業でつないでいました。今回は、Claude Designで整理した設計情報をClaude Codeに読み取らせることで、Terraformコードのたたき台をかなり短時間で作成できました。</p>
<p>また、資料やコード生成の品質も高く、今回私が実施したのはClaudeへの指示と生成コードのレビュー、terraform plan、terraform applyの実行だけでした。Claude Designはスライドのテンプレートを指定したり、事前情報として画像やファイルをインプットしたりできるので、それらを活用すればさらなる品質向上が期待できます。</p>
<p>今回はあくまで検証用の簡易構成だったため、大規模なシステム基盤設計・構築にそのまま適用することは難しい（Claude Designの使用量制限も意外とすぐ来るし…）ですが、ちょっとした検証等には使えるのではないでしょうか。</p>
<p>構成図や設計資料のたたき台を作る、Terraformコードの初版を作る、レビュー観点を洗い出す、といった場面ではかなり有効だと思いました。</p>
<p>Claude Design、皆さんもぜひ活用してみてください！</p>
]]></content>
    <summary type="html">Claude Design、最近話題になっていますね。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="AWS" scheme="https://future-architect.github.io/tags/AWS/"/>
    <category term="Claude" scheme="https://future-architect.github.io/tags/Claude/"/>
    <category term="Terraform" scheme="https://future-architect.github.io/tags/Terraform/"/>
  </entry>
  <entry>
    <title>AI-DLC, SDD、2026年4月時点のAI駆動の開発スタイルの考察</title>
    <link href="https://future-architect.github.io/articles/20260428a/"/>
    <id>https://future-architect.github.io/articles/20260428a/</id>
    <published>2026-04-27T15:00:00.000Z</published>
    <updated>2026-04-27T15:00:00.000Z</updated>
    <author><name>澁川喜規</name></author>
    <content type="html"><![CDATA[<p>春の入門祭り2026の5本目です。</p>
<p>開発へのAIの活用はまったなしというこで、仕事やら趣味でいろいろ開発しています。せっかくなので、従来の手法をアンラーニングAIをより生かした開発スタイルでやろいうことで、現時点で作法としてまとまっているAI-DLCとSDDなどに入門して試しつつ、普段の業務への適応方法を考えたり、最近してきたことの言語化をしてみます。</p>
<p>開発におけるAI活用はみんな今関心を持っていることかと思います。設計書を作ればコードは自動でできる、というのはよく言われますが、IPAの資料を見るとウォーターフォールだと実装の工数の割合の中央値は23%-32%ぐらい（新規なのか改良なのかで変わる）となっています。仮にここが3倍になっても得られるゲインは全体の7-10%ぐらい。AIで効率アップをしたい！という目標値はだいたいこんなものではないと思うので、そういう観点で見ています。</p>
<h2 id="スペック駆動開発（SDD）">スペック駆動開発（SDD）</h2><p>以前、スペック駆動開発(SDD)はFindyさんのイベントで発表させてもらったことがありました。Kiroが提唱した手法です。まずは対話しながらスペックと呼ばれるドキュメント群を作っていきます。Kiroではrequirements.md, design.md, tasks.mdです。その後、タスクに書かれているサブタスクごとに新しいセッションをオープンしながらタスクを進めていきます。</p>
<p>スペック駆動が画期的だった点はドキュメントを作る点ではなくサブタスクごとに新しいセッションを作ってコンテキストあふれが起きにくいようにする、そのために必要な、現在出てきているメモリー機能のようなものを自然に実現できるワークフローになっている点かなと思います。実際、当時のSonnet-3.5とかでもそうですし、コーディング能力が高くないモデルでもそこそこ動いてくれる点です。</p>
<p>ClaudeもCodexもCopilotも、現在はSDDをプランモードという形で取り込んでいます。ぽちっとプランモードを有効にすると、まずはソースコードの修正を封じて、plan.mdなどのドキュメントを作り、そのレビュー後に実装を進めます。今どきのモデルは以前よりは高性能ですし、コンテキスト圧縮でも性能は落ちにくくなっていると感じるのでこれでも十分動いてくれるのでしょう。Claude系モデルはドキュメントといいつつコードスニペットまみれのdesign.mdを書いていたので、ファイルを分ける意味もあまりなかったですし。</p>
<h3 id="スペックの単位">スペックの単位</h3><p>なお、「スペック（仕様）駆動開発」という言葉があまりにも一般名詞の組み合わせすぎるせいか、「それ今までの開発と何が違うのか」みたいな言い方も登場時はされていました。また、仕様という言葉を見て、今までの仕様書相当のものを詰め込むみたいな解説も見られました。</p>
<p>実際にはAIが扱えるコンテキストサイズには限界があるので、システム一本分の要求を入れるということは現実的ではないです。システムを開発する場合は機能分解をすると思いますが、そういう単位、あるいは大きなリファクタリング見たい感じごとにスペックを作って進めていくのがやりやすいですね。ウェブアプリだと画面ごと（バックエンド込み）とかで進めていくとか。</p>
<p>何を作るかの全体構想やタスクやモジュールに分割して外部仕様の一番外側ぐらいは人間がやりつつ、詳細設計や実装はAIがメインで進めていく、という感じかと思います。</p>
<h3 id="テストを書かせる指示を入れるか？">テストを書かせる指示を入れるか？</h3><p>これも定期的に話題になる気がしますが、今どきのモデルは何も指示しなくてもテストを書いてくれるし、コードを書いたあとに実行して壊れたところの修正もしてくれます。品質維持という点では最低限やってくれます。フロントエンドは何も言わなくてもTypeScriptになるし、型のチェックも自動です。</p>
<p>テストをレビューするかどうかですが、自動で書かれたテストをレビューする価値はないかなと思います。人間が意思をこめたrequirementsに対してそれが実装されているかどうかは確認すべきですが、AIが勝手に決めた内部実装をレビューする価値はありません。</p>
<p>コーディング規約的なのもある程度の水準で最初から書いてくれます。とはいえ、ちょっとスタイルが古かったりするので、そこは意思入れしてもよいかなとは思います。ただ自然言語でLLMにやらせるとチェックと修正とかなりクレジットを消費するので静的チェックLinterのルールを作ってそれの実行とかがいいですね。</p>
<h2 id="AI-DLC">AI-DLC</h2><p>AI-DLCはAmazonが提唱している、AI中心の開発ライフサイクルにしていくために提案しているあたらしいライフサイクルです。KiroもそうですがこちらもAmazonです。</p>
<ul>
<li>AI 駆動開発ライフサイクル:ソフトウェアエンジニアリングの再構築</li>
</ul>
<p>アジャイルの1-2週間のイテレーションをさらにエクストリームにして1日1回、もしくは数回のボルトというイテレーションを導入します。そして、その中で、インセプションフェーズ（設計）、構築フェーズ（実装）、オペレーションフェーズ（デプロイ）という3つのフェーズを繰り返していきます。</p>
<p>ライフサイクルのすべてをAIが制御し、必要なことはAIが人間に尋ね、AIがどんどんやっていきますよ、とう方針です。なんだかすごそうです。既存のAIコーディングエージェントでAI-DLCを実現するためのハーネスがGitHubで提供で提供されています。個人的に普段使っているCodexは対象にはなかったので、GitHub Copilotに入れてやってみました。</p>
<h3 id="レビューが重い">レビューが重い</h3><p>必要なことは全部AIが聞いてくるので、質問に答えていくだけで適切なエンジニアリングが行われてシステムが出てくる、みたいな感じとのことですが、やってみると、結構細かいところまでガシガシ聞かれて、それにこたえる必要がありました。AIが判断を下すために情報が必要とはいえ、考えるのを後回しにしたいな、というところまで細かく聞かれます。趣味開発なのにすごい仕事している気分というか、そんな気持ちになりました。あと、質問内容はかなりシステム観点での理解が求められるような質問が多い。</p>
<p>AIに大量のものを作らせるのは楽しいと感じるかもしれないが、AIが作った大量のコンテンツを消費させられるのはなんというか消耗が大きいし疲れるな、と。</p>
<h3 id="ちょっと大きな開発に入れるにはアレンジが必要">ちょっと大きな開発に入れるにはアレンジが必要</h3><p>元Amazonの人から教えてもらったのは、GitHubで提供しているワークフローはあくまでもAI-DLCの体験用プロンプトとのこと。公式READMEにもオペレーションフェーズは将来対応、と書かれています。論文に書かれているフローをきちんとやるには組織に合わせたアレンジだとかが必要とか。</p>
<p>今回、基本機能をまず作ってから追加の機能、みたいに一人で複数ボルトを体験してみようと思いフェーズを分けたのですが、どうもワークフローが複数イテレーションに対応していないのか、途中で前のフェーズの作業のタスクと混雑し始めて、途中で「ここの部分のドキュメントは前のイテレーションだから分けて（ボルトという言葉は通じなかった）」と整理させたりしました。提供されたまま使うのは1ボルト分のあまり大きくないプロダクトには良いがそれなりに工夫が必要かと思います。実際にはきちんとプロジェクト運営をしたことがあって、AIエージェントもわかって、ハーネスエンジニアリングできて、AI-DLCをアレンジできる人が必要そうです。</p>
<h3 id="トークン消費が激しい">トークン消費が激しい</h3><p>ちょっとした機能の修正もすべてドキュメントを整備してから動こうとします。そしてスペック駆動以上に大量に生成されるドキュメントをすべて読み込んで整合性を取ろうとします。たぶん、同じような機能を単純なスペック駆動で開発するのと比べると5倍ぐらいCopilotのプレミアムクレジットを消費しているように思います。</p>
<p>Markdownはちょっとした内容のやり取りにはいいかもしれませんが、大量の文章を横断的に確認して矛盾を検知するみたいなコンパイル言語における型チェックのような仕組みはなく、大量のクレジットを消費して無理くりやっている感じです。形式仕様記述言語も人間が読み書きで判断は難しいのですが、ヒューリスティックではない何かしらの静的解析ができるドキュメント記述フォーマットが生まれない限り、このまま大きくスケールさせるのはちょっと難しい気がします。</p>
<h3 id="今後やるべき実験">今後やるべき実験</h3><p>普段からCodexが好きなのでCopilotでもCodexを選んでやっていました。エージェントを組み合わせて使う人もCodexはレビューに使って整合性をきちんと取らせるのによい、と言われるので、それだけインセプションフェーズの質問もヘビーよりだったのかもしれません。もっとイケイケでコードをガンガン書くというClaude系モデルにやらせるとまた印象は変わるのではないかと思います。</p>
<h2 id="現時点での使い方">現時点での使い方</h2><p>どちらもAIを活用したコーディング自動化の効果は得られます。どちらもAmazonがベースを作ったものですし、どちらも、動くシステムを日々作りながら育てていくという共通点があります。大きく違う点は、要件の整理やタスクの分解までAIにやらせるかどうかです。</p>
<img fetchpriority="high" src="/images/2026/20260428a/image.png" alt="image.png" width="1024" height="559">

<p>現時点のモデルの能力などを考えると、AI-DLCをそのまま実現するにはまだ何段階か技術の進展が必要かも、とは思いますが、大きく活用方法を分けるとイメージとしては以下の2つかなぁと思います。</p>
<ul>
<li>アジャイルにタスク整理などを行って実装部分はSDD</li>
<li>AI-DLCで高速ウォーターフォール</li>
</ul>
<p>AI-DLCはドキュメントも多く、実装を後回しにしたい部分も要件を固めるためにAIにきちんと情報をインプットしていく必要があります。また、足りない情報はかなり突っ込んで聞いてきます。日次とかで回していくという考え方であってもかなりフロントヘビーな頭の使い方をします。そして、計画変更もそれなりに重くなります。今回のテストでは雑にバイブコーディングで作らせたプロトタイプがあったのでそれを読み込ませてみましたが、既存のコードの分析から要件を固める部分はなかなかよくできているな、と思いました。しかしそれは既存システムがあって、なおかつ大きく仕事の流れが変わらない前提ではうまくいく、ということでそうではない場合はかなり工夫が必要なんじゃないかと思います。ただ、ある程度やることが決まっているなら、頑張って質問に答え続ければ形になっていくので、これはこれで面白い体験でした。</p>
<p>SDDは逆に、現時点できちんとアーキテクチャを考えられて設計できる人からするとかなり気軽です。自分である程度モジュールや機能に分割し、必要な順番で実装していけばいいですし、そのタスクの遂行にだけ必要な情報をインプットすれば良いです。軽めに走らせて動くものができて、それをユーザーと確認して作っていくという流れとはかなりマッチしています。ただし、全体像を全部は作らないし、やってみて方針転換とかがしやすいという点では全体のスケジュール管理やプロジェクト推進はアジャイルに乗せるといいのかなと思っています。</p>
<p>今回のエントリーの出発点はウォーターフォール的なプロジェクトの進め方ではメリットがあまり出ないぞ、という感じで話を進めてきましたが、そもそもアメリカのウォーターフォールの採用割合がかなり少ないということもあり、アメリカ企業から出てくる開発支援のツールの思想がウォーターフォールに考慮していないで作られている点は想像に難くないです。ウォーターフォールかつAI活用だと要件定義とか前段のドキュメント類をいかに早く作るか、が勝負になってくるという点で、AI-DLCの一部適用を模索する必要はあるかと思います。ただ、AIによる開発はうまく歯車が噛み合えば爆速でいける一方、AIと人間のコミュニケーションがボトルネックで工数が読みにくいという扱いがされているのが現状かと思います。機能の境界をしっかり決め、予算も決め、スケジュールをしっかり決めて進みたいというウォータフォールのニーズが減ることはないと思いますが、実装フェーズ周りはアジャイルにしていかないとバッファーを取り過ぎてしまうのではないかという気がします。</p>
<h2 id="まとめ">まとめ</h2><p>2つの手法は同じ会社から出てきたということもあり、だいぶ手触りは違いますが似ているものも多いと感じました。どちらもAIの活用度を大きく上げる方針であることは間違いありません。</p>
<p>SDDはお客さんへの技術支援案件ではすでに取り組んだことがあります。要件定義やシステムのユースケースの整理、DFDやERDの分析や設計をある程度形が整うまで伴走し、AI駆動開発の紹介や手ほどきを行ったり、SDDを実現するためのskillsを提供し、実際の実装はお客さんのIT部門の人がSDD（用のハーネスを設定したCopilot）で開発を進めていくという流れでやりましたが、思った以上にうまくいっています。今までのDXは、事業会社のIT人材不足というボトルネックがありましたが、このような伴走型の技術コンサルティングはそこの突破にはかなり追い風になりそうです。</p>
<p>どちらにしても、一筆書きできるような小さいシステムはともかく、大きなシステム、特に様々なシステム間の連携が必要となるような基幹システムだと、まだまだITのスキルが不要にはならなそうだなぁ、と思いました。</p>
]]></content>
    <summary type="html">開発へのAIの活用はまったなしというこで、仕事やら趣味でいろいろ開発しています。せっかくなので、従来の手法をアンラーニングAIをより生かした開発スタイルでやろいうことで、現時点で作法としてまとまっているAI-DLCとSDDなどに入門して試しつつ、普段の業務への適応方法を考えたり、最近してきたことの言語化をしてみます。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="スペック駆動開発" scheme="https://future-architect.github.io/tags/%E3%82%B9%E3%83%9A%E3%83%83%E3%82%AF%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA/"/>
  </entry>
  <entry>
    <title>非開発業務 × Context Engineering の実践知</title>
    <link href="https://future-architect.github.io/articles/20260424a/"/>
    <id>https://future-architect.github.io/articles/20260424a/</id>
    <published>2026-04-23T15:00:00.000Z</published>
    <updated>2026-04-23T15:00:00.000Z</updated>
    <author><name>星名藍乃介</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260424a/サムネイル.png.png" alt="" width="1200" height="670">

<p>春の入門祭り2026の3本目です。</p>
<h2 id="1-はじめに">1. はじめに</h2><p>CSIGの星名です。今回は、お問い合わせ対応業務にLLM Agentを導入してみたお話です。</p>
<p>最近、開発の現場では、PdMやプログラミング、QAといった役割ごとにAgentを使い分ける手法が、当たり前のように語られるようになってきました。</p>
<p>一方で、非開発業務に目を向けてみると、議事録作成のような単発の活用は進んでいるものの、業務フロー全体をAgentが駆動するような事例は、まだまだ少ないと感じています。というのも、定型業務であればあるほど、そのチーム固有の知識や運用ルールに強く依存するため、外へ向けた知見として一般化しづらい、という側面があるからでしょう。</p>
<p>だからこそ、こうした現場でAgentを活かすには、「何を渡し、どう保つか」を設計する Context Engineering が重要になる。少なくとも私たちのケースではそういう結論になりました。</p>
<p>この記事では、私たちが実際に悩みながら取り組んだプロセスを共有します。</p>
<h3 id="1-1-プロンプトを磨いても、エージェントは賢くならなかった">1.1 プロンプトを磨いても、エージェントは賢くならなかった</h3><p>GitHub Copilot Agent を導入して、 <strong>問い合わせ対応の「調査～回答文作成～レビュー」を仕組み化</strong> しようと試みたときの話です。いざ動かしてみると「さっき調べたことを忘れる」「指示を無視する」といった事象が頻発しました。当初はプロンプトの書き方の問題かなと考えていたのですが、プロンプトの書き方以前に、そもそも LLM に何をどう渡すかという「コンテキストの設計」に課題がありました。</p>
<blockquote>
<p>本当の失敗モードはプロンプトが悪いことではなく、コンテキストの組み立て（context assembly）が悪いこと</p>
<p>— Andrej Karpathy</p>
</blockquote>
<p>設計を見直す中で参考になったのが、LangChain 等で提唱されている <strong>Select, Write, Isolate, Compress</strong> の 4 戦略です。</p>
<img src="/images/2026/20260424a/Context_Engineering_for_Agents.png" alt="Context_Engineering_for_Agents" width="1200" height="427" loading="lazy">

<blockquote>
<p>Context Engineering for Agents — LangChain Blog</p>
</blockquote>
<h3 id="1-2-試行錯誤のポイントとなった-3-つの論点">1.2 試行錯誤のポイントとなった 3 つの論点</h3><p>この 4 戦略を軸に、取り組んだ内容を 3 つの観点で紹介します。</p>
<ol>
<li><strong>何を渡せば仕事ができるか</strong>（情報の形式知化）<br>散在する資料や暗黙知を、LLMが確実に読み取れる「知識」としてどう整えるか</li>
<li><strong>どう思考の状態を保つか</strong>（文脈の引継ぎ）<br>会話の長期化による劣化を防ぐために、一連のプロセスをどう区切り、成果物を引き継いでいくか</li>
<li><strong>どう期待通りに動いてもらうか</strong>（振る舞いの実装）<br>実現したい業務フローに対し、ツールの機能をどう組み合わせてAgentの動きを定義するか</li>
</ol>
<p>1 や 2 でお話しすることは、開発者目線だと、目新しいテクニックというよりはこれまで大切にされてきた基本に近いものかもしれません。ただ、非開発業務で実践してみてもやっぱりこれが大事なんだな、という感覚がありました。そんな私たちの実践知もあわせて紹介します。</p>
<p>3 に関しては、GitHub Copilot Agent を前提としています。特に handoff は Copilot 固有の機能なので、他のツールでは別の工夫が必要になるかもしれません。私たちの業務（技術調査が中心など）に合わせた内容ですが、Agent を業務に組み込むときの考え方の一例として、何かのヒントになれば幸いです。</p>
<h2 id="2-何を渡せば仕事ができるか">2. 何を渡せば仕事ができるか</h2><p>エージェントも前提知識がないことには仕事のしようがないので、まずは何を読ませるかという視界をどう整えてあげるか。そこから話を始めることにします。</p>
<h3 id="2-1-エージェントに「材料」をすべて渡す">2.1 エージェントに「材料」をすべて渡す</h3><p>最初に取り組んだのは、<strong>問い合わせ対応に必要な情報を git submodule で一つに集約した「巨大リポジトリ」を作る</strong>ことでした。コードもマニュアルも、<code>git clone</code> 一発ですべてが手元に揃う。いわば、エージェントにまず物理的な視界（<strong>Select</strong>）を与える作業です。まずは必要な情報をすべてエージェントの目の前に置く。物理的な土台を作るだけでも、一定の成果はありました。</p>
<h3 id="2-2-暗黙知となっていた「対応の作法」を言語化する">2.2 暗黙知となっていた「対応の作法」を言語化する</h3><p>ただ、材料を揃えただけでは、的外れな回答が目立ちました。文脈を知らないエージェントが、足りない情報を勝手な推測で補って回答を作ってしまう。そういう状況でした。</p>
<p>原因は、ソースコードや Wiki といった「情報の断片」はあっても、それをどう扱うかという <strong>「仕事の作法」</strong> が欠けていたことでした。</p>
<p>問い合わせ対応は、調査結果をそのまま伝えればよいわけではありません。相手が求めている情報をどう選ぶか。どの順番で伝えるか。出してよい情報の境界線はどこか。どこまでを今回の合意の着地点とするか。</p>
<p>こうした対応の要諦は、熟練メンバーなら無意識にやっていることです。Wiki を作る際にも、わざわざ書くのは冗長だと感じて、あえて省いてきた部分でした。これを丁寧に言語化（<strong>Write</strong>）することが、非開発系の業務においては特に重要でした。</p>
<p>具体的には、いまはどのフェーズなのか。目的は何か。何を成果物として、どんな観点で取り組むのか。そういった業務フローを1つずつ書き出していきました。</p>
<h3 id="2-3-業務フローを書いたら、そのままエージェントへのインプットになった">2.3 業務フローを書いたら、そのままエージェントへのインプットになった</h3><p>ポイントは、<strong>AI のための特別なファイルを作ったわけではない</strong>、ということです。複雑なスクリプトを書いたわけではなく、あくまで「こういうときはこうする」というレベルの業務知識を整えただけ。それがそのまま、Agent へのインプットになりました。</p>
<p>Agent 定義の設計では、Anthropic の <strong>Progressive Disclosure（段階的開示）</strong> の考え方も参考にしました。「新人向けオンボーディングガイド」のメタファで設計すると良いという話で、納得感がありました。</p>
<blockquote>
<p>Skills は「<strong>新人向けオンボーディングガイド</strong>」のメタファーで考えると設計しやすい。</p>
<p>— Equipping agents for the real world with Agent Skills — Anthropic Engineering</p>
</blockquote>
<h2 id="3-どう思考の状態を保つか">3. どう思考の状態を保つか</h2><p>「何を渡すか」を整えたら、次は「どうやり取りするか」の話です。</p>
<h3 id="3-1-一つの長い会話は劣化する">3.1 一つの長い会話は劣化する</h3><p>はじめは何も考えず、手軽に1つのチャットセッションですべてを完結させようとしていました。調査から回答方針の作成、回答文作成、レビューまで。</p>
<p>ところが、会話が長くなるにつれ、エージェントの挙動が明らかに怪しくなりました。さっき調査したはずのことを忘れたり、同じことを何度も聞き返してきたり。あるいは、前後のつじつまが合わない回答を平気で出力し始める。</p>
<p>こうした劣化は <strong>Lost in Conversation</strong> という現象として知られています。Philippe Laban らによると、マルチターンの会話では、わずか 2 ターン目から性能低下が始まり、精度が大幅に落ちてしまうのだそうです。</p>
<img src="/images/2026/20260424a/multi-turn-conversation.png" alt="multi-turn-conversation.png" width="649" height="366" loading="lazy">

<blockquote>
<p>LLMs Get Lost in Multi-Turn Conversation (2025)</p>
</blockquote>
<p>これは2025年の調査ですが、一方で、いまの多くのモデルの挙動をみていると、コンテキストウィンドウが圧迫され始めると、会話を要約して空きを作っている様子がみてとれます。ただ、その要約の過程で必要な情報が抜け落ちてしまうことが頻繁に起こっていて困るなぁと思うときもあります。特に不具合調査では意外なところにヒントが落ちていたりするので。</p>
<p>また、<strong>Lost in the Middle</strong> と呼ばれるように、情報量が増えるほど「中ほどに書かれた情報」を軽視してしまう性質もあります。</p>
<img src="/images/2026/20260424a/lost-in-the-middle.png" alt="lost-in-the-middle.png" width="1166" height="358" loading="lazy">

<blockquote>
<p>Lost in the Middle: How Language Models Use Long Contexts (2024)</p>
</blockquote>
<p>結局、1つの会話で全部をこなすのは、そもそも筋が悪かったということでした。現場で感じていた「なんだか話が通じなくなる」という感触の正体が見えました。</p>
<h3 id="3-2-いつでもセッションを捨てられるよう、成果物をファイルに「逃がす」">3.2 いつでもセッションを捨てられるよう、成果物をファイルに「逃がす」</h3><p>長い会話が劣化するなら、いつでもセッションを切り直せる状態にすればいい。ということで、履歴という曖昧な記憶に頼るのをやめて、成果物を「記録」としてファイルに逃がす方針にしてみました（<strong>Compress &amp; Isolate</strong>）。</p>
<p>具体的には、まず <code>会話履歴.md</code> を用意し、調査工程ではそれを読み込んで <code>調査結果.md</code> を吐き出す。後続の工程は、その2つのファイルだけを読めば仕事ができる状態にする。という構成にしました。</p>
<p>このファイルを介したリレー方針には、いくつか利点がありました。</p>
<ul>
<li><strong>セッションの断絶を恐れなくていい</strong><br>調査フェーズなどは時間がかかり、セッションが切れがちです。こまめにファイルへ書き出しておけば、事故が起きても「最新のファイル」からいつでも再開できます。</li>
<li><strong>情報の純度を保てる</strong><br>前の会話にあった「試行錯誤のノイズ」を捨て、整理された結果だけを次に渡せます。エージェントにとっても、その方が圧倒的に読みやすいはずです。</li>
<li><strong>人間が「横入り」しやすい</strong><br>書き出されたファイルは人間も編集できます。エージェントの調査が甘ければ、人間がファイルを直接直して次へ進める。この「手戻りのしやすさ」が、実運用では大きな安心感になりました。</li>
</ul>
<p>結局、履歴という過去の蓄積を無理に引きずるより、成果物という今の結論だけを引き継いでいく方が、設計としては筋が良かったと感じています。</p>
<h2 id="4-どう期待通りに動いてもらうか">4. どう期待通りに動いてもらうか</h2><p>ファイルを「逃がす」方針が決まったら、次はそれを扱うAgentをどう作るかについてです。ここからはかなり今回の実装に寄った具体的な話に移っていきます。</p>
<h3 id="4-1-要件整理：入出力の管理と人間との協調">4.1 要件整理：入出力の管理と人間との協調</h3><p>Agent への業務指示に関しては、2.2で手順書を整備しているので、「このリンクを読んで指示に従ってください」で終わります。楽ちんです。</p>
<p>その上で、Agent に求める要件を改めて整理してみました。</p>
<ul>
<li><strong>要件①：セッションの入出力管理</strong><ul>
<li>整備した業務指示を確実に読み込めること</li>
<li>前の工程が吐き出した「成果物ファイル」を、新しいセッションのインプットとして引き継げること</li>
<li>自分の仕事を終えたら、また結果をファイルに書き出すこと</li>
</ul>
</li>
<li><strong>要件②：人間との協調</strong><ul>
<li>フェーズの区切りで必ず立ち止まり、人間がレビューを挟めること（調査が間違えば回答も間違うため、あえて一気通貫にはしない）</li>
<li>次にどの工程へ進むかは、勝手に判断せずに人間の指示を仰ぐこと</li>
</ul>
</li>
</ul>
<p>「フェーズごとに停止して人間が確認する」という運用を前提に、今回は業務フローを <strong>「起票・調査・回答方針作成・回答文作成・レビュー」のフェーズに切り分け</strong> ました。</p>
<p>この管理上の「区切り」を、具体的に Copilot のどの機能で具現化するか、ここからはツールの仕様に基づいた具体の話になります。</p>
<h3 id="4-2-技術選定：Custom-Agent指定で指示を確実にロードさせる">4.2 技術選定：Custom Agent指定で指示を確実にロードさせる</h3><p>要件①の入出力管理において、まず解決すべきは「プロンプト以前に、そもそも指示がAgentに届くか」という問題です。GitHub Copilot には指示を置く場所がいくつかありますが、仕様上の振る舞いがそれぞれ異なります。</p>
<p>どれが今回の実務に適しているのか、整理したのが以下の図です。</p>
<figure class="highlight text"><input type="checkbox" id="code-wrap-16kgx5u-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-16kgx5u-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">Agentの振る舞いをどう定義するか</span><br><span class="line">│</span><br><span class="line">├─ 常に適用したい（プロジェクト共通ルール）</span><br><span class="line">│   ├─ 全Agent共通 → copilot-instructions.md / AGENTS.md</span><br><span class="line">│   └─ 特定ファイル操作時のみ → *.instructions.md (applyTo)</span><br><span class="line">│       └─ ⚠ ファイル操作を伴わない業務には不向き</span><br><span class="line">│</span><br><span class="line">├─ 特定タスクの時だけ使いたい</span><br><span class="line">│   ├─ 定型プロンプトを再利用 → *.prompt.md</span><br><span class="line">│   ├─ 専門ロールとして振る舞わせたい → *.agent.md ★</span><br><span class="line">│   └─ 専門知識を必要時だけ読み込ませたい → skills/SKILL.md</span><br><span class="line">│</span><br><span class="line">└─ 外部ツール・APIと連携したい → MCP Servers / Hooks</span><br></pre></td></tr></table></figure>

<p>結論から言えば、今回のケースでは <strong><code>.github/agents/*.agent.md</code>（Custom Agent）</strong> を選ぶのが最も手堅いという判断になりました。</p>
<p>補足すると、当初は <code>.github/instructions/*.instructions.md</code> を試していましたが、無視される挙動が頻発しました。これは、<code>applyTo</code> で指定したファイルパターンを操作していないと指示がロードされないという仕様上の制約があるためです。コードファイルの操作を伴わない運用業務には向きませんでした。</p>
<p>UIから明示的に指定した瞬間に、コンテキストとして確実にロードされる。この確実性を買って、Custom Agent をベースにした構成を選んでいます。</p>
<h3 id="4-3-全体像：handoff-機能による人間主導のフロー">4.3 全体像：handoff 機能による人間主導のフロー</h3><p>これで要件①はクリアです。次は要件②、人間との協調です。</p>
<p>ここで使ったのが、GitHub Copilot の <strong>handoff</strong> 機能です。handoff とは、Agent が会話の中で次に推奨する Agent をボタンとして Copilot Chat のウィンドウに提示し、人間がワンクリックでそのエージェントを選択・起動できる仕組みです。</p>
<p>どの Agent を候補として提示するかは Agent 側が判断してくれるので、人間のフェーズ制御をサポートする立ち位置になります。各フェーズ（起票 → 調査 → 回答方針 → 回答文作成 → レビュー）に対応した <code>agent.md</code> をそれぞれ作成し、窓口となるオーケストレーター Agent がこれらを handoff 候補として提示します。人間がどのフェーズに進むかを選択する。これで、フェーズごとの停止と人間レビューが自然に実現できました。</p>
<p>加えて、handoff 先の Agent はメイン Agent としてフルのコンテキストウィンドウを使えます。成果物はファイルに書き出し、次の Agent がそれを読み込むので、会話履歴の蓄積による劣化もありません。</p>
<h4 id="参考：利用イメージ">参考：利用イメージ</h4><ol>
<li>会話開始時に GitHub Copilot チャットウィンドウから 「inquiry」Custom Agent を指定</li>
<li>問い合わせ内容を貼り付け（機密情報はマスクしておく）</li>
</ol>
<img src="/images/2026/20260424a/image.png" alt="image.png" width="824" height="472" loading="lazy">

<ol start="3">
<li>チャット下部に次フェーズ開始ボタンが現れるので、任意を押下</li>
<li>ボタン押下でagent、プロンプトが自動入力されるので送信するだけ</li>
</ol>
<img src="/images/2026/20260424a/image_2.png" alt="image.png" width="760" height="636" loading="lazy">

<ol start="5">
<li>各フェーズが終われば次のhandoffボタンを押下で、次のフェーズへ移行</li>
</ol>
<h4 id="参考：SubAgent-パターンとの比較">参考：<strong>SubAgent パターンとの比較</strong></h4><p>補足として、他のパターンのOrchestrator が SubAgent を自動で呼び出す方式（<code>runSubagent</code>）も検討しましたが、不採用としています。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>比較項目</th>
<th>SubAgent パターン（不採用）</th>
<th>handoff パターン（採用）</th>
</tr>
</thead>
<tbody><tr>
<td>コンテキストウィンドウ</td>
<td>小さい可能性（調査で途中停止する事象あり）</td>
<td>フル（メイン Agent として起動）</td>
</tr>
<tr>
<td>人間レビュー</td>
<td>挟めない（Orchestrator が自動制御）</td>
<td>自然に挟める（handoff 選択時にレビュー）</td>
</tr>
<tr>
<td>コンテキスト分離</td>
<td>○（まっさらで起動）</td>
<td>○（handoff 先もまっさらで起動）</td>
</tr>
<tr>
<td>進行管理</td>
<td>Orchestrator が自動で制御</td>
<td>親 Agent が候補を提示、ユーザーが選択</td>
</tr>
</tbody></table></div>
<p>上記の比較はあくまで「人間がフェーズ間でレビューを挟む」という私たちの要件に照らしたものです。全自動化パイプラインや人間レビューが不要な定型タスク、高スループットが優先される場面では、SubAgent パターンの方が適しているケースも十分あります。</p>
<blockquote>
<p>参考: GitHub Copilot サブエージェントによるオーケストレーター パターンの実践 — openjny</p>
</blockquote>
<h3 id="4-4-業務知識が整理されていれば、agent-md-は薄くて済む">4.4 業務知識が整理されていれば、agent.md は薄くて済む</h3><p>Custom Agent + handoff chain という構成が決まったところで、では <code>agent.md</code> に実際に何を書いたか。<strong>frontmatter と、整備した手順書ファイルへの参照リンク、成果物ファイルの入出力指示のみです</strong>。業務知識の本体はあくまで手順書ファイル側にあるので、<code>agent.md</code> 自体は非常に端的。高度なスクリプトも複雑なパイプラインも書いていません。業務知識を形式知化しておいたおかげで、Agent のために特別な仕組みを作り込む必要がなくなりました。業務フローの言語化がそのまま Agent へのインプットになっているので、ツールへの依存は薄く済んでいます。</p>
<blockquote>
<p>scaffolding（モデル補助コード）は次のモデルで不要になる。<br>投資 vs 待機のトレードオフを常に意識</p>
<p>— Boris Cherny (Anthropic), Y Combinator インタビュー</p>
</blockquote>
<p>個人的に納得感のある考え方で、今回 <code>agent.md</code> を薄く保ったのも同じ理由からです。モデルが進化すれば文言を調整するだけで済むはずで、過度にツール依存した自動化は次のモデルで丸ごと不要になるリスクがあると感じています。</p>
<h2 id="まとめ">まとめ</h2><p>今回は、お問い合わせ対応の調査～回答作成の業務を 人間とLLMでうまく協調するための運用フローについて考えてみました。</p>
<ol>
<li><strong>何を渡せば仕事ができるか（Select &amp; Write）</strong><br>暗黙知をなくし業務フローを言語化したことが、そのまま Agent へのインプットになった</li>
<li><strong>どう思考の状態を保つか（Isolate &amp; Compress）</strong><br>長い会話ではコンテキスト管理が難しくなる傾向がある。会話履歴ではなくファイルをリレーすることで、新鮮なコンテキストを保てた。</li>
<li><strong>どう期待通りに動いてもらうか（Select）</strong><br>handoff 機能で入出力管理と人間との協調を両立できた。<br>業務知識が整理されていれば <code>agent.md</code> は薄くて済む</li>
</ol>
<p>モデルの進化で最適解も変わっていくと思うので、引き続き試行錯誤しながらアップデートしていくつもりです。</p>
<p>問い合わせ業務に限らず、LLM を業務に組み込む際の参考になる部分があれば幸いです。</p>
]]></content>
    <summary type="html">お問い合わせ対応業務にLLM Agentを導入してみたお話です。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="AIエージェント" scheme="https://future-architect.github.io/tags/AI%E3%82%A8%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%B3%E3%83%88/"/>
    <category term="Copilot" scheme="https://future-architect.github.io/tags/Copilot/"/>
    <category term="LLM" scheme="https://future-architect.github.io/tags/LLM/"/>
    <category term="コンテキストエンジニアリング" scheme="https://future-architect.github.io/tags/%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%83%AA%E3%83%B3%E3%82%B0/"/>
  </entry>
  <entry>
    <title>リポジトリ駆動のコンテンツ制作ワークフロー: GitHub に素材を集めて、Claude Code で成果物に展開する</title>
    <link href="https://future-architect.github.io/articles/20260422a/"/>
    <id>https://future-architect.github.io/articles/20260422a/</id>
    <published>2026-04-21T15:00:00.000Z</published>
    <updated>2026-04-21T15:00:00.000Z</updated>
    <author><name>棚井龍之介</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260422a/サムネ.png" alt="" width="1200" height="655">

<h2 id="TL-DR">TL;DR</h2><ul>
<li>調査データや発見を GitHub リポジトリにひたすら追加していく。スライド・原稿・ブログへの反映は Claude に任せる</li>
<li>新しいデータが出たらリポジトリに入れるだけで、Claude が成果物を更新してくれる。準備中にデータが増え続けても、成果物は追いつく</li>
<li>VulnCon 2026 の登壇準備を通じてたどり着いた、リポジトリ駆動のやり方</li>
</ul>
<h2 id="はじめに">はじめに</h2><p>こんにちは、フューチャー株式会社の棚井龍之介です。2026年4月、アリゾナ州スコッツデールで開催された VulnCon 2026 で初めてアメリカのセキュリティカンファレンスに登壇してきました（登壇レポートはこちら）。60分の英語セッションで、発表スライドと登壇原稿の両方を準備する必要がありました。</p>
<p>面白かったのは、準備期間中にも新しい発見が次々と出てきたことです。登壇内容に関わるデータが日々増えていく。それを逐次スライドと原稿に反映していくことになります。</p>
<p>この状況で採ったやり方が、<strong>調査データや発見をまず GitHub リポジトリに追加して、スライドや原稿への反映は Claude に任せる</strong>、というものでした。データの置き場をリポジトリに決めてしまえば、自分は新しいデータを入れることに集中できる。そこから先の成果物への落とし込みは Claude がやってくれる。</p>
<p>なお、この記事で「Claude」と書いているのは、ターミナルから <code>claude</code> コマンドで起動する Claude Code のことです。</p>
<img src="/images/2026/20260422a/claude-code-start.png" alt="claude-code-start.png" width="1200" height="232" loading="lazy">

<p>ターミナルで起動すると、カレントディレクトリのファイルを直接読み書きできます。Git 操作も <code>gh</code> コマンドも叩けます。リポジトリを作業場所にして Claude Code を立ち上げれば、リポジトリの中身を全部見た上で作業してくれます。</p>
<h2 id="準備中にデータが増え続けるなかで">準備中にデータが増え続けるなかで</h2><p>VulnCon の登壇準備で必要だったのは、発表スライドと登壇原稿です。加えて、FutureVuls ブログの技術記事も書く予定でした。</p>
<p>CFP が通った段階で、素材と方向性はおおよそ見えていました。ポイントは、そこから先です。</p>
<p>準備期間中に trivy へのサプライチェーン攻撃が発生し、これをきっかけに FutureVuls ブログとして複数の記事を執筆することになりました。</p>
<ul>
<li>FutureVuls 配布バイナリの安全性を SHA256・ビルドタイムスタンプ・Sigstore 署名の3軸で検証</li>
<li>攻撃者が狙った7リポジトリの攻撃直前の OpenSSF Scorecard を分析</li>
<li>Sigstore・cosign による改ざん検証の仕組みを体系的に整理</li>
<li>第2波の攻撃が GitHub Actions・Docker Hub・npm・PyPI に波及し、影響確認ガイドを公開</li>
</ul>
<p>どれも登壇内容に直結するテーマです。情報を収集しながらスライドと原稿に反映し、同時にブログとしても公開していました。スライド、原稿、ブログ——複数の成果物を並行して、継続的に更新し続ける状況です。</p>
<p>ここで力を発揮したのが、Claude を活用したワークフローでした。</p>
<h2 id="やり方-データをリポジトリに入れて、Claude-に反映させる">やり方: データをリポジトリに入れて、Claude に反映させる</h2><p>採ったやり方は単純です。新しいデータや発見が出たら、まず GitHub リポジトリに追加する。形式は何でもいい。Markdown でも、テキストファイルでも、画像でも、PDF でも。とにかくリポジトリに入れておく。スライドや原稿への反映は Claude に頼む。</p>
<p>リポジトリの中は、大きく「素材」と「成果物」に分けています。</p>
<figure class="highlight sh"><input type="checkbox" id="code-wrap-48k5kc-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-48k5kc-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">リポジトリ/</span><br><span class="line">├── 素材       <span class="comment"># 調査データ、分析メモ、リサーチ結果</span></span><br><span class="line">├── 成果物     <span class="comment"># ブログ記事、登壇原稿、スライド</span></span><br><span class="line">└── CLAUDE.md  <span class="comment"># Claude への指示書</span></span><br></pre></td></tr></table></figure>

<p>素材の中身は自分が管理しやすい粒度で分ければいい。ファイル1つにまとめてもいいし、テーマごとにディレクトリを切ってもいい。実際、自分のリポジトリでは最初は大雑把にファイルを放り込んでいましたが、データが増えてきたタイミングで Claude と壁打ちしながら構成を見直しました。「このデータとこのデータは分けたほうが扱いやすいか？」「ブログ用のプランはどこに置く？」といった相談を Claude にして、ディレクトリ構成自体を育てていく。最初から完璧な設計を決める必要はありません。</p>
<p>たとえば、攻撃者が狙った7リポジトリの Scorecard 分析が終わったら、まず素材として追記する。</p>
<figure class="highlight txt"><input type="checkbox" id="code-wrap-48k5kc-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-48k5kc-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">case-studies/ に 7リポジトリの攻撃直前 Scorecard 分析結果を追加しました。</span><br><span class="line">登壇原稿の適切な場所に反映してください。</span><br><span class="line">スライドにも追加してください。</span><br></pre></td></tr></table></figure>

<p>今回の VulnCon 2026 の準備では、自分と共同登壇者の神戸さんが、それぞれの調査結果や発見を次々と同じリポジトリに追加していきました。日によって誰がどのデータを入れるかはバラバラです。でも、データの置き場がリポジトリに決まっているので、スライドのどこに入れるか、原稿のどの段落を書き換えるかは、Claude がリポジトリ全体を見て判断してくれます。</p>
<p>Claude にリポジトリごと見せておけば、原稿に新しいデータを追加したときにスライド側の整合性も一緒に確認してくれます。スライドと原稿の整合性チェックを丸ごと任せられたのは助かりました。</p>
<p>さらに言えば、Claude はデータが増えたときに単に差分を反映するだけではなく、新しい情報を踏まえてより良い構成を提案してくれることもありました。「この発見を入れるなら、Part 5 と Part 6 の順番を入れ替えたほうが流れがいい」といった提案です。データが増えるたびに構成が良くなっていく、という体験は、手作業ではなかなかできません。</p>
<p>また、準備中に過去の自社ブログや公開情報を思い出して「そういえば、これも関連するな」と気づくことがあります。そういうときは、とりあえずリポジトリに入れておく。あとは Claude に任せます。使うかどうか、どこに入れるかは Claude が判断してくれます。素材の投入に迷わなくていいので、思いついたらすぐリポジトリに放り込む癖がつきました。</p>
<h2 id="なぜ-Slidev-か">なぜ Slidev か</h2><p>VulnCon のスライドは Slidev で作りました。Markdown でスライドが書けるツールです。</p>
<p>Slidev を選んだ一番の理由は、Claude との相性です。Claude はテキストファイルの読み書きが得意なので、Markdown で書かれたスライドなら中身を理解して直接編集できます。PowerPoint でも扱えますが、テキストベースのほうが編集がスムーズです。リポジトリのデータからスライドへの流れを Claude に任せたいなら、スライドも Markdown で書いておくのが自然でした。</p>
<p>たとえば、VulnCon で実際に使ったスライドを2枚紹介します。</p>
<p>1枚目は、サプライチェーン攻撃で CVE が採番されなかった理由を整理したスライドです。</p>
<figure class="highlight markdown"><input type="checkbox" id="code-wrap-48k5kc-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-48k5kc-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="section"># Why No CVE?</span></span><br><span class="line"></span><br><span class="line">| What happened                          | Why it falls outside CVE scope                       |</span><br><span class="line">| -------------------------------------- | ---------------------------------------------------- |</span><br><span class="line">| Workflow misconfiguration exploited    | Site-specific misconfiguration, not a product defect |</span><br><span class="line">| Repository hijacked via stolen PAT     | Platform behavior abuse, not a code flaw             |</span><br><span class="line">| RCE on CI/CD runner                    | Environment-specific, not tied to a software version |</span><br><span class="line">| Malicious code / compromised artifacts | ✅ CVE-2026-28353, CVE-2026-33634                    |</span><br><span class="line"></span><br><span class="line"><span class="quote">&gt; CVEs captured the <span class="strong">**artifacts**</span>, but missed the <span class="strong">**attack chain**</span> that produced them.</span></span><br></pre></td></tr></table></figure>

<p>この Markdown が、こういうスライドになります。</p>
<img src="/images/2026/20260422a/slide-why-no-cve-18.png" alt="slide-why-no-cve-18.png" width="1200" height="676" loading="lazy">

<p>2枚目は、この攻撃をどうやって知ったかを聴衆に問いかけるスライドです。</p>
<figure class="highlight markdown"><input type="checkbox" id="code-wrap-48k5kc-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-48k5kc-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">---</span><br><span class="line"><span class="section">layout: center</span></span><br><span class="line"><span class="section">---</span></span><br><span class="line"></span><br><span class="line"><span class="section"># How Did We Learn About This Attack?</span></span><br><span class="line"></span><br><span class="line">~~CVE~~ <span class="literal">&amp;nbsp;</span> ~~NVD~~ <span class="literal">&amp;nbsp;</span> ~~Vulnerability Scanner~~ <span class="literal">&amp;nbsp;</span> ~~CSPM~~</span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">v-click</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="section">## We learned about it on <span class="strong">**X**</span></span></span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">v-click</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">v-click</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="section">## This is the CVE Blind Spot</span></span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">v-click</span>&gt;</span></span></span><br></pre></td></tr></table></figure>

<p>実際のスライドはこうなります。</p>
<img src="/images/2026/20260422a/slide-how-did-we-learn-19.png" alt="slide-how-did-we-learn-19.png" width="1200" height="676" loading="lazy">

<p><code>&lt;v-click&gt;</code> は Slidev の機能で、クリックするたびに次の要素が表示されます。まず CVE や NVD が取り消し線で並んでいて、クリックすると「X（旧 Twitter）で知った」が出てくる。もう一度クリックすると「これが CVE の盲点だ」と結論が出る。こういうプレゼンの「間」を、Markdown で書ける。Claude はこの Markdown を直接読み書きできるので、「この v-click の順番を入れ替えて」といった修正もそのまま頼めます。レイアウトの調整も Markdown と CSS の範囲で完結するので、ストレスなく進められました。</p>
<p>VulnCon の準備では、登壇原稿からスライドを起こすこともあれば、スライドを先に直して原稿に反映させることもありました。どちらが先でもいい。原稿とスライドの両方がリポジトリにあるので、Claude が双方向に反映してくれます。</p>
<p>今回は英語での登壇だったので、スライドと原稿の役割分担にも気を配りました。スライドには情報を多めに載せて、聴衆が視覚的に追えるようにする。一方で原稿の英語表現はシンプルに抑えて、ノンネイティブでも話しやすくする。こういう「整合性は保ちつつ、それぞれの役割に合わせてアレンジする」ことも、両方のファイルを同時に見られる Claude だからできることでした。</p>
<h2 id="ブログ記事も同じリポジトリから">ブログ記事も同じリポジトリから</h2><p>VulnCon の登壇内容をブログ記事にする場合も、同じリポジトリから直接書けます。データはすでにリポジトリにある。登壇原稿もある。ブログ用に新たにデータを集め直す必要がありません。</p>
<p>やることは、リポジトリの中の素材を踏まえて、どういうブログを書いてほしいかを Claude に伝えるだけです。</p>
<figure class="highlight txt"><input type="checkbox" id="code-wrap-48k5kc-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-48k5kc-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">リポジトリの素材を使って、サプライチェーン攻撃の影響確認ガイドをブログ記事として書いて。</span><br><span class="line">対象読者は FutureVuls ユーザ。</span><br></pre></td></tr></table></figure>

<p>Claude はリポジトリ内のファイルを直接読めるので、数値をコピペし直す手間がありません。素材に書いてある数値を、Claude がそのまま引用してくれます。</p>
<p>もちろん、生成された記事をそのまま公開するわけではありません。生成した原稿に対して Claude 自身にセルフチェックさせたり、PR を作成して GitHub Copilot をレビュアーにアサインしたりしながら推敲を重ねています。複数の AI の視点を入れることで、一人では気づきにくい表現の不自然さや論理の飛躍を拾えます。</p>
<p>ブログの執筆途中で新しい発見があったときも同じです。素材をリポジトリに追加して、「この情報を既存の記事に入れて」と頼めば、Claude が記事の流れを読んで適切な場所に追加してくれる。つまり、最初の執筆だけでなく「編集」も任せられる。スライドと原稿のときと同じ話で、新しいデータが出てきたら、自分はリポジトリに入れるだけでいい。</p>
<h2 id="CLAUDE-md-で表記を揃える">CLAUDE.md で表記を揃える</h2><p>成果物が増えてくると、スライドとブログで数値の書き方を揃えたくなります。そこで <code>CLAUDE.md</code> をリポジトリのルートに置いています。</p>
<p>Claude Code はセッション開始時にこのファイルを自動で読み込みます。</p>
<figure class="highlight markdown"><input type="checkbox" id="code-wrap-48k5kc-6" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-48k5kc-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="section">## ブログ文体ルール</span></span><br><span class="line"><span class="bullet">-</span> 主張・論考・体験記系 → だ・である体</span><br><span class="line"><span class="bullet">-</span> 実務 How-to・解説系 → です・ます体</span><br><span class="line"></span><br><span class="line"><span class="section">## よく使う数字（実測値）</span></span><br><span class="line">| 指標 | 値 |</span><br><span class="line">|------|-----|</span><br><span class="line">| Scanner バイナリサイズ | 106.6 MB → 34.1 MB（-68%） |</span><br><span class="line">| trivy 由来の依存 | 352 → 144（-59%） |</span><br></pre></td></tr></table></figure>

<p>ここに正規の数値を載せておくと、スライドでもブログでも「-68%」で統一されます。「約7割減」ではなく正確な数値で統一できます。出典が PR なのか Issue なのかも、ここで決めておけばブレません。</p>
<p>最初は「まあ書いておくか」くらいの気持ちで作りましたが、成果物の本数が増えるほど効果を実感しました。</p>
<h2 id="リポジトリ操作も-Claude-に任せる">リポジトリ操作も Claude に任せる</h2><p>ファイルの作成・編集、<code>git commit</code>、<code>git push</code>、Issue の起票——こういったリポジトリ操作は基本的に Claude に任せています。</p>
<p>Claude Code は <code>gh</code> コマンドも叩けるので、Issue の起票は <code>gh issue create</code> で済みます。自分は「データを集める」「指示を出す」「出来上がりを確認する」に絞れます。</p>
<h3 id="壁打ちの結果を-Issue-に残す">壁打ちの結果を Issue に残す</h3><p>Claude との壁打ちで出た気づきや方針変更は、そのまま GitHub Issue にしておく。これが地味に効きました。</p>
<p>たとえば、VulnCon の Q&amp;A 準備をしているとき。</p>
<figure class="highlight txt"><input type="checkbox" id="code-wrap-48k5kc-7" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-48k5kc-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">Issue #xx: Q&amp;A セッション用のバックアップスライドを追加する</span><br></pre></td></tr></table></figure>

<p>Issue の起票自体も Claude に頼みます。翌日の別セッションで「Issue #xx の内容を踏まえてスライドを追加して」と言えば、前回の議論を引き継げる。</p>
<p>Claude とのセッションは消えますが、リポジトリに書いたものは消えません。Issue やコミットログが、セッション間の記憶の代わりになります。</p>
<h3 id="Perplexity-で素材を足す">Perplexity で素材を足す</h3><p>一次データの補強に Perplexity を使いました。</p>
<p>ブログに書く数値の裏取り——「endoflife.date のカバレッジは2026年4月時点で何件か」「gorilla&#x2F;mux はいつアーカイブされたか」——こういった確認を Perplexity に投げて、返ってきた結果を出典ごとリポジトリに追記しておく。</p>
<p>Claude はリポジトリの中身しか見ないので、Perplexity で集めた情報もリポジトリに入れておかないと使えません。リポジトリに素材が増えるほど、Claude が書ける内容の幅が広がります。</p>
<h2 id="実践を通じた所感">実践を通じた所感</h2><p>1ヶ月やってみて思ったのは、結局自分の仕事は「自分で試行錯誤して、それを記録すること」だった、ということです。こういうデータは自分で手を動かさないと生まれない。でも逆に、試行錯誤の記録さえリポジトリにあれば、そこから先は Claude に任せられる。</p>
<p>リポジトリに入れる素材は、Markdown で書くこともあれば、画像や PDF をそのまま放り込むこともありました。きれいに整形してから入れる必要はない。ただ、スライドや原稿のように Claude に直接編集させたい成果物は Markdown にしておくと、この流れが途切れません。</p>
<p>ディレクトリ構成は最初の数日で試行錯誤しましたが、構成が固まってからは、データを入れて反映を頼むだけのループになりました。VulnCon の直前まで新しい発見を出し続けられたのは、このループが軽かったからだと思います。</p>
<h2 id="運用上の留意点">運用上の留意点</h2><p>Claude が書いた文章は、必ず自分でレビューしています。<code>CLAUDE.md</code> で数値を揃えていても、文脈に合わない場所で引用されることはあるので、数値と前後の文脈の両方を確認する。</p>
<p>また、Claude の修正を完全には信用せず、適用漏れや横展開漏れがないかをチェックし続けることも大事です。たとえば「この表現を全箇所修正して」と頼んでも、一部が抜けていることがある。このチェック自体に、Claude 自身のセルフチェックや GitHub Copilot の PR レビューなど、複数の AI を組み合わせると抜け漏れを拾いやすくなります。</p>
<p>なお、Claude に <code>git push</code> や <code>gh issue create</code> を任せる以上、リポジトリはプライベートで運用し、機密情報は直接書かないようにしています。</p>
<h2 id="おわりに">おわりに</h2><p>VulnCon 2026 の準備を通じてたどり着いたやり方は、<strong>データを GitHub リポジトリに集めて、成果物への落とし込みは Claude に任せる</strong>、というものでした。</p>
<p>新しい発見が出たらリポジトリに追加する。スライドや原稿への反映は Claude がやる。準備期間中にデータが増え続けても、リポジトリにさえ入れておけば、成果物は追いつく。</p>
<p>共通の情報源から、スライド・原稿・ブログといった複数の成果物を作る必要があり、しかもデータが継続的に増えていく——今回の VulnCon 準備は、まさにこのやり方が合うケースでした。同じような状況に直面している方の参考になれば幸いです。</p>
<p>なお、この記事自体も同じリポジトリから Claude で書いています。コミットログを見ると <code>Co-Authored-By: Claude</code> の記録が残っています。VulnCon の登壇準備でスライドや原稿を作るつもりで始めたリポジトリが、試行錯誤の記録が溜まった結果、こうしてワークフロー自体を紹介するブログ記事にまで繋がりました。リポジトリに記録を残しておくと、思わぬ形で次の成果物が生まれることがあります。</p>
]]></content>
    <summary type="html">調査データや発見を GitHub リポジトリにひたすら追加していく。スライド・原稿・ブログへの反映は Claude に任せる</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="Claude" scheme="https://future-architect.github.io/tags/Claude/"/>
    <category term="GitHub" scheme="https://future-architect.github.io/tags/GitHub/"/>
  </entry>
  <entry>
    <title>Agentic AI Summit '26 Spring 参加レポート</title>
    <link href="https://future-architect.github.io/articles/20260416a/"/>
    <id>https://future-architect.github.io/articles/20260416a/</id>
    <published>2026-04-15T15:00:00.000Z</published>
    <updated>2026-04-15T15:00:00.000Z</updated>
    <author><name>古賀晴大</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260416a/top.jpg" alt="" width="372" height="259">

<h2 id="はじめに">はじめに</h2><p>2025年10月新卒入社で、製造・エネルギー事業部所属の古賀です。<br>2026年3月19日（木）に開催されたGoogle Cloud 主催の「Agentic AI Summit ‘26 Spring」に参加してきました。</p>
<p>本イベントでは、単なるタスクを自動化するAIから進化した、自律的に思考し行動する「Agentic AI」をメインテーマとしており、自律型AIがいかに業務プロセスを変革し、ビジネス成果をもたらすかを学びました。</p>
<p> 今回は、イベントで発表された多岐にわたる技術を「5つの領域」に整理し、それぞれの領域で「どのような変革が起きるのか」と「それを支える具体的な技術・サービス」を紐づけてまとめました。</p>
<h2 id="1-業務改革の領域">1. 業務改革の領域</h2><h3 id="変革">変革</h3><p><strong>これまで</strong><br>現在のAIは非常に優秀ですが、ユーザや組織の文脈を知らないため、毎回ゼロから詳細な背景を説明する必要があります。また、情報のサイロ化が起き、データが様々なシステムに分断されている状態です。そのため、現在のAIは例えるなら「中途入社初日の新人」のような状態であり、プロンプトを出せる限られた従業員しか恩恵を受けられないという問題を抱えています。</p>
<p><strong>これから</strong><br>Gemini Enterpriseを使用することで、AIを新人から文脈を理解できる「育てるチームメイト」にしていくことが可能です。これにより、個別の作業を自動化する「点の自動化」から、分断されたプロセス全体をカバーする「プロセスの自律化」へと移行することが可能になります。</p>
<h3 id="技術・サービス">技術・サービス</h3><p><strong>Gemini Enterprise</strong><br>社内外のデータソースと安全に接続できる基盤であり、AIエージェント（自社開発、購入したもの、Google標準）が稼働するプラットフォームとして機能するSaaSプロダクトです。長期記憶（オフィスアプリ等のパーソナライズドデータソース）とパーソナライズによってユーザの過去の行動や好み、特性を記憶し、個別の状況に合わせた提案等が可能になります。さらに、マルチモーダルRAGや各種コネクタを用い、散在する社内データ（CRM、Drive、メール等）にアクセスし、人間と同じように情報を検索・統合できる環境を構築できます。</p>
<h2 id="2-エンジニアリングの領域">2. エンジニアリングの領域</h2><h3 id="変革-1">変革</h3><p><strong>これまで</strong><br>「Gemini 3」モデルでは、アーキテクチャの相談、リファクタリング、テストコード生成、仕様書作成など、開発ライフサイクル全体を支えることが可能です。しかし、LLMでのコード生成はブラウザや専用アプリ上で行われているため、開発環境（IDE）との往復が手間という問題もあります。さらに、人間の処理能力（タイピング速度や思考スピード）が依然として開発のボトルネックになるという構造的な限界が存在しています。</p>
<p><strong>これから</strong><br>人間がコードを書き、AIがそれを支援する時代から、AIが自律的な部下としてワークフロー全体を主導する時代へとシフトしています。これにより人間の役割は「コードを書くこと」から「何を開発するか」「AIが立てた計画の承認」「最終的な成果物のレビュー」へとシフトし、圧倒的なスピードとスケーラビリティでの開発が可能となります。</p>
<h3 id="技術・サービス-1">技術・サービス</h3><p><strong>Gemini Code Assist（支援型）</strong><br>エンジニアが使い慣れたIDE（開発環境）とAIを統合し、シームレスな体験を提供するサービスです。開発者が開いているファイルやプロジェクト構造（コンテキスト）を自動で認識し、的確な提案を実施します。またインライン補完やチャットベースでのコード生成・解説が可能です。</p>
<p><strong>Antigravity（自律型）</strong><br>エージェント主導の開発プラットフォームです。VS CodeベースのUIを使用しており、Gemini 3だけでなく、ClaudeやOSSモデルなどマルチモデルに対応しています。また、フロントエンド用、バックエンド用など複数のエージェントを立ち上げ、並列で同時開発を進行できます。さらに、エージェントが自律的にブラウザを立ち上げ、描画崩れやロジックの動作確認を自動で実施（動画やスクリーンショットで人間に報告）できます。</p>
<h2 id="3-エージェント運用の領域">3. エージェント運用の領域</h2><h3 id="変革-2">変革</h3><p><strong>これまで</strong><br>企業において複数のAIエージェントが開発・導入されるマルチエージェント時代を迎えるに伴い、「本当に使われているのか」「ROIは出ているのか」「現在のエージェントが最適か」などの懸念が生まれています。また、従来のBIツール（ダッシュボード）では、定型的な指標の確認は可能ですが、想定外の事象に対する深掘り分析には、データアナリストへ依頼が必要となり「タイムロス」が発生していました。</p>
<p><strong>これから</strong><br>エージェントの行動を可視化し、評価と改善のサイクルを回すこと、そして得られた対話データをビジネスインサイトに繋げることが成功要因の1つとなってきます。さらに、構造化データだけでなく、画像・動画・PDFやグラフデータなどの非構造化データを駆使し、誰もがデータからインサイトを得られる「データの民主化」を実現し、データを統合的に扱える基盤を構築することが不可欠になります。</p>
<h3 id="技術・サービス-2">技術・サービス</h3><p><strong>Vertex AI Agent Builder</strong><br>AIエージェントの開発から運用までをエンドツーエンドで支援する統合プラットフォームです。AgentOps機能が含まれており、プロンプトとレスポンスの文脈からAIが自動で採点基準を生成する「適応型ルーブリック」による回答品質の評価や、安全性・ハルシネーションの監視などを行えます。これによって継続的な改善のループを回し続けることが可能です。</p>
<p><strong>BigQuery Agent Analytics</strong><br>AIエージェントの行動ログ（ユーザとの対話履歴など）をリアルタイムでBigQueryに収集し、Gemini Enterpriseのような分析用エージェントを使って高度に分析するための機能です。開発時に数行のコードを追加するだけでログ収集が始まり、その後は自然言語で問いかけるだけでAIが自律的にSQLを生成し、対話ログから顧客のペインポイント（VoC）を直接検知するだけでなく、「なぜAIが答えられなかったのか」といったエラー原因の特定（AgentOps）までを行ってくれます。</p>
<h2 id="4-顧客体験の領域">4. 顧客体験の領域</h2><h3 id="変革-3">変革</h3><p><strong>これまで</strong><br>従来の顧客接点は、ユーザ自身が手動で情報を入力して検索や購買をする「機能的」なサイトが主流でした。検索やコマースのプロセスは断片化されており、消費者が期待するパーソナライズされた結果や代行アクションに十分に応えられていませんでした。</p>
<p><strong>これから</strong><br>これからは、AIがユーザに寄り添う「没入型（イマーシブ）」な顧客体験への移行が実現します。断片化された検索やコマースはシームレスな体験へと変わり、AIエージェントがユーザに代わって自律的に情報収集や代行（購買）アクションを実行します。</p>
<h3 id="技術・サービス-3">技術・サービス</h3><p><strong>Gemini Enterprise for Customer Experience（GECX）</strong><br>断片化された検索やコマースをシームレスな顧客体験へと変える統合ソリューションスイートです。CX Agent Studioを含めた様々な要素で構成されており、自社データに基づく正確な回答を導く次世代検索エンジン（Vertex AI Search）や、会話データの分析と可視化を行うCustomer Experience Insightsが含まれています。</p>
<p><strong>CX Agent Studio</strong><br>エージェント構築・テスト・デプロイを担う基盤です。自然言語による指示だけでAIがフローを自動生成するローコード開発により、開発期間を短縮します。また、他社SaaS等と連携した実務アクションの実行や、自然な「割り込み」に対応する高品質な音声対話機能も備えています。さらに、不適切な発言やプロンプトインジェクションを防ぐガードレール機能といったエンタープライズ品質の安全性を標準搭載しつつ、推論プロセスの可視化や「Quality AI」による全対話の自動評価を行えます。</p>
<p><strong>ADK Gemini Live API Toolkit</strong><br>リアルタイム音声AIエージェントの開発ツールキットです。これまで開発の壁となっていた複雑なストリーミング通信の制御を簡略化し、AIが話している途中でユーザが「割り込み」できる、人間のように自然な双方向の会話を実現します。また、ネイティブオーディオモデルが声のトーンから感情を読み取る機能や、スマートフォンのカメラ映像をリアルタイムに処理するマルチモーダル機能も備えています。</p>
<h2 id="5-セキュリティの領域">5. セキュリティの領域</h2><h3 id="変革-4">変革</h3><p><strong>これまで</strong><br>攻撃者による生成AIの利用が一般化し、フィッシングメールの巧妙化や、脆弱性分析の自動化など攻撃手法が高度化しています。そのため、従来の人間主体の監視・対応では、AIによる高度な攻撃スピードに追い付くことが困難になりつつあります。</p>
<p><strong>これから</strong><br>AIエージェントを活用してSOCのワークロードを低減し、検知・対応の質を向上させる「Agentic SOC」への移行が求められます。現在はアラート発生時のトリアージ・調査・マルウェア解析の支援を実現していますが、将来的には、調査結果への対応、自動ルールチューニングまでをAIが一貫して実行し、ライフサイクル全般をAIが自律的に実行する世界に変わっていくことになります。さらに、AIを防御に活用するだけでなく、AIモデルや学習データそのものを守るための保護策も不可欠になります。</p>
<h3 id="技術・サービス-4">技術・サービス</h3><p><strong>トリアージ・エージェント</strong><br>アラート発生時の初期調査を自動化するAIエージェントです。これまで人間が約30分かけて行っていた約20ステップの調査を、約1分で完了させることが可能になり、アラートが陽性か偽陽性かの一次判定を迅速に行えます。</p>
<p><strong>Model Armor</strong><br>生成AIアプリケーションを脅威から守るためにGoogle Cloudが提供している保護機能です。プロンプトインジェクションのブロックや、不適切な回答を抑制するためのフィルタリング機能を持っています。組織全体のガバナンス基準を適用でき、感度のチューニングも可能となっています。</p>
<h2 id="まとめ">まとめ</h2><p>業務改革やエンジニアリングの現場では、AIが自社の文脈を理解して自律的なチームメイトとして働き、顧客接点においてはAIが主体となってユーザに寄り添う没入型の体験が実現しつつあります。 一方で、こうした自律型AIをビジネスで確実に機能させるためには、エージェントの行動を可視化して継続的に改善を回す「運用基盤」と、AIモデルやデータそのものを脅威から守る強固な「セキュリティ体制」が両輪として不可欠であると学びました。</p>
<p>今回の学びを活かし、自律型AIをどのように日々の業務に適応させ、新しいプロセスをデザインしていくかについて、考えていきたいと思います。</p>
]]></content>
    <summary type="html">2026年3月19日（木）に開催されたGoogle Cloud 主催の「Agentic AI Summit '26 Spring」に参加してきました。本イベントでは、単なるタスクを自動化するAIから進化した、自律的に思考し行動する「Agentic AI」をメインテーマとしており、自律型AIがいかに業務プロセスを変革し、ビジネス成果をもたらすかを学びました。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="Gemini" scheme="https://future-architect.github.io/tags/Gemini/"/>
    <category term="GoogleCloud" scheme="https://future-architect.github.io/tags/GoogleCloud/"/>
    <category term="参加レポート" scheme="https://future-architect.github.io/tags/%E5%8F%82%E5%8A%A0%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88/"/>
  </entry>
  <entry>
    <title>IT未経験の新卒が、AIとPJ効率化ツールを共同開発して得た学び</title>
    <link href="https://future-architect.github.io/articles/20260414a/"/>
    <id>https://future-architect.github.io/articles/20260414a/</id>
    <published>2026-04-13T15:00:00.000Z</published>
    <updated>2026-04-13T15:00:00.000Z</updated>
    <author><name>實藤陸</name></author>
    <content type="html"><![CDATA[<h2 id="はじめに">はじめに</h2><p>初めまして、實藤陸（さねふじりく）です。<br>2025年新卒入社で、現在は製造エネルギーサービス事業部に所属しています。</p>
<p>今回は新卒入社の私がプロジェクト効率化ツールとして、GAS（文字起こし自動追加GAS）の開発をAIと共同で行いましたので、その内容について共有できればと思います！</p>
<div class="note-container note-info note-has-title"><div class="note-title"><span class="note-icon"></span>本記事のメイントピック</div><div class="note-body">

<p>本記事では、開発したツールの概要だけでなく、<strong>IT未経験の新卒が、どうやってAI（Gemini）に指示を出し、実運用レベルのシステムを作り上げたか</strong>というAIとの共同開発プロセスに重きを置いて学びを共有します。</p>
</div></div>

<h2 id="開発の背景と課題">開発の背景と課題</h2><p>今回の開発に至った経緯は、PJにおいて下記のような背景と課題があったためです。</p>
<table>
  <tbody>
    <tr>
      <td width="10%"><b>背景</b></td>
      <td>Meet会議で生成されるGeminiの文字起こしや録画ファイルを、PJ共有ドライブに手動で蓄積し、振り返りに活用していた。</td>
    </tr>
    <tr>
      <td><b>課題</b></td>
      <td>ファイルは主催者のマイドライブ内 <code>Meet Recordings</code> フォルダに自動保存される仕様。そのため、<b>共有ドライブへの移動の手間や移動漏れ</b>が頻発し、そのたびにSlackで依頼するコミュニケーションコストが発生していた。</td>
    </tr>
  </tbody>
</table>

<p>そこで、この一連の作業を自動化するGASの開発に着手しました。</p>
<h2 id="文字起こし自動追加GASの概要">文字起こし自動追加GASの概要</h2><p>まずは作成したツールの概要を紹介します。<br>文字起こし自動追加GASは簡潔に言うと、Google Meetの文字起こしや録画ファイルを、所定のフォルダに自動で整理し、Slackに通知するGASです！</p>
<p>2つのGASを用いて、Google Meet（のGemini機能）によって自動生成され、個人のマイドライブに保存される『文字起こし・録画・チャット』ファイルを、PJ共有ドライブ内の各フォルダへ移動し、Slackへ通知します。</p>
<h3 id="GASのざっくり構成図">GASのざっくり構成図</h3><img fetchpriority="high" src="/images/2026/20260414a/image.png" alt="image.png" width="1200" height="329">

<h3 id="それぞれのGASの内容">それぞれのGASの内容</h3><ol>
<li><strong>GAS（1）：各自のマイドライブから「一時プール」へ（各自で実行）</strong><ul>
<li>各自のマイドライブから30分おきに自動で実行。</li>
<li>ファイル名の先頭に <code>YYYYMMDD_</code> を追加し、特定キーワード（顧客名など）を含む会議だけを共有ドライブ内の「一時プールフォルダ」へ移動させます。</li>
</ul>
</li>
<li><strong>GAS（2）：プールから各フォルダへ仕分け＆通知（管理者が実行）</strong><ul>
<li>毎日夜間に自動実行。</li>
<li>プール内のファイルを「顧客会議用」「内部会議用」などに自動で振り分け、移動件数やエラー結果をSlackに通知します。</li>
</ul>
</li>
</ol>
<div class="note-container note-info note-has-title"><div class="note-title"><span class="note-icon"></span>なぜSlack通知するのか？</div><div class="note-body">

<p>処理のログを残し、正しい移動先へ格納されたか（あるいはエラーが起きていないか）を可視化して、メンバー全員が確認できるようにするためです。</p>
</div></div>

<p>これにより、どのメンバーが会議の主催者であっても、「文字起こし・録画・チャット」ファイルをPJの共有ドライブの正しいフォルダへ自動で蓄積できるようになり、当初の課題が解決できました！</p>
<h2 id="AIとの共同開発のプロセス">AIとの共同開発のプロセス</h2><p>今回の開発において大いに助けられたのが、Geminiの存在でした。</p>
<p>自分はIT未経験入社の新卒で、GASに関する知見はまったくない状態でした。そのため、最初から完璧な設計をするのではなく、とりあえずAIに作ってもらい、動くものを見ながらツッコミを入れて改善していくという<strong>アジャイル的な開発プロセス</strong>をとりました。</p>
<p>ここではコードを1行も書くことなく、GASを完成させたプロセスをご紹介します。</p>
<h3 id="（1）-まずは「動くGAS」を作る！">（1） まずは「動くGAS」を作る！</h3><p>開発のスタートは、「どうすればいいんだ」というざっくりとした質問をGeminiに投げることでした。<br>当時の私は何をすればいいかよくわかっていなかったため、綿密な要件定義は行わず、以下のようなニュアンスで指示を出しました。</p>
<blockquote>
<p><strong>💬 Geminiへの指示のイメージ</strong><br>「Google Meetの文字起こしファイルを、マイドライブから共有ドライブに集めたい。とりあえず進め方と動くGASのコードを考えて」</p>
</blockquote>
<p>こんな抽象度の高いプロンプトでも、Geminiは大枠の方針を示し、最低限動くコードを一瞬で作成してくれました。</p>
<h3 id="（2）-人間が「観点」を与え、AIにブラッシュアップさせる">（2） 人間が「観点」を与え、AIにブラッシュアップさせる</h3><p>動くものができたことで、実際に運用するには何が足りないかが明確に見えてきました。ここからは、上長とも相談しながら、Geminiが作った初期コードに対して<strong>人間ならではの観点</strong>を追加していく反復作業に入りました。</p>
<p>具体的にGeminiにぶつけた観点を2つ紹介します。</p>
<h4 id="観点1：セキュリティと可視化">観点1：セキュリティと可視化</h4><p>初期のコードでは、Slackへの通知機能がなく移動結果が確認しにくかったり、フォルダのID等がコードに「ベタ打ち」されていたりと、セキュリティ観点がほぼ抜け落ちていました。<br>そこで、コードをレビューさせたり、ポイントで以下のようにツッコミを入れました。</p>
<blockquote>
<p><strong>💬 Geminiへのツッコミのイメージ</strong><br>「ここのフォルダIDやSlackのURL、コードに直接ベタ打ちで書いてるけどセキュリティ的に大丈夫？ベタ打ちを避ける方法で書き直して」</p>
</blockquote>
<p>この指示により、外部から見えない「スクリプトプロパティ」を使ってIDを隠す、安全なシステムへと改善してくれました。</p>
<h4 id="観点2：本番運用に向けたアーキテクチャの変更">観点2：本番運用に向けたアーキテクチャの変更</h4><p>当初の開発では、「1つのGASを各メンバーにデプロイしてもらい、各自のマイドライブから直接共有ドライブの各フォルダに振り分ける形式」となっていました。<br>しかし、この構成で本番展開しようとすると、SlackのWebhookやトリガー設定など各メンバーの初期設定の手間が大きすぎることが判明しました。<br>そこで上長と相談し、横展開のしやすさを最優先して「各自が投げるGASと、管理者が仕分けるGASの2つに分ける」という方針に変更しました。</p>
<blockquote>
<p><strong>💬 Geminiへの指示のイメージ</strong><br>「このツールをチームに横展開することを考えて、各自が投げるGASと管理者が仕分けるGASの2つにコードを分割して」</p>
</blockquote>
<p>このように、<strong>ざっくりとした要件でまず動くものを作り、必要な観点を後からぶつけていく</strong>というプロセスを繰り返すことで、無事に実運用できるツールを完成させることができました。</p>
<h2 id="本開発における学び">本開発における学び</h2><p>ここからは今回のAIとの共同開発における学びを共有できればと思います。</p>
<h3 id="Geminiってすごい">Geminiってすごい</h3><p>まずは、IT未経験で研修を受けた程度の自分が、今回の開発を業務と並行して短期間で完遂できたのは間違いなくGeminiのおかげです。<br>今回はGASということもあり、ほとんどエラーのない正確なコードを提供してくれました。<br>大感謝です。</p>
<h3 id="アジャイルな進め方がAI時代のスタンダード？">アジャイルな進め方がAI時代のスタンダード？</h3><p>今回のように未経験者がAIと協働開発をするには、最初から完璧な設計をするのではなく、<strong>とりあえずAIに作ってもらい、動くものを見ながらツッコミを入れて改善していくべきである</strong>と感じました（というかそれしか方法がない）。</p>
<p>さらに今後、AIとの共同開発が当たり前になる世界の中では、どんな難易度の開発であっても、コーディングにかかる時間が激減するため、こういったアジャイルな進め方が主流になっていくのではないかと思っています。</p>
<p>そう考えてみると、今回の進め方はまさに<strong>AIとの共同開発におけるスタンダード</strong>だったのではと個人的には感じています。</p>
<h3 id="人間がやるべきこと">人間がやるべきこと</h3><p>AIとの共同開発が当たり前になる世界のなかで、人間がやるべきこととして、主に下記2点が重要だと感じています。</p>
<ul>
<li><strong>なぜその機能が必要なのかを考えること</strong><br>Geminiは指示された通りのコードを作るのは得意な一方で、やはり背景を察したりはできません。<br>そのため、「この機能作って」というプロンプトではなく、「現状こんな課題があって、それを解決するためにこんな機能を作りたい」というプロンプトにする方が圧倒的に求めているものに近いアウトプットができます。<br>すなわち、<strong>実現したいことの背景や目的、ゴールを整理して示すこと</strong>がAIとの共同開発においては重要だと学びました。</li>
<li><strong>コードを理解し観点を与えること</strong><br>前述の通り、Geminiは最低限のコードは生成してくれますが、不足している観点があることも多いです（自分の知識不足も原因ではあります）。<br>そこで人間に求められるのは、<strong>コードの内容を正しく理解しレビューをする力</strong>だと思っています。<br>実際にその機能を運用することを想定して、多くの観点からレビューをすることで、AIとの共同開発でも品質の高いアウトプットが出せることを学びました。</li>
</ul>
<div class="note-container note-info note-has-title"><div class="note-title"><span class="note-icon"></span>学びの総括</div><div class="note-body">

<p>今後、AIの書くコードの正確性は高まっていく中で人間に求められる力としては、コードを理解する知識はもちろん、<strong>その機能を作る背景や目的・ゴールを正しく理解し、必要な観点を洗い出せる力</strong>なのではと感じています。</p>
</div></div>

<h2 id="おわりに">おわりに</h2><p>今回は文字起こし自動追加GASの概要と、AIとの共同開発での学びについて共有させていただきました。</p>
<p>PJ効率化ツールとして文字起こし自動追加GASに興味を持っていただけていたら、非常にうれしいです！<br>また、自分と同じ若手メンバーなど、IT初学者の方にとってAIとの共同開発の進め方が何かの役に立てば幸いです。</p>
<p>最後まで読んでいただきありがとうございました！</p>
<hr>
<details><summary><b>おまけ：今回作成したGASのソースコード（マスキング済）</b></summary>

<p>ご参考までに、実際にAIと作成して運用しているソースコード（※機密情報のみマスキング済）を掲載します。</p>
<figure class="highlight js"><input type="checkbox" id="code-wrap-1tif1pu-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1tif1pu-1" title="コードの折り返しを切り替える"></label><figcaption><span>GAS①：マイドライブから一時プールへ移動する処理（各自実行）</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * ■■■ 文字起こし・録画ファイル選別＆投げ込みボット（メンバー用） ■■■</span></span><br><span class="line"><span class="comment"> * */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">CONFIG</span> = &#123;</span><br><span class="line">  <span class="comment">// 1. 検索元フォルダ名</span></span><br><span class="line">  <span class="attr">sourceFolderName</span>: <span class="string">&#x27;Meet Recordings&#x27;</span>,</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 2. 移動先プールID（プロパティから取得）</span></span><br><span class="line">  <span class="attr">poolFolderId</span>: <span class="title class_">PropertiesService</span>.<span class="title function_">getScriptProperties</span>().<span class="title function_">getProperty</span>(<span class="string">&#x27;POOL_FOLDER_ID&#x27;</span>),</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 3. 移動対象キーワード（正規表現）</span></span><br><span class="line">  <span class="attr">targetKeywords</span>: [</span><br><span class="line">    <span class="string">&#x27;^\\d&#123;8&#125;_【顧客名A】&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;^\\d&#123;8&#125;_【顧客名B】&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;^\\d&#123;8&#125;_【内部プロジェクトA】&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;^\\d&#123;8&#125;_【内部プロジェクトB】&#x27;</span></span><br><span class="line">  ],</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 4. 対象ファイル形式</span></span><br><span class="line">  <span class="attr">targetMimeTypes</span>: [<span class="title class_">MimeType</span>.<span class="property">GOOGLE_DOCS</span>, <span class="string">&#x27;video/mp4&#x27;</span>, <span class="title class_">MimeType</span>.<span class="property">PLAIN_TEXT</span>]</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">moveToPoolWithFilter</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!<span class="variable constant_">CONFIG</span>.<span class="property">poolFolderId</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;❌ 設定エラー: POOL_FOLDER_ID が設定されていません。&#x27;</span>);</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> props = <span class="title class_">PropertiesService</span>.<span class="title function_">getScriptProperties</span>();</span><br><span class="line">  <span class="keyword">const</span> lastRunStr = props.<span class="title function_">getProperty</span>(<span class="string">&#x27;LAST_RUN_TIME&#x27;</span>);</span><br><span class="line">  <span class="keyword">const</span> now = <span class="keyword">new</span> <span class="title class_">Date</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// デフォルト検索開始日（過去すべて）</span></span><br><span class="line">  <span class="keyword">let</span> searchDateStr = <span class="string">&#x27;2000-01-01T00:00:00&#x27;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (lastRunStr) &#123;</span><br><span class="line">    <span class="keyword">const</span> lastDate = <span class="keyword">new</span> <span class="title class_">Date</span>(lastRunStr);</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">isNaN</span>(lastDate.<span class="title function_">getTime</span>())) &#123;</span><br><span class="line">      <span class="comment">// タイムラグ対策（1分前から）</span></span><br><span class="line">      lastDate.<span class="title function_">setMinutes</span>(lastDate.<span class="title function_">getMinutes</span>() - <span class="number">1</span>);</span><br><span class="line">      searchDateStr = <span class="title class_">Utilities</span>.<span class="title function_">formatDate</span>(lastDate, <span class="string">&#x27;GMT&#x27;</span>, <span class="string">&quot;yyyy-MM-dd&#x27;T&#x27;HH:mm:ss&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> query = <span class="string">`modifiedDate &gt; &#x27;<span class="subst">$&#123;searchDateStr&#125;</span>&#x27;`</span>;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`🔎 高速検索クエリ: <span class="subst">$&#123;query&#125;</span>`</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> sourceFolders = <span class="title class_">DriveApp</span>.<span class="title function_">getFoldersByName</span>(<span class="variable constant_">CONFIG</span>.<span class="property">sourceFolderName</span>);</span><br><span class="line">  <span class="keyword">const</span> poolFolder = <span class="title class_">DriveApp</span>.<span class="title function_">getFolderById</span>(<span class="variable constant_">CONFIG</span>.<span class="property">poolFolderId</span>);</span><br><span class="line">  <span class="keyword">const</span> regexPatterns = <span class="variable constant_">CONFIG</span>.<span class="property">targetKeywords</span>.<span class="title function_">map</span>(<span class="function"><span class="params">k</span> =&gt;</span> <span class="keyword">new</span> <span class="title class_">RegExp</span>(k));</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> movedCount = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">while</span> (sourceFolders.<span class="title function_">hasNext</span>()) &#123;</span><br><span class="line">    <span class="keyword">const</span> folder = sourceFolders.<span class="title function_">next</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> files = folder.<span class="title function_">searchFiles</span>(query);</span><br><span class="line"></span><br><span class="line">      <span class="keyword">while</span> (files.<span class="title function_">hasNext</span>()) &#123;</span><br><span class="line">        <span class="keyword">const</span> file = files.<span class="title function_">next</span>();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (file.<span class="title function_">isTrashed</span>()) <span class="keyword">continue</span>;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="variable constant_">CONFIG</span>.<span class="property">targetMimeTypes</span>.<span class="title function_">includes</span>(file.<span class="title function_">getMimeType</span>())) <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">          <span class="keyword">const</span> isMoved = <span class="title function_">processFile</span>(file, poolFolder, regexPatterns);</span><br><span class="line">          <span class="keyword">if</span> (isMoved) &#123;</span><br><span class="line">            movedCount++;</span><br><span class="line">          &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">          <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">`❌ ファイル処理エラー: <span class="subst">$&#123;file.getName()&#125;</span> (<span class="subst">$&#123;e.message&#125;</span>)`</span>);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">`❌ 検索エラー(query: <span class="subst">$&#123;query&#125;</span>): <span class="subst">$&#123;e.message&#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  props.<span class="title function_">setProperty</span>(<span class="string">&#x27;LAST_RUN_TIME&#x27;</span>, now.<span class="title function_">toISOString</span>());</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (movedCount &gt; <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`✅ 処理完了: <span class="subst">$&#123;movedCount&#125;</span>件のファイルを転送しました。`</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`✅ 新規転送なし（<span class="subst">$&#123;searchDateStr&#125;</span> 以降）`</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">processFile</span>(<span class="params">file, poolFolder, regexPatterns</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> originalName = file.<span class="title function_">getName</span>();</span><br><span class="line">  <span class="keyword">let</span> datePrefix = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> dateMatch = originalName.<span class="title function_">match</span>(<span class="regexp">/(\d&#123;4&#125;)[\/\-\s](0[1-9]|1[0-2])[\/\-\s](0[1-9]|[12]\d|3[01])/</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (dateMatch) &#123;</span><br><span class="line">    datePrefix = <span class="string">`<span class="subst">$&#123;dateMatch[<span class="number">1</span>]&#125;</span><span class="subst">$&#123;dateMatch[<span class="number">2</span>]&#125;</span><span class="subst">$&#123;dateMatch[<span class="number">3</span>]&#125;</span>`</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> createdDate = file.<span class="title function_">getDateCreated</span>();</span><br><span class="line">    datePrefix = <span class="title class_">Utilities</span>.<span class="title function_">formatDate</span>(createdDate, <span class="string">&#x27;Asia/Tokyo&#x27;</span>, <span class="string">&#x27;yyyyMMdd&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> newName = originalName;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (!originalName.<span class="title function_">startsWith</span>(datePrefix)) &#123;</span><br><span class="line">    newName = <span class="string">`<span class="subst">$&#123;datePrefix&#125;</span>_<span class="subst">$&#123;originalName&#125;</span>`</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> isMatch = regexPatterns.<span class="title function_">some</span>(<span class="function"><span class="params">regex</span> =&gt;</span> regex.<span class="title function_">test</span>(newName));</span><br><span class="line">  <span class="keyword">if</span> (!isMatch) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (file.<span class="title function_">getName</span>() !== newName) &#123;</span><br><span class="line">    file.<span class="title function_">setName</span>(newName);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (poolFolder.<span class="title function_">getFilesByName</span>(newName).<span class="title function_">hasNext</span>()) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`⚠️ [スキップ] プールに同名ファイルが存在: <span class="subst">$&#123;newName&#125;</span>`</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  file.<span class="title function_">moveTo</span>(poolFolder);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`⭕️ [転送成功] <span class="subst">$&#123;newName&#125;</span>`</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight js"><input type="checkbox" id="code-wrap-1tif1pu-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1tif1pu-2" title="コードの折り返しを切り替える"></label><figcaption><span>GAS②：プールから内部/外部フォルダへ仕分け＆Slack通知（管理者実行）</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * ==============================================================================</span></span><br><span class="line"><span class="comment"> * ■■■ 議事録・仕分け＆通知マスターボット（管理者用） ■■■</span></span><br><span class="line"><span class="comment"> * ==============================================================================</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">CONFIG</span> = &#123;</span><br><span class="line">  <span class="attr">poolFolderId</span>:     <span class="title class_">PropertiesService</span>.<span class="title function_">getScriptProperties</span>().<span class="title function_">getProperty</span>(<span class="string">&#x27;POOL_FOLDER_ID&#x27;</span>),</span><br><span class="line">  <span class="attr">internalFolderId</span>: <span class="title class_">PropertiesService</span>.<span class="title function_">getScriptProperties</span>().<span class="title function_">getProperty</span>(<span class="string">&#x27;INTERNAL_FOLDER_ID&#x27;</span>),</span><br><span class="line">  <span class="attr">externalFolderId</span>: <span class="title class_">PropertiesService</span>.<span class="title function_">getScriptProperties</span>().<span class="title function_">getProperty</span>(<span class="string">&#x27;EXTERNAL_FOLDER_ID&#x27;</span>),</span><br><span class="line">  <span class="attr">slackWebhookUrl</span>:  <span class="title class_">PropertiesService</span>.<span class="title function_">getScriptProperties</span>().<span class="title function_">getProperty</span>(<span class="string">&#x27;SLACK_WEBHOOK_URL&#x27;</span>),</span><br><span class="line"></span><br><span class="line">  <span class="attr">rules</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">name</span>: <span class="string">&#x27;外部&#x27;</span>,</span><br><span class="line">      <span class="attr">propKey</span>: <span class="string">&#x27;EXTERNAL_FOLDER_ID&#x27;</span>,</span><br><span class="line">      <span class="attr">keywords</span>: [<span class="string">&#x27;^\\d&#123;8&#125;_【顧客名A】&#x27;</span>,<span class="string">&#x27;^\\d&#123;8&#125;_【顧客名B】&#x27;</span>]</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">name</span>: <span class="string">&#x27;内部&#x27;</span>,</span><br><span class="line">      <span class="attr">propKey</span>: <span class="string">&#x27;INTERNAL_FOLDER_ID&#x27;</span>,</span><br><span class="line">      <span class="attr">keywords</span>: [<span class="string">&#x27;^\\d&#123;8&#125;_【内部プロジェクトA】&#x27;</span>,<span class="string">&#x27;^\\d&#123;8&#125;_【内部プロジェクトB】&#x27;</span>]</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">runBatchSort</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title class_">Logger</span>.<span class="title function_">log</span>(<span class="string">&#x27;--- 管理者バッチ処理開始 ---&#x27;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (!<span class="variable constant_">CONFIG</span>.<span class="property">poolFolderId</span> || !<span class="variable constant_">CONFIG</span>.<span class="property">internalFolderId</span> || !<span class="variable constant_">CONFIG</span>.<span class="property">externalFolderId</span> || !<span class="variable constant_">CONFIG</span>.<span class="property">slackWebhookUrl</span>) &#123;</span><br><span class="line">    <span class="title class_">Logger</span>.<span class="title function_">log</span>(<span class="string">&#x27;❌ エラー: スクリプトプロパティが不足しています。&#x27;</span>);</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> slackLogs = [];</span><br><span class="line">  <span class="keyword">const</span> poolFolder = <span class="title class_">DriveApp</span>.<span class="title function_">getFolderById</span>(<span class="variable constant_">CONFIG</span>.<span class="property">poolFolderId</span>);</span><br><span class="line">  <span class="keyword">const</span> files = poolFolder.<span class="title function_">getFiles</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> moveCount = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">let</span> remainCount = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">let</span> errorCount = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> rules = <span class="variable constant_">CONFIG</span>.<span class="property">rules</span>.<span class="title function_">map</span>(<span class="function"><span class="params">r</span> =&gt;</span> (&#123;</span><br><span class="line">    <span class="attr">folder</span>: <span class="title class_">DriveApp</span>.<span class="title function_">getFolderById</span>(<span class="title class_">PropertiesService</span>.<span class="title function_">getScriptProperties</span>().<span class="title function_">getProperty</span>(r.<span class="property">propKey</span>)),</span><br><span class="line">    <span class="attr">regex</span>: r.<span class="property">keywords</span>.<span class="title function_">map</span>(<span class="function"><span class="params">k</span> =&gt;</span> <span class="keyword">new</span> <span class="title class_">RegExp</span>(k)),</span><br><span class="line">    <span class="attr">name</span>: r.<span class="property">name</span></span><br><span class="line">  &#125;));</span><br><span class="line"></span><br><span class="line">  <span class="keyword">while</span> (files.<span class="title function_">hasNext</span>()) &#123;</span><br><span class="line">    <span class="keyword">const</span> file = files.<span class="title function_">next</span>();</span><br><span class="line">    <span class="keyword">const</span> fileName = file.<span class="title function_">getName</span>();</span><br><span class="line">    <span class="keyword">let</span> isMoved = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">const</span> rule <span class="keyword">of</span> rules) &#123;</span><br><span class="line">        <span class="keyword">if</span> (rule.<span class="property">regex</span>.<span class="title function_">some</span>(<span class="function"><span class="params">r</span> =&gt;</span> r.<span class="title function_">test</span>(fileName))) &#123;</span><br><span class="line"></span><br><span class="line">          <span class="keyword">if</span> (rule.<span class="property">folder</span>.<span class="title function_">getFilesByName</span>(fileName).<span class="title function_">hasNext</span>()) &#123;</span><br><span class="line">            <span class="keyword">const</span> msg = <span class="string">`⚠️ [重複] <span class="subst">$&#123;fileName&#125;</span> は <span class="subst">$&#123;rule.name&#125;</span> に既に存在するためスキップしました`</span>;</span><br><span class="line">            <span class="title class_">Logger</span>.<span class="title function_">log</span>(msg);</span><br><span class="line">            slackLogs.<span class="title function_">push</span>(msg);</span><br><span class="line">            isMoved = <span class="literal">true</span>;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">          &#125;</span><br><span class="line"></span><br><span class="line">          file.<span class="title function_">moveTo</span>(rule.<span class="property">folder</span>);</span><br><span class="line"></span><br><span class="line">          <span class="keyword">const</span> logMsg = <span class="string">`✅ [移動] <span class="subst">$&#123;fileName&#125;</span> ➡ <span class="subst">$&#123;rule.name&#125;</span>`</span>;</span><br><span class="line">          <span class="title class_">Logger</span>.<span class="title function_">log</span>(logMsg);</span><br><span class="line">          slackLogs.<span class="title function_">push</span>(logMsg);</span><br><span class="line"></span><br><span class="line">          moveCount++;</span><br><span class="line">          isMoved = <span class="literal">true</span>;</span><br><span class="line">          <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (!isMoved) &#123;</span><br><span class="line">         <span class="title class_">Logger</span>.<span class="title function_">log</span>(<span class="string">`⚠️ [残留] <span class="subst">$&#123;fileName&#125;</span> (条件不一致)`</span>);</span><br><span class="line">         remainCount++;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">    &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">      <span class="keyword">const</span> errorMsg = <span class="string">`❌ [エラー] <span class="subst">$&#123;fileName&#125;</span>: <span class="subst">$&#123;e.message&#125;</span>`</span>;</span><br><span class="line">      <span class="title class_">Logger</span>.<span class="title function_">log</span>(errorMsg);</span><br><span class="line">      slackLogs.<span class="title function_">push</span>(errorMsg);</span><br><span class="line">      errorCount++;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title class_">Logger</span>.<span class="title function_">log</span>(<span class="string">`処理終了: 移動<span class="subst">$&#123;moveCount&#125;</span>件 / 残留<span class="subst">$&#123;remainCount&#125;</span>件 / エラー<span class="subst">$&#123;errorCount&#125;</span>件`</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (slackLogs.<span class="property">length</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="title function_">sendDailyReport</span>(slackLogs, moveCount, remainCount);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title class_">Logger</span>.<span class="title function_">log</span>(<span class="string">&#x27;通知対象なしのためSlack送信をスキップします。&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">sendDailyReport</span>(<span class="params">logs, moveCount, remainCount</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> message = &#123;</span><br><span class="line">    <span class="string">&quot;text&quot;</span>: <span class="string">&quot;📊 *議事録 自動仕分けレポート*&quot;</span>,</span><br><span class="line">    <span class="string">&quot;blocks&quot;</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="string">&quot;type&quot;</span>: <span class="string">&quot;section&quot;</span>,</span><br><span class="line">        <span class="string">&quot;text&quot;</span>: &#123;</span><br><span class="line">          <span class="string">&quot;type&quot;</span>: <span class="string">&quot;mrkdwn&quot;</span>,</span><br><span class="line">          <span class="string">&quot;text&quot;</span>: <span class="string">`📊 *本日の仕分け結果*\n実行完了: <span class="subst">$&#123;moveCount&#125;</span>件 / プール残留: <span class="subst">$&#123;remainCount&#125;</span>件`</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123; <span class="string">&quot;type&quot;</span>: <span class="string">&quot;divider&quot;</span> &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="string">&quot;type&quot;</span>: <span class="string">&quot;section&quot;</span>,</span><br><span class="line">        <span class="string">&quot;text&quot;</span>: &#123;</span><br><span class="line">          <span class="string">&quot;type&quot;</span>: <span class="string">&quot;mrkdwn&quot;</span>,</span><br><span class="line">          <span class="string">&quot;text&quot;</span>: logs.<span class="title function_">join</span>(<span class="string">&quot;\n&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="title class_">UrlFetchApp</span>.<span class="title function_">fetch</span>(<span class="variable constant_">CONFIG</span>.<span class="property">slackWebhookUrl</span>, &#123;</span><br><span class="line">      <span class="string">&quot;method&quot;</span>: <span class="string">&quot;post&quot;</span>,</span><br><span class="line">      <span class="string">&quot;contentType&quot;</span>: <span class="string">&quot;application/json&quot;</span>,</span><br><span class="line">      <span class="string">&quot;payload&quot;</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(message)</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">    <span class="title class_">Logger</span>.<span class="title function_">log</span>(<span class="string">&#x27;Slack送信エラー: &#x27;</span> + e.<span class="property">message</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

</details>
]]></content>
    <summary type="html">新卒入社の私がプロジェクト効率化ツールとして、GAS（文字起こし自動追加GAS）の開発をAIと共同で行いましたので、その内容について共有できればと思います！</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="GAS" scheme="https://future-architect.github.io/tags/GAS/"/>
    <category term="初心者向け" scheme="https://future-architect.github.io/tags/%E5%88%9D%E5%BF%83%E8%80%85%E5%90%91%E3%81%91/"/>
  </entry>
  <entry>
    <title>API GatewayとLambdaで実装するプライベートなMCPサーバー</title>
    <link href="https://future-architect.github.io/articles/20260324a/"/>
    <id>https://future-architect.github.io/articles/20260324a/</id>
    <published>2026-03-23T15:00:00.000Z</published>
    <updated>2026-03-23T15:00:00.000Z</updated>
    <author><name>木村太陽</name></author>
    <content type="html"><![CDATA[<p>Strategic AI Group&#x2F;MLOpsチームでアルバイトをしている木村です。アルバイトでは最新技術の調査を担当し、社内や案件にて活用することを想定したシステム導入の検証を実施しています。プライベートではエンジニアにありがちな(?)運動不足解消として、マラソンをしていて、47都道府県制覇を目指しています。</p>
<p>今回はAWS上のLambdaで自作したプライベートなMCPサーバーをDify上で使用する手順について記事にします。これから社内でもどんどん広がっていくであろうMCPを使っていてとても面白かったので、ぜひ皆さんも本記事を参考に試してみてください！</p>
<h2 id="概要">概要</h2><p>「社内でLLMに様々なスキルを持たせたい」というのは、多くの企業が抱える要望です。本記事では、AWS Lambda上に自作のプライベートなMCP（Model Context Protocol）サーバーを構築し、それをDifyから呼び出す手順を解説します。</p>
<p>セキュリティを担保しつつ、誰もが簡単にAIアプリを構築できる環境の「第一歩」を目指します。</p>
<p>最終的には以下のようにDifyからMCPサーバーにアクセスできます。</p>
<p>以下のツールではこんにちはに対してHELLOを返すcalculate_helloというmcpツール、計算について、足し算、掛け算、引き算をするcalculate_add,calculate_product,calculate_subというmcpツールが使われています。</p>
<img fetchpriority="high" src="/images/2026/20260324a/image.png" alt="image.png" width="844" height="724">

<p>これを応用してWeb検索したり、Googleカレンダーに予定を自動で入れることができます。</p>
<h2 id="本記事のキーテクノロジー">本記事のキーテクノロジー</h2><h3 id="Dify">Dify</h3><ul>
<li>Dify公式サイト</li>
</ul>
<p>Difyは、LLMアプリを直感的に開発できるオープンソースのLLMアプリ開発プラットフォームです。RAG（検索拡張生成）やエージェント機能を手軽に実装できるのが特徴です。</p>
<p>本記事では社内ネットワークのEC2でDifyを動かすことでプライベートな構築を実現しています。</p>
<h3 id="MCP">MCP</h3><p>Anthropicが公開したMCPは(参考：Model Context Protocol（MCP）とは)、LLMと外部データ（DB、API、ローカルファイル等）を接続するためのオープンプロトコルです。これまではツールごとに専用の繋ぎ込みが必要でしたが、MCPという「共通規格」を通すことで、1つのMCPサーバーを作るだけで様々なAIクライアントからデータを利用可能になります。似ているものでRAGがありますがRAGは「知識の検索・参照」に特化しているのに対し、MCPは「機能（ツール）の呼び出し・実行」に特化しています。</p>
<img src="/images/2026/20260324a/image_2.png" alt="image.png" width="709" height="330" loading="lazy">

<h3 id="API-Gateway-Lambdaによるプライベートな環境">API Gateway+Lambdaによるプライベートな環境</h3><p>社内DBや秘匿性の高い情報が流出するリスクを排除するため、社内ネットワーク内での運用が求められるケースがあります。</p>
<p>今回は上記を実現するべく、API Gatewayのアクセスを社内ネットワークからに制限し、すべてのリソースを社内に置いておくことでプライベートな環境を実現することを目指しました。<br>※前提として、社内ネットワークとAWS環境がVPN接続されていることとします。</p>
<h3 id="通信方式">通信方式</h3><p>MCPには、ローカルMCP（stdio）とリモートMCP（Streamable HTTP）の2種類があります。ローカルMCPの場合、同じ環境を相手のPCにも構築する必要があり、共有に手間がかかってしまうので、リモートMCPを採用しました。</p>
<p>また、リモートMCPをサーバレスで実行するために通信方式は以下の設定にしています。(参考：MCPの通信方式)</p>
<ul>
<li>stateless_http: stateless_http&#x3D;True に設定し、本来Statefulな通信を前提とするMCPプロトコルをステートレスなHTTP通信に変換することでLambdaのような1回切りのリクエストに対応させます</li>
<li>Streamable_http：Lambdaでの1回限りの重い処理を、タイムアウトで切断される前に小出しで届けて完結させる</li>
</ul>
<h2 id="構成図">構成図</h2><p>本構成では、セキュリティを最優先し、API Gatewayを「プライベート」モードでデプロイします。これにより、社内ネットワークからのみAIツールを呼び出すことが可能になります。</p>
<img src="/images/2026/20260324a/image_3.png" alt="image.png" width="696" height="364" loading="lazy">

<h3 id="Lambda採用理由">Lambda採用理由</h3><p> EC2でMCPサーバをホストした場合、常時EC2を起動しておく必要があり、利用していない期間も不要な料金が発生します。そこでサーバレスなLambdaでホストすることで、MCPサーバーが呼ばれた時だけ料金が発生するので、コストを抑えられます。</p>
<h3 id="本構成のデメリット">本構成のデメリット</h3><p>大量のログ解析や複雑なデータ集計など、完了までに時間がかかるタスクを依頼すると、AIに結果が返る前にAPI Gatewayのタイムアウト制限より、接続が切れてエラーになる可能性があります。</p>
<p>また、VPCエンドポイントは月10ドルほどの固定費がかかるというデメリットも挙げられます。</p>
<h2 id="構築手順">構築手順</h2><ol>
<li>まずは開発環境（EC2）にAWS SAM CLIやPython 3.11をインストールし、必要なIAMロールを付与します</li>
<li>コードを作成してAWSにデプロイ</li>
</ol>
<p>ファイル構成は以下の通りです。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">.</span><br><span class="line">├── mcp_server</span><br><span class="line">│   ├── __init__.py</span><br><span class="line">│   └── __main__.py</span><br><span class="line">├── requirements.txt</span><br><span class="line">├── run.sh</span><br><span class="line">└── template.yaml</span><br></pre></td></tr></table></figure>

<p>mcp_server&#x2F;<code>__init__.py</code> は空ファイルで大丈夫です。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">touch</span> mcp_server/<span class="string">&#x27;__init__.py&#x27;</span></span><br></pre></td></tr></table></figure>

<figure class="highlight py"><input type="checkbox" id="code-wrap-1dk5xdj-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1dk5xdj-1" title="コードの折り返しを切り替える"></label><figcaption><span>mcp_server/__main__.py</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> uvicorn</span><br><span class="line"><span class="keyword">from</span> fastmcp <span class="keyword">import</span> FastMCP</span><br><span class="line"></span><br><span class="line"><span class="comment"># FastMCPの初期化</span></span><br><span class="line"><span class="comment"># stateless_http=True にすることで、Lambdaのような1回切りのリクエストに対応させます</span></span><br><span class="line">mcp = FastMCP(<span class="string">&quot;MyRemoteMCP&quot;</span>, stateless_http=<span class="literal">True</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># ツール定義：型ヒントとドキュメント文字列がそのままMCPの定義になります</span></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">calculate_add</span>(<span class="params">a: <span class="built_in">float</span>, b: <span class="built_in">float</span></span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;2つの数値を足し合わせます&quot;&quot;&quot;</span></span><br><span class="line">    result = a + b</span><br><span class="line">    <span class="keyword">return</span> <span class="string">f&quot;計算結果: <span class="subst">&#123;a&#125;</span> + <span class="subst">&#123;b&#125;</span> = <span class="subst">&#123;result&#125;</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># @mcp.tool()でいくつでもツールを追加可能</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># FastMCPが内部で生成したFastAPIアプリを抽出</span></span><br><span class="line">app = mcp.http_app()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    <span class="comment"># Lambda Web Adapterが待機する8080ポートで起動</span></span><br><span class="line">    uvicorn.run(app, host=<span class="string">&quot;0.0.0.0&quot;</span>, port=<span class="number">8080</span>)</span><br></pre></td></tr></table></figure>

<figure class="highlight sh"><figcaption><span>run.sh</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="built_in">exec</span> python -m mcp_server</span><br></pre></td></tr></table></figure>

<p>run.shに実行権限を付与。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">chmod</span> +x run.sh</span><br></pre></td></tr></table></figure>

<figure class="highlight text"><figcaption><span>requirements.txt</span></figcaption><table><tr><td class="code"><pre><span class="line">fastmcp==2.14.5</span><br><span class="line">fastapi==0.128.1</span><br><span class="line">uvicorn==0.40.0</span><br></pre></td></tr></table></figure>

<ol start="3">
<li>AWSリソースの構築</li>
</ol>
<p>以下のSam templateを実行すれば本記事のAWSリソースが構築できます。<br>[]内は各々の環境に合わせて変更してください。</p>
<figure class="highlight yaml"><input type="checkbox" id="code-wrap-1dk5xdj-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1dk5xdj-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="attr">Transform:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">AWS::Serverless-2016-10-31</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">AWS::LanguageExtensions</span></span><br><span class="line"></span><br><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">VpcId:</span> &#123; <span class="attr">Type:</span> <span class="string">String</span>, <span class="attr">Default:</span> <span class="string">vpc-</span>[<span class="string">VPCID</span>] &#125;</span><br><span class="line">  <span class="attr">SubnetId1:</span> &#123; <span class="attr">Type:</span> <span class="string">String</span>, <span class="attr">Default:</span> <span class="string">subnet-</span>[<span class="string">サブネットID</span>] &#125;</span><br><span class="line">  <span class="attr">SubnetId2:</span> &#123; <span class="attr">Type:</span> <span class="string">String</span>, <span class="attr">Default:</span> <span class="string">subnet-</span>[<span class="string">サブネットID</span>] &#125;</span><br><span class="line"></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="comment"># --- セキュリティグループ ---</span></span><br><span class="line">  <span class="attr">InternalServiceSG:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">AWS::EC2::SecurityGroup</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">GroupDescription:</span> <span class="string">Allow</span> <span class="string">internal</span> <span class="string">traffic</span> <span class="string">for</span> <span class="string">MCP</span> <span class="string">Server</span></span><br><span class="line">      <span class="attr">VpcId:</span> <span class="type">!Ref</span> <span class="string">VpcId</span></span><br><span class="line">      <span class="attr">SecurityGroupIngress:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">IpProtocol:</span> <span class="string">tcp</span></span><br><span class="line">          <span class="attr">FromPort:</span> <span class="number">443</span></span><br><span class="line">          <span class="attr">ToPort:</span> <span class="number">443</span></span><br><span class="line">          <span class="attr">CidrIp:</span> [<span class="string">CiderIp</span>]</span><br><span class="line"></span><br><span class="line">  <span class="comment"># --- VPCエンドポイント (API Gateway用) ---</span></span><br><span class="line">  <span class="attr">MyVpce:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">AWS::EC2::VPCEndpoint</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">ServiceName:</span> <span class="type">!Sub</span> <span class="string">&quot;com.amazonaws.$&#123;AWS::Region&#125;.execute-api&quot;</span></span><br><span class="line">      <span class="attr">VpcEndpointType:</span> <span class="string">Interface</span></span><br><span class="line">      <span class="attr">VpcId:</span> <span class="type">!Ref</span> <span class="string">VpcId</span></span><br><span class="line">      <span class="attr">SubnetIds:</span> [ <span class="type">!Ref</span> <span class="string">SubnetId1</span>, <span class="type">!Ref</span> <span class="string">SubnetId2</span> ]</span><br><span class="line">      <span class="attr">SecurityGroupIds:</span> [ <span class="type">!Ref</span> <span class="string">InternalServiceSG</span> ]</span><br><span class="line">      <span class="attr">PrivateDnsEnabled:</span> <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># --- API Gateway (Private) ---</span></span><br><span class="line">  <span class="attr">MyApi:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">AWS::Serverless::Api</span></span><br><span class="line">    <span class="attr">DependsOn:</span> <span class="string">MyVpce</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Name:</span> <span class="string">mcp-api</span></span><br><span class="line">      <span class="attr">StageName:</span> <span class="string">prod</span></span><br><span class="line">      <span class="attr">EndpointConfiguration:</span></span><br><span class="line">        <span class="attr">Type:</span> <span class="string">PRIVATE</span></span><br><span class="line">        <span class="attr">VPCEndpointIds:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">MyVpce</span></span><br><span class="line">      <span class="attr">Auth:</span></span><br><span class="line">        <span class="attr">ResourcePolicy:</span></span><br><span class="line">          <span class="attr">CustomStatements:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">              <span class="attr">Principal:</span> <span class="string">&quot;*&quot;</span></span><br><span class="line">              <span class="attr">Action:</span> <span class="string">&quot;execute-api:Invoke&quot;</span></span><br><span class="line">              <span class="attr">Resource:</span> <span class="string">&quot;execute-api:/*/*/*&quot;</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Deny</span></span><br><span class="line">              <span class="attr">Principal:</span> <span class="string">&quot;*&quot;</span></span><br><span class="line">              <span class="attr">Action:</span> <span class="string">&quot;execute-api:Invoke&quot;</span></span><br><span class="line">              <span class="attr">Resource:</span> <span class="string">&quot;execute-api:/*/*/*&quot;</span></span><br><span class="line">              <span class="attr">Condition:</span></span><br><span class="line">                <span class="attr">StringNotEquals:</span></span><br><span class="line">                  <span class="attr">&quot;aws:SourceVpce&quot;:</span> <span class="type">!Ref</span> <span class="string">MyVpce</span></span><br><span class="line">      <span class="attr">DefinitionBody:</span></span><br><span class="line">        <span class="attr">openapi:</span> <span class="string">&quot;3.0.1&quot;</span></span><br><span class="line">        <span class="attr">paths:</span></span><br><span class="line">          <span class="string">/&#123;proxy+&#125;:</span></span><br><span class="line">            <span class="attr">x-amazon-apigateway-any-method:</span></span><br><span class="line">              <span class="attr">x-amazon-apigateway-integration:</span></span><br><span class="line">                <span class="attr">httpMethod:</span> <span class="string">POST</span></span><br><span class="line">                <span class="attr">type:</span> <span class="string">aws_proxy</span></span><br><span class="line">                <span class="attr">uri:</span> <span class="type">!Sub</span> <span class="string">&quot;arn:aws:apigateway:$&#123;AWS::Region&#125;:lambda:path/2015-03-31/functions/$&#123;Function.Arn&#125;/invocations&quot;</span></span><br><span class="line">              <span class="attr">responses:</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment"># --- Lambda関数(VPC内) ---</span></span><br><span class="line">  <span class="attr">Function:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">AWS::Serverless::Function</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Architectures:</span> [<span class="string">arm64</span>]</span><br><span class="line">      <span class="attr">Runtime:</span> <span class="string">python3.11</span></span><br><span class="line">      <span class="attr">Timeout:</span> <span class="number">30</span></span><br><span class="line">      <span class="attr">CodeUri:</span> <span class="string">.</span></span><br><span class="line">      <span class="attr">Handler:</span> <span class="string">run.sh</span></span><br><span class="line">      <span class="attr">Layers:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="type">!Sub</span> <span class="string">arn:aws:lambda:$&#123;AWS::Region&#125;:753240598075:layer:LambdaAdapterLayerArm64:18</span></span><br><span class="line">      <span class="attr">VpcConfig:</span></span><br><span class="line">        <span class="attr">SecurityGroupIds:</span> [ <span class="type">!Ref</span> <span class="string">InternalServiceSG</span> ]</span><br><span class="line">        <span class="attr">SubnetIds:</span> [ <span class="type">!Ref</span> <span class="string">SubnetId1</span>, <span class="type">!Ref</span> <span class="string">SubnetId2</span> ]</span><br><span class="line">      <span class="attr">Environment:</span></span><br><span class="line">        <span class="attr">Variables:</span></span><br><span class="line">          <span class="attr">AWS_LAMBDA_EXEC_WRAPPER:</span> <span class="string">/opt/bootstrap</span></span><br><span class="line">          <span class="attr">PORT:</span> <span class="number">8080</span></span><br><span class="line">      <span class="attr">Events:</span></span><br><span class="line">        <span class="attr">ProxyApiRoot:</span></span><br><span class="line">          <span class="attr">Type:</span> <span class="string">Api</span></span><br><span class="line">          <span class="attr">Properties:</span></span><br><span class="line">            <span class="attr">RestApiId:</span> <span class="type">!Ref</span> <span class="string">MyApi</span></span><br><span class="line">            <span class="attr">Path:</span> <span class="string">/&#123;proxy+&#125;</span></span><br><span class="line">            <span class="attr">Method:</span> <span class="string">ANY</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># --- API GatewayからLambdaを呼ぶ許可 ---</span></span><br><span class="line">  <span class="attr">LambdaInvokePermission:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">AWS::Lambda::Permission</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Action:</span> <span class="string">lambda:InvokeFunction</span></span><br><span class="line">      <span class="attr">FunctionName:</span> <span class="type">!Ref</span> <span class="string">Function</span></span><br><span class="line">      <span class="attr">Principal:</span> <span class="string">apigateway.amazonaws.com</span></span><br><span class="line">      <span class="attr">SourceArn:</span> <span class="type">!Sub</span> <span class="string">&quot;arn:aws:execute-api:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:$&#123;MyApi&#125;/*&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">Outputs:</span></span><br><span class="line">  <span class="attr">FunctionArn:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&quot;Lambda Function ARN&quot;</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!GetAtt</span> <span class="string">Function.Arn</span></span><br><span class="line">  <span class="attr">VpceId:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&quot;VPC Endpoint ID&quot;</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">MyVpce</span></span><br><span class="line">  <span class="attr">ApiUrl:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&quot;API Gateway URL&quot;</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!Sub</span> <span class="string">&quot;https://$&#123;MyApi&#125;.execute-api.$&#123;AWS::Region&#125;.amazonaws.com/prod/&quot;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>本構成で重要な部分をピックアップして解説します。</p>
<p>API Gateway:</p>
<ul>
<li>エンドポイントタイプ：プライベート</li>
<li>VPCエンドポイントID：[エンドポイントID(vpce-xxxx)]</li>
<li>リソースポリシー：特定のVPCエンドポイント経由のアクセスのみ許可</li>
<li>統合タイプ：Lambda関数</li>
<li>Lambdaプロキシ統合：ON</li>
<li>Lambda関数：SAMで作った関数の名前(mcp-server-stack-Function-xxx)</li>
<li>デプロイの際のステージ名：prod</li>
</ul>
<p>Lambda:</p>
<ul>
<li>LambdaをVPCのプライベートサブネットを指定することで、インターネットからは直接見えない状態に設定</li>
</ul>
<p>VPCエンドポイント:</p>
<ul>
<li>VPCEndpoint(Interface型)の採用によりAPI Gatewayをインターネットに公開せず、VPC内部のプライベートIPだけで叩けるようにします。</li>
</ul>
<h3 id="Difyからの接続">Difyからの接続</h3><p>Difyのツール＞MCPからツールを追加します。</p>
<ul>
<li>サーバー名：https:&#x2F;&#x2F;[自分のVPCEのDNS名(上から2つ目)]&#x2F;prod&#x2F;mcp</li>
<li>ヘッダー名：HOST</li>
<li>ヘッダーの値：[自分のAPIのID].execute-api.ap-northeast-1.amazonaws.com</li>
</ul>
<img src="/images/2026/20260324a/image_5.png" alt="image.png" width="546" height="876" loading="lazy">

<h2 id="利用結果">利用結果</h2><p>Difyのツール設定から外部MCPツールを登録し、実際に計算を依頼した結果です。</p>
<img src="/images/2026/20260324a/image_6.png" alt="image.png" width="844" height="724" loading="lazy">

<p>VPCエンドポイント経由の閉域網通信でありながら、Difyのエージェント機能によってmcpツールが呼び出されていることが確認できました。</p>
<h2 id="まとめ">まとめ</h2><ul>
<li>したことまとめ<ul>
<li>AWS Lambda + API Gateway (Private) によるセキュアでサーバレスなMCPサーバーの構築</li>
<li>Dify との連携による実用的なAIエージェント環境の構築</li>
</ul>
</li>
<li>次やりたいこと<ul>
<li>今回は数値計算のシンプルなツールでしたが、次はGoogleカレンダーAPIとの連携によるスケジュール調整の自動化に挑戦し、最終的には秘書のようなAIを作りたいと考えています</li>
</ul>
</li>
</ul>
]]></content>
    <summary type="html">Strategic AI Group/MLOpsチームでアルバイトをしている木村です。アルバイトでは最新技術の調査を担当し、社内や案件にて活用することを想定したシステム導入の検証を実施しています。AWS上のLambdaで自作したプライベートなMCPサーバーをDify上で使用する手順について記事にします。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="APIGateway" scheme="https://future-architect.github.io/tags/APIGateway/"/>
    <category term="Dify" scheme="https://future-architect.github.io/tags/Dify/"/>
    <category term="Lambda" scheme="https://future-architect.github.io/tags/Lambda/"/>
    <category term="MCP" scheme="https://future-architect.github.io/tags/MCP/"/>
  </entry>
  <entry>
    <title>記録のための日記から、対話で育てる日記へ ーClaude × Obsidian × MCPで作る思考のジャーナリングー</title>
    <link href="https://future-architect.github.io/articles/20260317a/"/>
    <id>https://future-architect.github.io/articles/20260317a/</id>
    <published>2026-03-16T15:00:00.000Z</published>
    <updated>2026-03-16T15:00:00.000Z</updated>
    <author><name>山下量之</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260317a/top.jpg" alt="" height="502" width="900">

<h2 id="はじめに">はじめに</h2><p>最近、『自省録』を自己啓発として初めて読んでいる。</p>
<p>マルクス・アウレリウスが、誰に読ませるつもりもなく、自分に向かって書き続けた内省のメモ。読んでいると、「日記」って本来こういうものだったんじゃないか、と思うようになった。整っていなくていいし、結論がなくてもいい。ただ、自分の思考と正面から向き合うためのものだ。</p>
<p>そんなタイミングで、2026年の1月ごろから、やたらと「ボイスで日記をつける」系のコンテンツが目に入るようになった。</p>
<p>多くは Google NotebookLM を使った方法で、音声を録音して放り込むと、AIがそれを整理し、要点を抜き出し、ポッドキャスト風にまとめてくれる、というものだ。音声日記を素材にして「今日の出来事」「気づき」「感情の変化」を自動で構造化し、あとから聞き返せるコンテンツにする——そんな使い方を紹介する記事やYouTube動画も多い。</p>
<p>たしかに見栄えはいい。「今日の自分の話がコンテンツになっている」感じがして、続けやすそうだとも思う。</p>
<p>でも正直、私が欲しかったのは、きれいにまとめられた記録ではない。話しながら、考えながら、「あ、そういうことか」と腑に落ちていく、その過程そのものだ。</p>
<p>この記事は、そんな違和感を出発点にして、「対話によって思考を深め、そのまま資産として残す日記」を自分なりに試行錯誤した記録だ。</p>
<p>NotebookLM型のジャーナリングに感じた物足りなさから、Claude × Obsidian × MCP を使って実際に組んだ仕組み、そして使ってみて見えてきたメリットと限界までを、思想と具体実装の両面からまとめている。</p>
<h2 id="こんなこと、ないだろうか">こんなこと、ないだろうか</h2><h3 id="「もやもや」が言葉にならないまま終わる">「もやもや」が言葉にならないまま終わる</h3><p>特に大きな出来事があったわけじゃないのに、一日が終わる頃に、なんとなく気分が重い。</p>
<p>「まあ、疲れてるだけかな」と思って、音声で少し話してみる。</p>
<p>でも、それをNotebookLMに放り込むと、「今日は疲労感があった一日でした」ときれいにまとめられる。</p>
<p>いや、そうじゃない。</p>
<h3 id="日記が「記録の墓場」になっていく">日記が「記録の墓場」になっていく</h3><p>日記は付けている。でも振り返ると、去年の自分と今年の自分がまったく繋がっていない。</p>
<p>1月に書いた「XXX」と、6月に書いた「YYY」が、実は同じ根っこを持っているかもしれない。でも日記の中では、それぞれ孤立したまま眠っている。</p>
<p>過去の自分が、今の自分の思考の役に立ってほしい。書き続けるほど、思考の解像度が上がってほしい。でも、ただ書くだけではそうならなかった。</p>
<h2 id="だから自分で作った">だから自分で作った</h2><p>この2つの不満を解消するために、以下の仕組みを作った。</p>
<figure class="highlight txt"><table><tr><td class="code"><pre><span class="line">音声入力（PC）</span><br><span class="line">  └ 思考を止めずに吐き出す</span><br><span class="line">  ↓</span><br><span class="line">文字起こし</span><br><span class="line">  └ 自分の言葉を外在化する</span><br><span class="line">  ↓</span><br><span class="line">AIプロジェクト（壁打ち・深掘り）</span><br><span class="line">  └ 思考を揺さぶる他者役</span><br><span class="line">  ↓</span><br><span class="line">「以上です」</span><br><span class="line">  └ 思考セッションの終点</span><br><span class="line">  ↓</span><br><span class="line">Obsidianに自動保存</span><br><span class="line">  └ 思考を資産に変換する</span><br></pre></td></tr></table></figure>

<p>使っているのは Claude（プロジェクト機能）、Obsidian、そして MCP の3つだ。</p>
<h2 id="NotebookLMじゃダメだった理由">NotebookLMじゃダメだった理由</h2><p>NotebookLMが悪いわけではない。</p>
<p>ただ、ジャーナリングにおいて「要約がゴール」になる瞬間、思考はそこで止まってしまう。</p>
<div class="scroll"><table>
<thead>
<tr>
<th></th>
<th>NotebookLM</th>
<th>このシステム(AI+Obsidian)</th>
</tr>
</thead>
<tbody><tr>
<td>主な用途</td>
<td>インプット情報の消化・要約</td>
<td>自分の思考の深掘り・資産化</td>
</tr>
<tr>
<td>AIの役割</td>
<td>要約者・解説者</td>
<td>対話パートナー</td>
</tr>
<tr>
<td>出力先</td>
<td>ポッドキャスト・要約テキスト</td>
<td>Obsidian（自分の知識ベース）</td>
</tr>
<tr>
<td>蓄積のしかた</td>
<td>都度生成</td>
<td>リンクでつながるノート群</td>
</tr>
</tbody></table></div>
<p>NotebookLM は、読んだ本や講義、記事など、外部から入ってきた情報を噛み砕き、「理解しやすい形」にするのがとても得意だ。</p>
<p>一方で、ジャーナリングの素材は「まだ言葉になっていない自分の内側」だ。そこには整理よりも、問い返してくれる存在が必要だった。</p>
<p>そこで、AIの出番だ。</p>
<h2 id="仕組みの核心：2フェーズで動くClaudeプロジェクト">仕組みの核心：2フェーズで動くClaudeプロジェクト</h2><h3 id="フェーズ1：壁打ちモード">フェーズ1：壁打ちモード</h3><p>音声入力で文字起こしされたテキストを貼ると、Claudeはまず<strong>記録を保存せず対話を始める</strong>。<br>※プロジェクトの指示文(事前プロンプト)はブログ末尾参照。</p>
<p>断定はしない。評価もしない。「〜ということでしょうか？」のトーンで、思考の続きを引き出す問いを1〜2個だけ返してくる。</p>
<p>実際の会話を見てもらった方が早い。</p>
<p><strong>私（文字起こし）：</strong></p>
<blockquote>
<p>今日の1on1、なんか微妙だったな。上司にプロジェクトの進捗を報告したんだけど、なんかすれ違ってる感じがした。悪い空気じゃなかったけど、話が噛み合ってなかったというか。まあ忙しそうだったし仕方ないのかな。</p>
</blockquote>
<p><strong>Claude：</strong></p>
<blockquote>
<p>「話が噛み合ってなかった」という感覚、もう少し聞かせてもらえますか。ご自身が伝えたかったことと、上司が反応したこととの間に、どんなズレがあったのでしょう？</p>
</blockquote>
<p><strong>私：</strong></p>
<blockquote>
<p>うーん、私は課題感を共有したかったんだけど、向こうはステータスの確認だけしたかった感じ。私は「どうすべきか一緒に考えたかった」んだと思う。</p>
</blockquote>
<p><strong>Claude：</strong></p>
<blockquote>
<p>なるほど。「一緒に考えたかった」というのが印象的でした。今の仕事全体を通して、そういう「考える場」が不足していると感じることはありますか？</p>
</blockquote>
<p><strong>私：</strong></p>
<blockquote>
<p>……あ、そうかも。最近ずっとそれが足りてないんだと思う。報告する場はあるけど、考える場がない。</p>
</blockquote>
<p><strong>（「以上です」と入力）</strong></p>
<p>3往復で「1on1がもやもやした」が「考える場が足りていない」という本質に近い深掘りができた。</p>
<p>「考える場がない」という言葉、自分で言うまで自分でも気づいてなかった。これが要約では絶対起きないことで、私がずっと欲しかったものだった。</p>
<h4 id="フェーズ2：自動保存モード">フェーズ2：自動保存モード</h4><p>「以上です」と入力すると、それまでの会話全体をもとに以下のフォーマットで整理されて Obsidian の指定フォルダとファイル名で自動保存される。</p>
<figure class="highlight markdown"><input type="checkbox" id="code-wrap-e21m0g-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-e21m0g-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">---</span><br><span class="line">date: 2026-02-22</span><br><span class="line"><span class="section">tags: [仕事, 人間関係, 自己理解]</span></span><br><span class="line"><span class="section">---</span></span><br><span class="line"></span><br><span class="line"><span class="section"># 2026-02-22</span></span><br><span class="line"></span><br><span class="line"><span class="section">## 要約</span></span><br><span class="line">1on1でのすれ違いを起点に、仕事における「考える場」の不足という</span><br><span class="line">本質的な課題が浮かび上がった。ステータス報告の場は存在するが、</span><br><span class="line">問いを立てて一緒に考える機会が構造的に欠けている可能性がある。</span><br><span class="line"></span><br><span class="line"><span class="section">## キーテーマ</span></span><br><span class="line">[[1on1]], [[対話の質]], [[仕事環境]]</span><br><span class="line"></span><br><span class="line"><span class="section">## 深掘りポイント</span></span><br><span class="line"><span class="bullet">-</span> 「考える場」が足りない状態は、いつ頃から続いているか</span><br><span class="line"><span class="bullet">-</span> 上司との関係でそれを求めることへの遠慮はあるか</span><br><span class="line"></span><br><span class="line"><span class="section">## 印象的なフレーズ</span></span><br><span class="line"><span class="quote">&gt; 報告する場はあるけど、考える場がない</span></span><br><span class="line"></span><br><span class="line">感情の言語化ができた瞬間だった。</span><br><span class="line"></span><br><span class="line"><span class="section">## 関連ノート候補</span></span><br><span class="line"><span class="bullet">-</span> [[仕事への不満 2025-06]]</span><br><span class="line"><span class="bullet">-</span> [[理想の働き方]]</span><br><span class="line"></span><br><span class="line"><span class="section">## 元の記録</span></span><br><span class="line">今日の1on1、なんか微妙だった。（以下略）</span><br></pre></td></tr></table></figure>

<p>これが蓄積されていくと、<code>[[仕事への不満]]</code> <code>[[考える場]]</code> というリンクでノート同士が繋がり始める。去年書いたノートと今日書いたノートが、突然ひとつの文脈でつながる瞬間がある。それが地味にすごく気持ちいい。</p>
<h2 id="セットアップの話">セットアップの話</h2><h3 id="まずChrome拡張から入った（10分で動く）">まずChrome拡張から入った（10分で動く）</h3><p>最初は一番手軽な方法から試した。</p>
<ol>
<li>Obsidian に「<strong>Local REST API</strong>」プラグインをインストール・有効化 → APIキーをメモ</li>
<li>Chrome 拡張「<strong>Obsidian AI Exporter</strong>」をインストール</li>
<li>拡張の設定に APIキーと保存先フォルダを入力</li>
</ol>
<p>これだけで、claude.ai 上でボタン1つ押すと Obsidian の指定フォルダに <code>.md</code> ファイルが飛ぶ。</p>
<p>制約は「自分がボタンを押す必要がある」こと。それでも手動コピペがなくなっただけで体験はだいぶ変わった。</p>
<p>ここから先は「Claude Desktop + MCP」を使った完全自動化の話。<br>「まず体験してみたい」人は、Chrome拡張のところまで読めば十分。<br>上記Chrome拡張機能はチャット内の全会話を書き出すため、最後のまとめ内容だけ　Obsidian に書き出したい方は次の完全自動化を参考にしてください。</p>
<h3 id="本命：Claude-Desktop-MCPで完全自動化">本命：Claude Desktop + MCPで完全自動化</h3><p>Chrome 拡張の次に、<strong>「以上です」を送ったら Claude が自分でファイルを保存する</strong>ところまで自動化したくなった。それを実現するのが MCP（Model Context Protocol）だ。</p>
<figure class="highlight txt"><table><tr><td class="code"><pre><span class="line">Claude Desktop</span><br><span class="line">  ↓ MCP接続</span><br><span class="line">Obsidian（Local REST API プラグイン）</span><br><span class="line">  ↓</span><br><span class="line">Vault 内に直接ファイルを作成・読み書き</span><br></pre></td></tr></table></figure>

<h4 id="セットアップ手順（Mac・Claude-Desktop-インストール済み想定）">セットアップ手順（Mac・Claude Desktop インストール済み想定）</h4><p><strong>Step 1：Obsidian に Local REST API プラグインを入れる</strong></p>
<ul>
<li>設定 → コミュニティプラグイン → <code>Local REST API</code> をインストール・有効化</li>
<li>APIキーをメモ（設定画面に表示される）</li>
</ul>
<p><strong>Step 2：Node.js の確認</strong></p>
<p>Macの場合</p>
<figure class="highlight bash"><input type="checkbox" id="code-wrap-e21m0g-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-e21m0g-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">node --version  <span class="comment"># 入っていない場合は brew install node</span></span><br></pre></td></tr></table></figure>

<p><strong>Step 3：Claude Desktop の設定ファイルを編集</strong></p>
<p>Macの場合</p>
<figure class="highlight bash"><input type="checkbox" id="code-wrap-e21m0g-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-e21m0g-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">open ~/Library/Application\ Support/Claude/claude_desktop_config.json</span><br></pre></td></tr></table></figure>

<p>以下を追記：</p>
<figure class="highlight json"><input type="checkbox" id="code-wrap-e21m0g-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-e21m0g-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;mcpServers&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;obsidian&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npx&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;-y&quot;</span><span class="punctuation">,</span> <span class="string">&quot;obsidian-local-rest-api-mcp&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;env&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;OBSIDIAN_API_URL&quot;</span><span class="punctuation">:</span> <span class="string">&quot;http://localhost:27123&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;OBSIDIAN_API_KEY&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ここにAPIキーを貼る&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<p><strong>Step 4：Claude Desktop を再起動して確認</strong></p>
<p>新しいチャットで「私のObsidianのVaultにあるファイル一覧を見せて」と入力して、ファイル一覧が返ってきたら成功。</p>
<p>ちなみにこのセットアップ自体は Claude Code に任せた。「Mac、Claude Desktop 済み、Vaultはローカル」という情報を渡したら設定ファイルの確認から書き込みまで全部やってくれた。自分でやる必要すらなかった。</p>
<h2 id="実際に使ってみて">実際に使ってみて</h2><p>MCP で完全自動化してからは、「以上です」と打って Obsidian を開くともうファイルができている、という体験になった。</p>
<p>日記を書いている感覚というより、思考セッションをしている感覚に近い。終わった後「書いた」じゃなくて「考えた」という感じがする。これは地味だけどけっこう大事な差だと思う。</p>
<p>あと、Claude が実際に Vault を検索して「このノートと関係しそう」と提案してくれるようになってから、関連ノートの精度が上がった。ノートが増えるほど提案の質が高まっていくので、続けるほど便利になる。</p>
<h2 id="正直な課題">正直な課題</h2><h3 id="音声会話モードにすると英語になる">音声会話モードにすると英語になる</h3><p>Claude の音声会話モード（Audio）はシステムプロンプトを引き継がず、英語で応答する仕様になっている。プロジェクト設定も無効になる。</p>
<p>これは最初ちょっとがっかりした。「声で喋りながら深掘り対話できたら最高じゃん」と思ってたから。でも結局、音声入力（スマホのキーボード音声入力など）で文字起こしをしてからプロジェクトに貼り付けるフローに徹することにした。「声で話す」と「音声会話モード」は別物だと割り切ってしまえば問題ない。</p>
<h3 id="スマホからだと自動保存できない">スマホからだと自動保存できない</h3><p>MCPはPCのローカルで動くサーバーなので、スマホの claude.ai からは届かない。外で思いついてスマホで対話しても、「以上です」の後の自動保存が機能しない。</p>
<p>対処法は3つある。</p>
<p><strong>A. PCで完結させる</strong>——最初からPCのClaude Desktopで使う。自動保存も含めて全部完結するので一番シンプル。</p>
<p><strong>B. スマホ対話 → PCで保存の2段階フロー</strong>——スマホで対話まで完了させたままにしておいて、PCを開いたタイミングでClaude Desktopに「さっきの会話をObsidianに保存して」と一言送る。</p>
<p><strong>C. スマホで手動保存</strong>——出力されたMarkdownをコピーして、ObsidianモバイルアプリにそのままペーストしてDailyノートを作る。手間はあるけど確実。</p>
<p>外出先ではCかB、基本はA、というのが今の自分の落とし所だ。</p>
<h2 id="おわりに">おわりに</h2><p>NotebookLM のジャーナリング活用を否定したいわけじゃない。「記録を資産にする」というアプローチとして、あれはあれで優れていると思う。</p>
<p>ただぼくは「<strong>対話で思考を深めて、それを知識ベースに育てていく</strong>」ことがしたかった。記録じゃなくて、思考の筋肉を鍛えたかった。</p>
<p>この構成は今のところそれに一番フィットしている。Obsidian のノート数が増えるにつれて、リンクのネットワークが育っていく感覚がある。去年の自分が今日の自分に「それ、前にも考えてたよ」と言いかけてくるような感じ、とでも言えばいいか。</p>
<p>日記がようやく、未来の自分の役に立ち始めた気がしている。</p>
<h2 id="付録：完全なシステムプロンプト">付録：完全なシステムプロンプト</h2><p>以下をそのまま Claude のプロジェクトのシステムプロンプトにコピーして使える。</p>
<figure class="highlight markdown"><input type="checkbox" id="code-wrap-e21m0g-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-e21m0g-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">あなたはユーザーの音声日記の思考パートナーであり、Obsidian日記の整理アシスタントです。</span><br><span class="line">以下の2フェーズで動作してください。</span><br><span class="line"></span><br><span class="line">---</span><br><span class="line"></span><br><span class="line"><span class="section">## フェーズ1：壁打ち・深掘り（対話モード）</span></span><br><span class="line"></span><br><span class="line">文字起こしテキストが入力されたら、Obsidianへの出力はまだ行いません。</span><br><span class="line">まず思考パートナーとして対話を始めてください。</span><br><span class="line"></span><br><span class="line"><span class="section">### 振る舞い方</span></span><br><span class="line"><span class="bullet">-</span> ユーザーの話を受け止め、興味深いと感じた点・掘り下げられそうな点を1〜2個ピックアップして問いかける</span><br><span class="line"><span class="bullet">-</span> 一度に多くを聞きすぎない。問いは1〜2個に絞る</span><br><span class="line"><span class="bullet">-</span> 断定・評価・説教はしない。「〜ということでしょうか？」「〜が気になりました」のようなトーンで</span><br><span class="line"><span class="bullet">-</span> ユーザーが答えたら、その返答に対してもさらに深掘りを続ける</span><br><span class="line"><span class="bullet">-</span> 対話は何往復してもよい</span><br><span class="line"></span><br><span class="line"><span class="section">### 対話の終了</span></span><br><span class="line">ユーザーが「以上です」と入力したタイミングで、フェーズ2に移行する。</span><br><span class="line"></span><br><span class="line">---</span><br><span class="line"></span><br><span class="line"><span class="section">## フェーズ2：Obsidian出力＆自動保存モード</span></span><br><span class="line"></span><br><span class="line">「以上です」を受け取ったら、それまでの会話全体（文字起こし＋対話のやり取り）をもとに</span><br><span class="line">以下のフォーマットで出力し、その後すぐに Obsidian へ自動保存する。</span><br><span class="line"></span><br><span class="line"><span class="section">### 出力フォーマット</span></span><br><span class="line"></span><br><span class="line">---</span><br><span class="line">date: YYYY-MM-DD</span><br><span class="line"><span class="section">tags: [タグ1, タグ2, タグ3]</span></span><br><span class="line"><span class="section">---</span></span><br><span class="line"></span><br><span class="line"><span class="section"># YYYY-MM-DD</span></span><br><span class="line"></span><br><span class="line"><span class="section">## 要約</span></span><br><span class="line">（3〜5文で核心をまとめる。対話で深まった内容も反映する）</span><br><span class="line"></span><br><span class="line"><span class="section">## キーテーマ</span></span><br><span class="line">[[テーマノート名1]], [[テーマノート名2]], [[テーマノート名3]]</span><br><span class="line"></span><br><span class="line"><span class="section">## 深掘りポイント</span></span><br><span class="line"><span class="bullet">-</span> （対話を通じて浮かび上がった未解決の問いや気づきを2〜3個）</span><br><span class="line"></span><br><span class="line"><span class="section">## 印象的なフレーズ</span></span><br><span class="line"><span class="quote">&gt; （ユーザー自身の言葉から引用）</span></span><br><span class="line"></span><br><span class="line">（一言コメント）</span><br><span class="line"></span><br><span class="line"><span class="section">## 関連ノート候補</span></span><br><span class="line"><span class="bullet">-</span> [[関連しそうなノートタイトル1]]</span><br><span class="line"><span class="bullet">-</span> [[関連しそうなノートタイトル2]]</span><br><span class="line"></span><br><span class="line"><span class="section">## 元の記録</span></span><br><span class="line">（文字起こしを最小限に整形したもの。「えー」「あの」「まあ」などのフィラー、</span><br><span class="line">明らかな言い直し、過剰な繰り返しを除去する。話し言葉のテンポや一人称の感覚は残し、</span><br><span class="line">書き言葉に変換しすぎない。ユーザーが後から読んで「自分が話した内容だ」と感じられる程度にとどめる）</span><br><span class="line"></span><br><span class="line"><span class="section">### 自動保存の手順</span></span><br><span class="line"></span><br><span class="line">フォーマットの出力が完了したら、以下の手順で Obsidian へ保存する：</span><br><span class="line"></span><br><span class="line"><span class="bullet">1.</span> obsidian:list<span class="emphasis">_directory で `daily` フォルダの存在を確認する</span></span><br><span class="line"><span class="emphasis">2. フォルダが存在しない場合は、obsidian:write_</span>file でダミーファイルを作成してフォルダを生成する</span><br><span class="line"><span class="bullet">3.</span> 今日の日付を YYYY-MM-DD 形式で取得し、daily/YYYY-MM-DD.md として obsidian:write<span class="emphasis">_file で保存する</span></span><br><span class="line"><span class="emphasis">4. 保存完了後、ユーザーに「daily/YYYY-MM-DD.md として Obsidian に保存しました」と通知する</span></span><br><span class="line"><span class="emphasis"></span></span><br><span class="line"><span class="emphasis">---</span></span><br><span class="line"><span class="emphasis"></span></span><br><span class="line"><span class="emphasis">## Obsidianリンク設計の方針</span></span><br><span class="line"><span class="emphasis"></span></span><br><span class="line"><span class="emphasis">- タグ・ウィキリンクのラベルは日本語で統一</span></span><br><span class="line"><span class="emphasis">- タグは #習慣化 #自己理解 #仕事 #人間関係 #旅行 などの粒度で統一し、新しいタグは既存タグとの重複・類似を避ける</span></span><br><span class="line"><span class="emphasis">- キーテーマ・関連ノート候補は [[ノートタイトル]] 形式で出力する（存在しないノートでもOK）</span></span><br><span class="line"><span class="emphasis">- テーマが蓄積されると自然にMOC（Map of Content）になっていく</span></span><br><span class="line"><span class="emphasis"></span></span><br><span class="line"><span class="emphasis">---</span></span><br><span class="line"><span class="emphasis"></span></span><br><span class="line"><span class="emphasis">## 週次レビュー</span></span><br><span class="line"><span class="emphasis"></span></span><br><span class="line"><span class="emphasis">「今週の日記をレビューして」と入力された場合、obsidian:list_</span>directory で daily フォルダの</span><br><span class="line">当該週のファイルを取得・参照したうえで、以下のフォーマットで出力・保存する：</span><br><span class="line"></span><br><span class="line">---</span><br><span class="line">date: YYYY-WXX</span><br><span class="line"><span class="section">tags: [週次レビュー]</span></span><br><span class="line"><span class="section">---</span></span><br><span class="line"></span><br><span class="line"><span class="section"># 週次レビュー YYYY-WXX</span></span><br><span class="line"></span><br><span class="line"><span class="section">## 今週の頻出テーマ</span></span><br><span class="line">[[テーマ1]], [[テーマ2]]</span><br><span class="line"></span><br><span class="line"><span class="section">## 変化・気づきの兆し</span></span><br><span class="line">（繰り返し出てきた問い、先週からの変化）</span><br><span class="line"></span><br><span class="line"><span class="section">## 来週への問い</span></span><br><span class="line">（1〜2個）</span><br><span class="line"></span><br><span class="line"><span class="section">## 今週のエントリー</span></span><br><span class="line"><span class="bullet">-</span> [[YYYY-MM-DD]]</span><br><span class="line"><span class="bullet">-</span> [[YYYY-MM-DD]]</span><br><span class="line"></span><br><span class="line">保存先は daily/YYYY-WXX.md。</span><br><span class="line"></span><br><span class="line">---</span><br><span class="line"></span><br><span class="line"><span class="section">## 全体的な注意事項</span></span><br><span class="line"><span class="bullet">-</span> ユーザーの言葉を勝手に断定・解釈しすぎない</span><br><span class="line"><span class="bullet">-</span> ネガティブな評価や説教はしない</span><br><span class="line"><span class="bullet">-</span> 話し言葉のニュアンスや感情のトーンを尊重する</span><br></pre></td></tr></table></figure>
]]></content>
    <summary type="html">最近、『自省録』を自己啓発として初めて読んでいる。マルクス・アウレリウスが、誰に読ませるつもりもなく、自分に向かって書き続けた内省のメモ。読んでいると、「日記」って本来こういうものだったんじゃないか、と思うようになった。整っていなくていいし、結論がなくてもいい。ただ、自分の思考と正面から向き合うためのものだ。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="Claude" scheme="https://future-architect.github.io/tags/Claude/"/>
    <category term="MCP" scheme="https://future-architect.github.io/tags/MCP/"/>
  </entry>
  <entry>
    <title>API費用8ドル。「ホリエモンAI選挙」の分析フローをDifyで一部再現してみた ‐ マルチAIエージェント実装ガイド</title>
    <link href="https://future-architect.github.io/articles/20260212a/"/>
    <id>https://future-architect.github.io/articles/20260212a/</id>
    <published>2026-02-11T15:00:00.000Z</published>
    <updated>2026-02-11T15:00:00.000Z</updated>
    <author><name>棚井龍之介</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260212a/スクリーンショット_2026-02-09_5.08.43.png" alt="" width="1200" height="812">

<h2 id="はじめに">はじめに</h2><p>最近、「マルチAIエージェント」という言葉をよく耳にするようになりました。</p>
<p>単一のLLMですべてを解決するのではなく、検索が得意なAI、分析が得意なAIなどを適材適所で組み合わせる手法です。その好例として話題になったのが、2026年の第51回衆議院議員選挙に合わせて公開された「ホリエモンAI選挙」です。</p>
<p>このAI選挙予測サイトを見て、「面白そう」と思ったエンジニアや「ウチのビジネスでも活かせそう」と直感したビジネスパーソンは少なくないはずです。私が凄いなと思った点は、<strong>「予測の仕組み(AIエージェントの組み合わせ、プロンプト)」が公開されていること</strong>です。つまり、各サービスの契約さえあれば、誰でも同じロジックを手動で試すことができます。具体的には、以下のような役割分担です。</p>
<ul>
<li><strong>Perplexity</strong> で最新の世論調査、報道データを検索させて</li>
<li><strong>Grok</strong> でX上の投稿を検索してSNSトレンドを分析させて</li>
<li><strong>Gemini</strong> でそれらの情報を統合・構造化してもらう</li>
</ul>
<p>このフローは手動で実行可能ですが、毎回3つのサービスを開いてプロンプトをコピー&amp;ペーストし、結果を貼り合わせる単調作業を繰り返すのは「プログラマーの三大美徳」（怠惰・短気・傲慢）に反します。</p>
<p>そこで、この記事では、<strong>Dify Cloud と 3つのAI Agent API(Perplexity &#x2F; Grok &#x2F; Gemini)を組み合わせて、ボタン1つで選挙情勢分析レポートを自動生成するワークフローを構築する方法</strong> を解説します。</p>
<p>「そんな複雑なシステム、個人で検証したら高いんじゃないの？」と思われがちですが、<strong>今回の検証(分析フローの一部再現)にかかったAPI費用は、たったの8ドル(約1,200円)</strong> です。本記事では、用意するソースコードはPythonが数行程度で、Dify Cloud上で動かせる(ローカル環境不要)手順に加え、「各サービスのAPIキーをどこで取るのか」「Difyのどの画面で何を設定するのか」を解説します。</p>
<div class="note-container note-alert"><span class="note-icon"></span><div>

<p>本記事は「ホリエモンAI選挙」の仕組みを参考にDifyでワークフローを構築する技術解説記事であり、同サービスを完全に再現するものではありません。以下の点にご留意ください。</p>
<ul>
<li>「ホリエモンAI選挙」では「全体情勢分析」「都道府県別分析」「比例ブロック分析」の3種類の分析が行われていますが、本記事では <strong>「全体情勢分析」のみ</strong> を対象としています</li>
<li>本家で投入されている事前データ(選挙区の区割り情報、立候補者情報など)の投入は行っていません</li>
<li>プロンプトの一部について、公開情報から推測して埋め合わせた箇所があります</li>
</ul>
</div></div>

<h2 id="完成イメージ">完成イメージ</h2><p>まず、最終的に何ができるのかを先にお見せします。</p>
<p>ワークフローの全体構成は以下の通りです。</p>
<pre class="mermaid" data-mermaid="adb1113f58d4704c67c72731dfe057462e57aa761e8bed846d66942c377551c8">graph LR
    %% ノード定義
    Start((スタート))
    DateGen[検索日作成<br>Code]
    Perplexity[Perplexity<br>Web検索]
    Grok[Grok<br>SNS分析]
    Gemini[Gemini<br>データ統合]
    TableGen[テーブル化<br>Code]
    Result(((集計結果<br>End)))
    Error(((エラー<br>End)))

    Start --> DateGen
    DateGen --> Perplexity
    DateGen --> Grok

    Perplexity --> Gemini
    Grok --> Gemini

    Gemini --> TableGen
    TableGen --> Result

    Perplexity -.->|Fail Branch| Error
    Grok -.->|Fail Branch| Error
    Gemini -.->|Fail Branch| Error
    TableGen -.->|Fail Branch| Error

    %% スタイル定義
    classDef default stroke:#333,stroke-width:2px;
    style Start fill:#155EEF,stroke:#155EEF,color:#fff
    style DateGen fill:#F04438,stroke:#F04438,color:#fff
    style Perplexity fill:#444CE7,stroke:#444CE7,color:#fff
    style Grok fill:#A020F0,stroke:#A020F0,color:#fff
    style Gemini fill:#155EEF,stroke:#155EEF,color:#fff
    style TableGen fill:#F04438,stroke:#F04438,color:#fff
    style Result fill:#333,stroke:#333,color:#fff
    style Error fill:#D92D20,stroke:#D92D20,color:#fff</pre>

<p>Dify上で「実行」ボタンを押すと、以下のフローが自動で処理されます。</p>
<ol>
<li><strong>Perplexity</strong>がWeb上の世論調査・ニュースを検索して分析</li>
<li><strong>Grok</strong>がX上の投稿を分析してSNS世論を分析</li>
<li>上記2つの結果を<strong>Gemini</strong>が統合、構造化されたJSONを出力</li>
<li>PythonコードがJSONをMarkdownテーブルに整形</li>
</ol>
<p>最終出力には、以下のように、内閣支持率、各政党それぞれの支持率・トレンド・分析コメント、データソースリストが含まれます。</p>
<p>(出力フォーマット以外の最終的な出力は黒塗りさせていただきました。皆さんも手元で試してみて、どんな結果が得られるのかを検証してみて下さい！)</p>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-09_1.53.47.png" alt="スクリーンショット_2026-02-09_1.53.47.png" width="1200" height="752" loading="lazy">

<h3 id="検証に要した費用">検証に要した費用</h3><p>今回のワークフローには、Dify・Perplexity・Grok・Gemini を利用しています。動作検証にあたり、初回費用枠や最低購入額等を利用することで、8ドル(1,200円 &#x3D; 150円×8ドル)となりました。(※為替レートは執筆時点の概算)</p>
<div class="scroll"><table>
<thead>
<tr>
<th>サービス名</th>
<th>検証に要した費用</th>
<th>備考</th>
</tr>
</thead>
<tbody><tr>
<td>Dify Cloud</td>
<td>$0</td>
<td>今回のワークフローは「無料枠」で実現可能</td>
</tr>
<tr>
<td>Perplexity</td>
<td>$3</td>
<td>APIクレジットの最低購入額が $3 のため</td>
</tr>
<tr>
<td>Grok(xAI)</td>
<td>$5</td>
<td>APIクレジットの最低購入額が $5 のため</td>
</tr>
<tr>
<td>Gemini(Google AI Studio)</td>
<td>$0</td>
<td>無料トライアル分の「91日間可能な $300 のウェルカムクレジット」を利用した</td>
</tr>
</tbody></table></div>
<hr>
<h2 id="事前準備1-Perplexity-API">事前準備1:Perplexity API</h2><h3 id="APIキーの取得手順">APIキーの取得手順</h3><ol>
<li>Perplexity にアクセスして、アカウントを作成します</li>
<li>ログイン後、左下のアイコン → 「API」を選択します</li>
<li>APIグループ名を入力して「保存」を選択します</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-09_2.14.00.png" alt="スクリーンショット_2026-02-09_2.14.00.png" width="1200" height="838" loading="lazy">

<ol start="4">
<li>「API請求」に移動して、支払い方法(クレジットカード)を登録します</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-09_2.15.41.png" alt="スクリーンショット_2026-02-09_2.15.41.png" width="1200" height="1028" loading="lazy">

<ol start="5">
<li>「クレジットをもっと購入」をクリックして、最低購入額の $3 を購入します</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-09_2.19.08.png" alt="スクリーンショット_2026-02-09_2.19.08.png" width="1200" height="542" loading="lazy">

<ol start="6">
<li>「APIキー」に移動後、「APIキーを生成する」をクリックしてキーを発行します</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_19.12.31.png" alt="スクリーンショット_2026-02-08_19.12.31.png" width="1200" height="757" loading="lazy">

<ol start="7">
<li>発行されたキーを控えておきます(後でDifyに登録します)</li>
</ol>
<h2 id="事前準備2-xAI-Grok-API">事前準備2:xAI(Grok)API</h2><h3 id="APIキーの取得手順-1">APIキーの取得手順</h3><ol>
<li><p>xAI Console にアクセスし、アカウントを作成します</p>
</li>
<li><p>ダッシュボードから「API Keys」に移動します</p>
</li>
<li><p>「Create API Key」でキーを発行します</p>
<p> <strong>設定パラメータ</strong></p>
<div class="scroll"><table>
<thead>
<tr>
<th>カテゴリ</th>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody><tr>
<td>Restrict access</td>
<td>Models</td>
<td>grok-4-1-fast-reasoning</td>
</tr>
<tr>
<td>-</td>
<td>Endpoints</td>
<td>Chat</td>
</tr>
<tr>
<td>Rate limits</td>
<td>TPM</td>
<td>50000</td>
</tr>
<tr>
<td>-</td>
<td>RPM</td>
<td>20</td>
</tr>
</tbody></table></div>
</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_19.26.37.png" alt="スクリーンショット_2026-02-08_19.26.37.png" width="1200" height="1510" loading="lazy">

<ol start="4">
<li>発行されたキーを控えておきます</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_19.27.54.png" alt="スクリーンショット_2026-02-08_19.27.54.png" width="1200" height="324" loading="lazy">

<ol start="5">
<li>Billingから「Purchase credits」を選択します</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-09_5.00.31.png" alt="スクリーンショット_2026-02-09_5.00.31.png" width="1200" height="869" loading="lazy">

<ol start="6">
<li>最低購入額の $5 を入力し、クレジットカードを登録して購入します</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-09_5.01.14.png" alt="スクリーンショット_2026-02-09_5.01.14.png" width="1200" height="808" loading="lazy">

<h2 id="事前準備3-Google-AI-Studio-Gemini-API">事前準備3:Google AI Studio(Gemini API)</h2><h3 id="APIキーの取得手順-2">APIキーの取得手順</h3><ol>
<li>Google AI Studio にアクセスし、右上の Get started からGoogleアカウントでログインします</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_19.37.08.png" alt="スクリーンショット_2026-02-08_19.37.08.png" width="1200" height="764" loading="lazy">

<ol start="2">
<li>左下の「Get API Key」を選択し、続いて右上の「APIキーを作成」をクリックします</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_19.38.50.png" alt="スクリーンショット_2026-02-08_19.38.50.png" width="1200" height="646" loading="lazy">

<ol start="3">
<li>「プロジェクトを作成」から新しいプロジェクトを作成します</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_19.43.06.png" alt="スクリーンショット_2026-02-08_19.43.06.png" width="1022" height="560" loading="lazy">

<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_19.43.17.png" alt="スクリーンショット_2026-02-08_19.43.17.png" width="1048" height="584" loading="lazy">

<ol start="4">
<li>「Copy API key」で取得したキーを控えておきます</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_19.43.40.png" alt="スクリーンショット_2026-02-08_19.43.40.png" width="1048" height="582" loading="lazy">

<ol start="5">
<li>続いて、「お支払い情報を設定」をクリックします</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_21.29.42.png" alt="スクリーンショット_2026-02-08_21.29.42.png" width="1200" height="326" loading="lazy">

<ol start="6">
<li><strong>$300分の無料クレジット</strong>と<strong>自動請求なし</strong>が表示されていることを確認してから「同意して続行」を選択します</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_21.30.42.png" alt="スクリーンショット_2026-02-08_21.30.42.png" width="1200" height="753" loading="lazy">

<ol start="7">
<li>「連絡先情報」と「お支払い方法(無料枠の場合も必須)」を入力して「無料で利用開始」を選択します</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_21.32.29.png" alt="スクリーンショット_2026-02-08_21.32.29.png" width="1200" height="744" loading="lazy">

<ol start="8">
<li><p>Google AI Studioのコンソール画面に戻り、割り当てティアが「お支払い情報を有効にする」と表示されていれば設定完了です</p>
<p> 注意点: 「お支払い情報を有効にする」をクリックすると以下画面に遷移しますが、課金が始まる「有効化」は不要です</p>
</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_21.44.09.png" alt="スクリーンショット_2026-02-08_21.44.09.png" width="1200" height="1011" loading="lazy">

<h2 id="Dify-Cloud-のセットアップ">Dify Cloud のセットアップ</h2><h3 id="アカウント作成">アカウント作成</h3><p>Dify Cloud にアクセスして、アカウントを作成します。</p>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_21.48.38.png" alt="スクリーンショット_2026-02-08_21.48.38.png" width="1200" height="823" loading="lazy">

<h3 id="プラグインのインストール">プラグインのインストール</h3><p>Dify Cloudでは、外部LLMとの接続に「プラグイン」を使用します。今回必要なプラグインは以下の2つです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>プラグイン</th>
<th>用途</th>
</tr>
</thead>
<tbody><tr>
<td><code>langgenius/perplexity</code></td>
<td>PerplexityをToolノードとして使用するため</td>
</tr>
<tr>
<td><code>langgenius/gemini</code></td>
<td>GeminiをLLMノードとして使用するため</td>
</tr>
</tbody></table></div>
<p>補足: xAI(Grok)の<code>langgenius/x</code> は、本ブログ執筆時点(2026.02.08)では <code>x_search</code>(X&#x2F;Twitterを検索するオプション機能) に対応していないため、今回は利用しません。</p>
<p>インストール手順:</p>
<ol>
<li>Dify画面右上の「プラグイン」からメニューを開きます</li>
<li>「プラグインをインストールする」から「マーケットプレイス」を選択します</li>
<li>PerplexityとGeminiを検索して、インストールします</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_21.58.33.png" alt="スクリーンショット_2026-02-08_21.58.33.png" width="1200" height="467" loading="lazy">

<ol start="4">
<li>右上のアイコンをクリックして「設定」に移動して、「モデルプロバイダー」を表示します</li>
<li>Geminiの「セットアップ」をクリックして、API Keyに <strong>Google AI Studioで発行したAPIキー</strong> を入力して保存します</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_22.04.35.png" alt="スクリーンショット_2026-02-08_22.04.35.png" width="1200" height="532" loading="lazy">

<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_22.05.22.png" alt="スクリーンショット_2026-02-08_22.05.22.png" width="1200" height="977" loading="lazy">

<ol start="6">
<li>トークン名の右側に緑色のランプが点灯していれば、Geminiの設定は完了です</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_22.06.21.png" alt="スクリーンショット_2026-02-08_22.06.21.png" width="1200" height="194" loading="lazy">

<h3 id="ワークフローの準備">ワークフローの準備</h3><ol>
<li>画面上部の「スタジオ」に移動して「最初から作成」を選択します</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_22.16.10.png" alt="スクリーンショット_2026-02-08_22.16.10.png" width="1200" height="557" loading="lazy">

<ol start="2">
<li>「ワークフロー」を選択して、名前と説明文を入力したら「作成する」を選択します</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_22.16.48.png" alt="スクリーンショット_2026-02-08_22.16.48.png" width="1200" height="599" loading="lazy">

<ol start="3">
<li>開始ノードには「ユーザ入力」を利用します</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_22.18.13.png" alt="スクリーンショット_2026-02-08_22.18.13.png" width="1200" height="593" loading="lazy">

<h3 id="環境変数の設定">環境変数の設定</h3><p>ワークフローの編集画面で、環境変数を2つ設定します。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>変数名</th>
<th>型</th>
<th>用途</th>
</tr>
</thead>
<tbody><tr>
<td><code>xai_api_key</code></td>
<td><strong>Secret</strong></td>
<td>GrokのAPIトークン</td>
</tr>
<tr>
<td><code>CHUDOU_DESCRIPTION</code></td>
<td>String</td>
<td>プロンプト内で参照する政党情報(後述)</td>
</tr>
</tbody></table></div>
<p><code>xai_api_key</code>をSecret型にすることで、Dify上のUIでマスク表示されます。もう1つの<code>CHUDOU_DESCRIPTION</code>は政党の説明文を格納する変数で、プロンプト内から<code>&#123;&#123;#env.CHUDOU_DESCRIPTION#&#125;&#125;</code>で参照します。</p>
<ol>
<li>右上の <code>ENV</code> をクリックします</li>
<li>「環境変数を追加」のポップアップに適切な「タイプ・変数名・値」を入力します<ul>
<li><code>xai_api_key</code> の値には、xAIで取得したAPIキーを入力します</li>
<li><code>CHUDOU_DESCRIPTION</code> には政党の説明文を入力します。今回、登録する説明文はAIに生成させました</li>
</ul>
</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_22.23.26.png" alt="スクリーンショット_2026-02-08_22.23.26.png" width="1200" height="883" loading="lazy">

<ol start="3">
<li>環境変数を登録すると、以下のように表示されます。（<code>CHUDOU_DESCRIPTION</code>の説明文はAIに生成させてみてください）</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_22.25.24.png" alt="スクリーンショット_2026-02-08_22.25.24.png" width="1070" height="726" loading="lazy">

<h2 id="ワークフロー構築">ワークフロー構築</h2><p>今回のワークフローは、最終的に以下のような形になります。</p>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-09_4.49.32.png" alt="スクリーンショット_2026-02-09_4.49.32.png" width="1200" height="616" loading="lazy">

<p>ここからは、Difyのワークフローエディタ上で構築する各ノードを順番に解説します。</p>
<h3 id="スタートノード">スタートノード</h3><p>入力変数は何も設定しません。今回のワークフローは「<strong>完全手動実行</strong>」です。画面右上の「テスト実行」ボタンを押すことで全体のワークフローが動きます。本ブログでは、動作確認のしやすさから「手動実行」としていますが、スケジュールノードに変更すれば「定期自動実行」への切り替えも可能です。</p>
<h3 id="検索日作成-Codeノード-Python">検索日作成(Codeノード &#x2F; Python)</h3><p>Grokの<code>x_search</code>や後続のノードに渡す日付範囲を動的生成するためのノードです。</p>
<p><strong>ノードの追加</strong></p>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_22.35.13.png" alt="スクリーンショット_2026-02-08_22.35.13.png" width="1200" height="1462" loading="lazy">

<p>Pythonの処理には以下コードを登録します。</p>
<figure class="highlight python"><input type="checkbox" id="code-wrap-119hf3x-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-119hf3x-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> datetime <span class="keyword">import</span> datetime, timedelta, timezone</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>() -&gt; <span class="built_in">dict</span>:</span><br><span class="line">    now = datetime.now(timezone.utc)</span><br><span class="line"></span><br><span class="line">    from_dt = now - timedelta(hours=<span class="number">24</span>)</span><br><span class="line"></span><br><span class="line">    to_date = now.strftime(<span class="string">&quot;%Y-%m-%d&quot;</span>)</span><br><span class="line">    from_date = from_dt.strftime(<span class="string">&quot;%Y-%m-%d&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="string">&quot;from_date&quot;</span>: from_date,</span><br><span class="line">        <span class="string">&quot;to_date&quot;</span>: to_date,</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>出力は<code>from_date</code>と<code>to_date</code>の2つの文字列です。UTCベースで生成しているのは、Dify Cloudのサーバのタイムゾーンに依存しないようにするためです。</p>
<ul>
<li>入力変数<ul>
<li>（なし）</li>
</ul>
</li>
<li>出力変数<ul>
<li>from_date: String</li>
<li>to_date: String</li>
</ul>
</li>
</ul>
<p><strong>ノードの完成形</strong></p>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_22.38.08.png" alt="スクリーンショット_2026-02-08_22.38.08.png" width="1200" height="1093" loading="lazy">

<h3 id="Grok-HTTP-Requestノード">Grok(HTTP Requestノード)</h3><p>Difyの xAI(Grok) プラグインには <code>langgenius/x</code> があります。ただし、このプラグインでは、X&#x2F;Twitterを検索する <code>x_search</code> が本ブログ執筆時点(2026.02.08)では利用できないため、今回は HTTP Requestノードで直接APIを叩きます。</p>
<p><strong>ノードの追加</strong></p>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_22.39.47.png" alt="スクリーンショット_2026-02-08_22.39.47.png" width="1200" height="1111" loading="lazy">

<p><strong>認証設定</strong></p>
<p>「認証なし」と表示されているボタンを選択して、環境変数に登録したGrokのAPIキーを登録します。</p>
<ul>
<li>認証タイプ: APIキー</li>
<li>API認証タイプ: Bearer</li>
<li>APIキー: <code>&#123;&#123;#env.xai_api_key#&#125;&#125;</code>（「環境変数」として登録した値）</li>
</ul>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_22.44.05.png" alt="スクリーンショット_2026-02-08_22.44.05.png" width="1200" height="844" loading="lazy">

<p><strong>パラメータ設定内容</strong></p>
<div class="scroll"><table>
<thead>
<tr>
<th>カテゴリ</th>
<th>設定項目</th>
<th>値</th>
</tr>
</thead>
<tbody><tr>
<td>API</td>
<td>メソッド</td>
<td>POST</td>
</tr>
<tr>
<td>-</td>
<td>URL</td>
<td><code>https://api.x.ai/v1/responses</code></td>
</tr>
<tr>
<td>ヘッダー</td>
<td>キー</td>
<td><code>Content-Type</code></td>
</tr>
<tr>
<td>-</td>
<td>値</td>
<td><code>application/json</code></td>
</tr>
<tr>
<td>タイムアウト設定</td>
<td>接続タイムアウト</td>
<td>10秒</td>
</tr>
<tr>
<td>-</td>
<td>読み取りタイムアウト</td>
<td>240秒</td>
</tr>
<tr>
<td>-</td>
<td>書き込みタイムアウト</td>
<td>30秒</td>
</tr>
</tbody></table></div>
<ul>
<li>失敗時再試行<ul>
<li>最大試行回数: 3回</li>
<li>再試行間隔: 1000ミリ秒</li>
</ul>
</li>
<li>例外処理<ul>
<li>例外分岐</li>
</ul>
</li>
</ul>
<p><strong>リクエストボディ(JSON)</strong></p>
<figure class="highlight json"><input type="checkbox" id="code-wrap-119hf3x-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-119hf3x-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;model&quot;</span><span class="punctuation">:</span> <span class="string">&quot;grok-4-1-fast-reasoning&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;input&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;role&quot;</span><span class="punctuation">:</span> <span class="string">&quot;user&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;【日付】&#123;&#123;#検索日作成.to_date#&#125;&#125;\n\nX上での2026年衆院選に関するSNS動向を分析してください。...&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;tools&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;x_search&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;from_date&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;&#123;#検索日作成.from_date#&#125;&#125;&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;to_date&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;&#123;#検索日作成.to_date#&#125;&#125;&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;temperature&quot;</span><span class="punctuation">:</span> <span class="number">0.7</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;top_p&quot;</span><span class="punctuation">:</span> <span class="number">0.95</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;max_output_tokens&quot;</span><span class="punctuation">:</span> <span class="number">30000</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<p><code>tools</code>配列に<code>x_search</code>を指定するのがポイントです。これにより、GrokがX上の投稿を実際に検索した上で回答を生成します。<code>from_date</code>と<code>to_date</code>は前段のCodeノードの出力を変数展開しています。</p>
<p>プロンプトは「ホリエモンAI選挙 予測の仕組みを公開」で公開された文面を利用しています。</p>
<p><strong>ノードの完成形</strong></p>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_22.59.16.png" alt="スクリーンショット_2026-02-08_22.59.16.png" width="1200" height="1655" loading="lazy">

<h3 id="Perplexity-Toolノード">Perplexity(Toolノード)</h3><p>DifyのPerplexityプラグインを「ツール」ノードとして配置します。</p>
<p><strong>ノードの追加</strong></p>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_22.57.42.png" alt="スクリーンショット_2026-02-08_22.57.42.png" width="1200" height="803" loading="lazy">

<p><strong>認証設定</strong></p>
<ol>
<li>「APIキー認証設定」をクリックする</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_23.01.17.png" alt="スクリーンショット_2026-02-08_23.01.17.png" width="1200" height="334" loading="lazy">

<ol start="2">
<li>Perplexity APIで発行したAPIキーを「Perplexity API key」に入力して保存する</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_23.02.14.png" alt="スクリーンショット_2026-02-08_23.02.14.png" width="1200" height="837" loading="lazy">

<p><strong>「Query」に登録するプロンプト</strong></p>
<p>「ホリエモンAI選挙 予測の仕組みを公開」のプロンプトに記載された以下の変数に対して、本ブログでは以下の対応を行なっています。</p>
<ul>
<li><strong>{TODAY}</strong>: 「検索日作成ノード」で作成した値の <code>to_date</code> を代入</li>
<li><strong>{CHUDOU_DESCRIPTION}</strong>: 環境変数の <code>&#123;CHUDOU_DESCRIPTION&#125;</code> を代入</li>
<li><strong>{RECENT_PARTIES_DETAIL_BLOCK}</strong>: プロンプトから削除</li>
</ul>
<p><strong>パラメータ設定内容</strong></p>
<div class="scroll"><table>
<thead>
<tr>
<th>パラメータ</th>
<th>設定値</th>
</tr>
</thead>
<tbody><tr>
<td>Model Name</td>
<td>sonar-pro</td>
</tr>
<tr>
<td>Max Tokens</td>
<td>20000</td>
</tr>
<tr>
<td>Temperature</td>
<td>0.6</td>
</tr>
<tr>
<td>Top K</td>
<td>40</td>
</tr>
<tr>
<td>Top P</td>
<td>0.9</td>
</tr>
<tr>
<td>Presence Penalty</td>
<td>0</td>
</tr>
<tr>
<td>Frequency Penalty</td>
<td>0.4</td>
</tr>
<tr>
<td>Return Images</td>
<td>False</td>
</tr>
<tr>
<td>Return Related Questions</td>
<td>False</td>
</tr>
<tr>
<td>Search Recency Filter</td>
<td>Week</td>
</tr>
<tr>
<td>Search Context Size</td>
<td>High</td>
</tr>
</tbody></table></div>
<ul>
<li>失敗時再試行<ul>
<li>最大試行回数: 3回</li>
<li>再試行間隔: 1000ミリ秒</li>
</ul>
</li>
<li>例外処理<ul>
<li>例外分岐</li>
</ul>
</li>
</ul>
<p><strong>ノードの完成形</strong></p>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_23.17.00.png" alt="スクリーンショット_2026-02-08_23.17.00.png" width="1200" height="1754" loading="lazy">

<h3 id="Gemini-LLMノード">Gemini(LLMノード)</h3><p>PerplexityとGrokの出力を受け取り、構造化されたJSONとして統合するGeminiノードを追加します。</p>
<p><strong>ノードの追加</strong></p>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_23.22.19.png" alt="スクリーンショット_2026-02-08_23.22.19.png" width="1200" height="428" loading="lazy">

<p><strong>LLMの設定</strong></p>
<ul>
<li><strong>モデル</strong>: <code>gemini-3-pro-preview</code></li>
<li><strong>Temperature</strong>: 0.3(構造化出力の安定性を重視)</li>
</ul>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_23.26.43.png" alt="スクリーンショット_2026-02-08_23.26.43.png" width="950" height="398" loading="lazy">

<p><strong>プロンプト</strong></p>
<p>プロンプトの中でPerplexityとGrokの出力結果を明示的に渡します。</p>
<figure class="highlight text"><table><tr><td class="code"><pre><span class="line">...</span><br><span class="line">【ニュース・調査データ(Perplexity)】</span><br><span class="line">-&gt; Perplexity / &#123;x&#125; text</span><br><span class="line"></span><br><span class="line">【X/Twitter世論分析(Grok)】</span><br><span class="line">-&gt; Grok / &#123;x&#125; body</span><br><span class="line">...</span><br></pre></td></tr></table></figure>

<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_23.31.40.png" alt="スクリーンショット_2026-02-08_23.31.40.png" width="1200" height="671" loading="lazy">

<p><strong>構造化出力(Structured Output)の活用</strong></p>
<p>Difyの「構造化出力」機能を有効にし、JSONスキーマを定義しています。これにより、Geminiの出力を指定したスキーマに従わせます。ただし、このスキーマを指定したとしても、本来ならば数字が入るべきところに文字列が入ってしまうケースなどが見られたので、出力の完全保証とはなりません。</p>
<p>「出力変数」の <code>structured_output</code> から、出力スキーマを指定します。</p>
<details>
<summary>インポートするJSON全文(クリックで展開)</summary>

<figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;object&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;analysis_date&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;cabinet_approval&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;object&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;rate&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;number&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;trend&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;source&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;confidence&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;number&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;analysis&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;required&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;rate&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;trend&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;source&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;confidence&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;analysis&quot;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;additionalProperties&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;party_momentum&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;object&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;ldp&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;object&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;support_rate&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;number&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;trend&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;analysis&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">          <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;required&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="string">&quot;support_rate&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;trend&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;analysis&quot;</span></span><br><span class="line">          <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;additionalProperties&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;chudou&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;object&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;support_rate&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;number&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;trend&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;analysis&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">          <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;required&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="string">&quot;support_rate&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;trend&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;analysis&quot;</span></span><br><span class="line">          <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;additionalProperties&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;ishin&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;object&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;support_rate&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;number&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;trend&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;analysis&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">          <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;required&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="string">&quot;support_rate&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;trend&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;analysis&quot;</span></span><br><span class="line">          <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;additionalProperties&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;dpfp&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;object&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;support_rate&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;number&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;trend&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;analysis&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">          <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;required&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="string">&quot;support_rate&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;trend&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;analysis&quot;</span></span><br><span class="line">          <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;additionalProperties&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;jcp&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;object&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;support_rate&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;number&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;trend&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;analysis&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">          <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;required&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="string">&quot;support_rate&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;trend&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;analysis&quot;</span></span><br><span class="line">          <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;additionalProperties&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;reiwa&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;object&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;support_rate&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;number&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;trend&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;analysis&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">          <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;required&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="string">&quot;support_rate&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;trend&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;analysis&quot;</span></span><br><span class="line">          <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;additionalProperties&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;sansei&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;object&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;support_rate&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;number&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;trend&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;analysis&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">          <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;required&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="string">&quot;support_rate&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;trend&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;analysis&quot;</span></span><br><span class="line">          <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;additionalProperties&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;team_mirai&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;object&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;support_rate&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;number&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;trend&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;analysis&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">          <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;required&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="string">&quot;support_rate&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;trend&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;analysis&quot;</span></span><br><span class="line">          <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;additionalProperties&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;hoshu&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;object&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;support_rate&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;number&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;trend&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;analysis&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">          <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;required&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="string">&quot;support_rate&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;trend&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;analysis&quot;</span></span><br><span class="line">          <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;additionalProperties&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;genzei_yukoku&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;object&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;support_rate&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;number&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;trend&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;analysis&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">          <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;required&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="string">&quot;support_rate&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;trend&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;analysis&quot;</span></span><br><span class="line">          <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;additionalProperties&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;other&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;object&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;support_rate&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;number&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;trend&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;analysis&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">              <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">          <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;required&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="string">&quot;support_rate&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;trend&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;analysis&quot;</span></span><br><span class="line">          <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;additionalProperties&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;required&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;ldp&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;chudou&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;ishin&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;dpfp&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;jcp&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;reiwa&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;sansei&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;team_mirai&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;hoshu&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;genzei_yukoku&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;other&quot;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;additionalProperties&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;key_issues&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;array&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;items&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;key_issues_confidence&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;number&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;regional_trends&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;object&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;urban&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;urban_summary&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;urban_confidence&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;number&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;rural&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;rural_summary&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;rural_confidence&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;number&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;required&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;urban&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;urban_summary&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;urban_confidence&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;rural&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;rural_summary&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;rural_confidence&quot;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;additionalProperties&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;sources&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;array&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;items&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;required&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;analysis_date&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;cabinet_approval&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;party_momentum&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;key_issues&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;key_issues_confidence&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;regional_trends&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;sources&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;additionalProperties&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

</details>

<img src="/images/2026/20260212a/スクリーンショット_2026-02-09_1.35.12.png" alt="スクリーンショット_2026-02-09_1.35.12.png" width="1200" height="1006" loading="lazy">

<p>スキーマの主要構成:</p>
<figure class="highlight json"><input type="checkbox" id="code-wrap-119hf3x-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-119hf3x-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;analysis_date&quot;</span><span class="punctuation">:</span> <span class="string">&quot;分析日&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;cabinet_approval&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;rate&quot;</span><span class="punctuation">:</span> <span class="string">&quot;支持率(数値)&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;trend&quot;</span><span class="punctuation">:</span> <span class="string">&quot;rising | stable | declining&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;source&quot;</span><span class="punctuation">:</span> <span class="string">&quot;情報源&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;confidence&quot;</span><span class="punctuation">:</span> <span class="string">&quot;信頼度(0-100)&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;analysis&quot;</span><span class="punctuation">:</span> <span class="string">&quot;分析コメント&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;party_momentum&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;ldp&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;support_rate&quot;</span><span class="punctuation">:</span> <span class="string">&quot;...&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;trend&quot;</span><span class="punctuation">:</span> <span class="string">&quot;...&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;analysis&quot;</span><span class="punctuation">:</span> <span class="string">&quot;...&quot;</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;chudou&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> ... <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// ... 各政党</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;key_issues&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;争点1&quot;</span><span class="punctuation">,</span> <span class="string">&quot;争点2&quot;</span><span class="punctuation">,</span> <span class="string">&quot;争点3&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;regional_trends&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;urban&quot;</span><span class="punctuation">:</span> <span class="string">&quot;都市部の傾向&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;rural&quot;</span><span class="punctuation">:</span> <span class="string">&quot;地方の傾向&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;sources&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;ソース1&quot;</span><span class="punctuation">,</span> <span class="string">&quot;ソース2&quot;</span><span class="punctuation">,</span> ...<span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<ul>
<li>失敗時再試行<ul>
<li>最大試行回数: 3回</li>
<li>再試行間隔: 1000ミリ秒</li>
</ul>
</li>
<li>例外処理<ul>
<li>例外分岐</li>
</ul>
</li>
</ul>
<p><strong>ノードの完成形</strong></p>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_23.38.11.png" alt="スクリーンショット_2026-02-08_23.38.11.png" width="1200" height="1504" loading="lazy">

<h3 id="テーブル化-Codeノード-Python">テーブル化(Codeノード &#x2F; Python)</h3><p>GeminiのJSON出力を、人間が読みやすいMarkdownテーブルに変換するノードです。</p>
<ul>
<li>入力変数<ul>
<li>gemini: Gemini&#x2F;{x} text String</li>
</ul>
</li>
<li>出力変数<ul>
<li>markdown_table: String</li>
</ul>
</li>
</ul>
<details>
<summary>テーブル化のPythonコード全文(クリックで展開)</summary>

<figure class="highlight python"><input type="checkbox" id="code-wrap-119hf3x-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-119hf3x-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> json</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>(<span class="params">gemini: <span class="built_in">str</span></span>) -&gt; <span class="built_in">dict</span>:</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        data = json.loads(gemini)</span><br><span class="line">    <span class="keyword">except</span> json.JSONDecodeError:</span><br><span class="line">        <span class="keyword">return</span> &#123;<span class="string">&quot;markdown_table&quot;</span>: <span class="string">&quot;エラー: 有効なJSONデータではありません。&quot;</span>&#125;</span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        <span class="keyword">return</span> &#123;<span class="string">&quot;markdown_table&quot;</span>: <span class="string">f&quot;エラーが発生しました: <span class="subst">&#123;<span class="built_in">str</span>(e)&#125;</span>&quot;</span>&#125;</span><br><span class="line"></span><br><span class="line">    date = data.get(<span class="string">&quot;analysis_date&quot;</span>, <span class="string">&quot;&quot;</span>)</span><br><span class="line">    cab = data.get(<span class="string">&quot;cabinet_approval&quot;</span>, &#123;&#125;)</span><br><span class="line"></span><br><span class="line">    trend_map = &#123;</span><br><span class="line">        <span class="string">&quot;rising&quot;</span>: <span class="string">&quot;上昇 ↗&quot;</span>,</span><br><span class="line">        <span class="string">&quot;declining&quot;</span>: <span class="string">&quot;下落 ↘&quot;</span>,</span><br><span class="line">        <span class="string">&quot;stable&quot;</span>: <span class="string">&quot;横ばい →&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    cab_trend = trend_map.get(cab.get(<span class="string">&quot;trend&quot;</span>), cab.get(<span class="string">&quot;trend&quot;</span>))</span><br><span class="line"></span><br><span class="line">    output = []</span><br><span class="line">    output.append(<span class="string">f&quot;### 分析日: <span class="subst">&#123;date&#125;</span>&quot;</span>)</span><br><span class="line">    output.append(<span class="string">&quot;&quot;</span>)</span><br><span class="line">    output.append(<span class="string">&quot;### 1. 内閣支持率・重要課題&quot;</span>)</span><br><span class="line">    output.append(<span class="string">&quot;| 項目 | 数値/ステータス | 分析・詳細 |&quot;</span>)</span><br><span class="line">    output.append(<span class="string">&quot;|---|---|---|&quot;</span>)</span><br><span class="line">    output.append(<span class="string">f&quot;| **内閣支持率** | <span class="subst">&#123;cab.get(<span class="string">&#x27;rate&#x27;</span>, <span class="string">&#x27;-&#x27;</span>)&#125;</span>% (<span class="subst">&#123;cab_trend&#125;</span>) | <span class="subst">&#123;cab.get(<span class="string">&#x27;analysis&#x27;</span>, <span class="string">&#x27;&#x27;</span>).replace(<span class="string">&#x27;\n&#x27;</span>, <span class="string">&#x27;&#x27;</span>)&#125;</span> |&quot;</span>)</span><br><span class="line"></span><br><span class="line">    issues = data.get(<span class="string">&quot;key_issues&quot;</span>, [])</span><br><span class="line">    issues_str = <span class="string">&quot;、&quot;</span>.join(issues)</span><br><span class="line">    output.append(<span class="string">f&quot;| **重要課題** | 信頼度: <span class="subst">&#123;data.get(<span class="string">&#x27;key_issues_confidence&#x27;</span>)&#125;</span>% | <span class="subst">&#123;issues_str&#125;</span> |&quot;</span>)</span><br><span class="line">    output.append(<span class="string">&quot;&quot;</span>)</span><br><span class="line"></span><br><span class="line">    parties = data.get(<span class="string">&quot;party_momentum&quot;</span>, &#123;&#125;)</span><br><span class="line"></span><br><span class="line">    name_map = &#123;</span><br><span class="line">        <span class="string">&quot;ldp&quot;</span>: <span class="string">&quot;自民党&quot;</span>,</span><br><span class="line">        <span class="string">&quot;chudou&quot;</span>: <span class="string">&quot;中道改革連合&quot;</span>,</span><br><span class="line">        <span class="string">&quot;ishin&quot;</span>: <span class="string">&quot;日本維新の会&quot;</span>,</span><br><span class="line">        <span class="string">&quot;dpfp&quot;</span>: <span class="string">&quot;国民民主党&quot;</span>,</span><br><span class="line">        <span class="string">&quot;jcp&quot;</span>: <span class="string">&quot;日本共産党&quot;</span>,</span><br><span class="line">        <span class="string">&quot;reiwa&quot;</span>: <span class="string">&quot;れいわ新選組&quot;</span>,</span><br><span class="line">        <span class="string">&quot;sansei&quot;</span>: <span class="string">&quot;参政党&quot;</span>,</span><br><span class="line">        <span class="string">&quot;team_mirai&quot;</span>: <span class="string">&quot;チームみらい&quot;</span>,</span><br><span class="line">        <span class="string">&quot;hoshu&quot;</span>: <span class="string">&quot;日本保守党&quot;</span>,</span><br><span class="line">        <span class="string">&quot;genzei_yukoku&quot;</span>: <span class="string">&quot;減税日本・ゆうこく連合&quot;</span>,</span><br><span class="line">        <span class="string">&quot;other&quot;</span>: <span class="string">&quot;その他&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    output.append(<span class="string">&quot;### 2. 政党別情勢&quot;</span>)</span><br><span class="line">    output.append(<span class="string">&quot;| 政党 | 支持率 | トレンド | 分析 |&quot;</span>)</span><br><span class="line">    output.append(<span class="string">&quot;|---|---|---|---|&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> key, info <span class="keyword">in</span> parties.items():</span><br><span class="line">        name = name_map.get(key, key)</span><br><span class="line">        rate = <span class="string">f&quot;<span class="subst">&#123;info.get(<span class="string">&#x27;support_rate&#x27;</span>, <span class="string">&#x27;&#x27;</span>)&#125;</span>%&quot;</span></span><br><span class="line">        trend_val = info.get(<span class="string">&quot;trend&quot;</span>, <span class="string">&quot;&quot;</span>)</span><br><span class="line">        trend = trend_map.get(trend_val, trend_val)</span><br><span class="line">        analysis = info.get(<span class="string">&quot;analysis&quot;</span>, <span class="string">&quot;&quot;</span>).replace(<span class="string">&quot;\n&quot;</span>, <span class="string">&quot; &quot;</span>)</span><br><span class="line"></span><br><span class="line">        output.append(<span class="string">f&quot;| <span class="subst">&#123;name&#125;</span> | <span class="subst">&#123;rate&#125;</span> | <span class="subst">&#123;trend&#125;</span> | <span class="subst">&#123;analysis&#125;</span> |&quot;</span>)</span><br><span class="line"></span><br><span class="line">    output.append(<span class="string">&quot;&quot;</span>)</span><br><span class="line"></span><br><span class="line">    reg = data.get(<span class="string">&quot;regional_trends&quot;</span>, &#123;&#125;)</span><br><span class="line"></span><br><span class="line">    output.append(<span class="string">&quot;### 3. 地域別情勢&quot;</span>)</span><br><span class="line">    output.append(<span class="string">&quot;| 地域 | 概況 | 詳細 | 信頼度 |&quot;</span>)</span><br><span class="line">    output.append(<span class="string">&quot;|---|---|---|---|&quot;</span>)</span><br><span class="line">    output.append(<span class="string">f&quot;| **都市部** | <span class="subst">&#123;reg.get(<span class="string">&#x27;urban_summary&#x27;</span>, <span class="string">&#x27;&#x27;</span>)&#125;</span> | <span class="subst">&#123;reg.get(<span class="string">&#x27;urban&#x27;</span>, <span class="string">&#x27;&#x27;</span>)&#125;</span> | <span class="subst">&#123;reg.get(<span class="string">&#x27;urban_confidence&#x27;</span>, <span class="string">&#x27;&#x27;</span>)&#125;</span>% |&quot;</span>)</span><br><span class="line">    output.append(<span class="string">f&quot;| **地方** | <span class="subst">&#123;reg.get(<span class="string">&#x27;rural_summary&#x27;</span>, <span class="string">&#x27;&#x27;</span>)&#125;</span> | <span class="subst">&#123;reg.get(<span class="string">&#x27;rural&#x27;</span>, <span class="string">&#x27;&#x27;</span>)&#125;</span> | <span class="subst">&#123;reg.get(<span class="string">&#x27;rural_confidence&#x27;</span>, <span class="string">&#x27;&#x27;</span>)&#125;</span>% |&quot;</span>)</span><br><span class="line">    output.append(<span class="string">&quot;&quot;</span>)</span><br><span class="line"></span><br><span class="line">    sources = data.get(<span class="string">&quot;sources&quot;</span>, [])</span><br><span class="line">    <span class="keyword">if</span> sources:</span><br><span class="line">        output.append(<span class="string">&quot;---&quot;</span>)</span><br><span class="line">        output.append(<span class="string">&quot;**データソース:**&quot;</span>)</span><br><span class="line">        <span class="keyword">for</span> src <span class="keyword">in</span> sources:</span><br><span class="line">            output.append(<span class="string">f&quot;- <span class="subst">&#123;src&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line">    final_markdown = <span class="string">&quot;\n&quot;</span>.join(output)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="string">&quot;markdown_table&quot;</span>: final_markdown</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

</details>

<br>

<p>主な処理:</p>
<ul>
<li>JSONパース処理</li>
<li>トレンド値の日本語変換<ul>
<li><code>rising</code> → <code>上昇 ↗</code></li>
<li><code>declining</code> → <code>下落 ↘</code></li>
<li><code>stable</code> → <code>横ばい →</code></li>
</ul>
</li>
<li>政党キーの日本語マッピング<ul>
<li><code>ldp</code> → <code>自民党</code></li>
<li><code>chudou</code> → <code>中道改革連合</code></li>
<li>etc.</li>
</ul>
</li>
<li>各セクション(内閣支持率、政党別情勢、地域別情勢、データソース)をMarkdown表に整形</li>
<li>JSONパースに失敗した場合のエラーメッセージ出力</li>
</ul>
<p>出力は1つの<code>markdown_table</code>文字列です。この文字列がワークフローの最終出力としてEndノードに渡されます。</p>
<p><strong>ノードの完成形</strong></p>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_23.48.04.png" alt="スクリーンショット_2026-02-08_23.48.04.png" width="1200" height="874" loading="lazy">

<h3 id="集計結果-出力ノード">集計結果(出力ノード)</h3><p>「テーブル化」された集計結果を表示します。</p>
<p><strong>ノードの追加</strong></p>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_23.48.46.png" alt="スクリーンショット_2026-02-08_23.48.46.png" width="1200" height="461" loading="lazy">

<p>出力変数:</p>
<div class="scroll"><table>
<thead>
<tr>
<th>変数名</th>
<th>値</th>
<th>内容</th>
</tr>
</thead>
<tbody><tr>
<td>markdown_table</td>
<td>テーブル化 &#x2F; {x} markdown_table String</td>
<td>テーブル形式に整形されたGeminiの集計結果</td>
</tr>
</tbody></table></div>
<p><strong>ノードの完成形</strong></p>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-09_1.43.51.png" alt="スクリーンショット_2026-02-09_1.43.51.png" width="1200" height="426" loading="lazy">

<h3 id="エラーハンドリング-出力ノード">エラーハンドリング(出力ノード)</h3><p>各ノード(Perplexity、Grok、Gemini、テーブル化)には<code>fail-branch</code>(失敗時分岐)を設定しています。いずれかのノードが失敗した場合、専用のエラーEndノードに遷移し、どのノードで失敗したかを個別の変数として表示します。</p>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-08_23.58.51.png" alt="スクリーンショット_2026-02-08_23.58.51.png" width="1200" height="729" loading="lazy">

<p>出力変数:</p>
<div class="scroll"><table>
<thead>
<tr>
<th>変数名</th>
<th>値</th>
<th>内容</th>
</tr>
</thead>
<tbody><tr>
<td>perplexity_error</td>
<td>Perplexity &#x2F; {x} error_message String</td>
<td>Perplexityノードのエラーメッセージ</td>
</tr>
<tr>
<td>grok_error</td>
<td>Grok &#x2F; {x} error_message String</td>
<td>Grokノードのエラーメッセージ</td>
</tr>
<tr>
<td>gemini_error</td>
<td>Gemini &#x2F; {x} error_message String</td>
<td>Geminiノードのエラーメッセージ</td>
</tr>
<tr>
<td>table_error</td>
<td>テーブル化 &#x2F; {x} error_message String</td>
<td>テーブル化ノードのエラーメッセージ</td>
</tr>
</tbody></table></div>
<p>これにより、外部APIや内部処理でエラーが起こった場合に、どれが落ちたのかを特定できます。</p>
<p><strong>ノードの完成形</strong></p>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-09_1.44.31.png" alt="スクリーンショット_2026-02-09_1.44.31.png" width="1200" height="662" loading="lazy">

<h2 id="ワークフローの実行">ワークフローの実行</h2><p>すべてのノードの配置と設定が完了したら、いよいよワークフローを実行します。</p>
<ol>
<li>右上の「テスト実行」をクリックする</li>
<li>ワークフローの実行完了を待つ</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-09_3.41.38.png" alt="スクリーンショット_2026-02-09_3.41.38.png" width="1200" height="453" loading="lazy">

<ol start="3">
<li><code>Test Run</code> の「結果」で集計結果を確認する</li>
</ol>
<img src="/images/2026/20260212a/スクリーンショット_2026-02-09_3.43.52.png" alt="スクリーンショット_2026-02-09_3.43.52.png" width="1200" height="601" loading="lazy">

<hr>
<h2 id="おわりに">おわりに</h2><p>本記事では、「ホリエモンAI選挙」の仕組みを参考に、Dify Cloud上でPerplexity・Grok・Geminiを組み合わせた選挙情勢分析ワークフローを構築しました。</p>
<p>今回改めて感じたのは、ホリエモンAI選挙が <strong>「予測の仕組み」を公開している</strong> ことの価値です。「Web検索 + SNS検索 + データ統合・構造化」というアーキテクチャだけでなく、具体的にどのAPIを使い、どんなプロンプトを投げているのかまで公開されていたことで、本記事のような再現検証が可能になりました。この「情報収集→分析→構造化」という役割分担のパターンは、選挙分析に限らず、市場調査やブランドモニタリングなど、幅広い領域に応用できるのではないかと感じています。</p>
<p>また、普段の私はインフラエンジニアとして活動していますが、今回はDify Cloudを利用したことで、インフラの構築やメンテナンスについてほとんど考える必要がありませんでした。ワークフローの設計とプロンプトの調整に集中でき、使い方さえ分かっていれば、アイデアを形にするまでの時間が大幅に短縮されることを実感しました。</p>
<p>なお、今回の実装にあたっては、各種調査にPerplexityを多用しました。達成したい目的が明確な状況では、各情報に引用元のリンクが付いた状態で回答が得られるため、情報の裏取りがしやすく非常に便利でした。</p>
<p>一方で課題もあります。実行するたびに支持率などの数値が大きく変動し、出力が安定しない事象が見られました。出力の安定性を高めるには、少なくとも以下の4点の調整が必要だと考えています。</p>
<ul>
<li><strong>質の高い事前投入データ</strong>: 本家で投入されている選挙区の区割り情報や立候補者情報を、今回は一切投入していない。正確なベースデータがなければ、AIの出力はその都度の検索結果に左右されやすくなる</li>
<li><strong>途中でのデータ加工処理</strong>: 各AIの出力をそのまま次のノードに渡すのではなく、中間で不要な情報の除去やフォーマットの正規化を行うことで、後段のAIへの入力品質を上げられる</li>
<li><strong>出力のバリデーション処理</strong>: Geminiの構造化出力後に、支持率の合計値が妥当な範囲か、数値フィールドに文字列が混入していないか等をCodeノードで検証し、異常値があれば再実行やフォールバックを行う仕組みを入れる</li>
<li><strong>LLMに与えるパラメータの最適化</strong>: Temperature等のパラメータを用途に応じて細かくチューニングすることで、出力のばらつきを抑えられる</li>
</ul>
<p>これらは今後の改善ポイントです。</p>
]]></content>
    <summary type="html">最近、「マルチAIエージェント」という言葉をよく耳にするようになりました。単一のLLMですべてを解決するのではなく、検索が得意なAI、分析が得意なAIなどを適材適所で組み合わせる手法です。その好例として...</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="AIエージェント" scheme="https://future-architect.github.io/tags/AI%E3%82%A8%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%B3%E3%83%88/"/>
    <category term="Dify" scheme="https://future-architect.github.io/tags/Dify/"/>
  </entry>
  <entry>
    <title>プログラミング未経験の学生が選択すると良さそうな生成AIツールと考え方</title>
    <link href="https://future-architect.github.io/articles/20251208a/"/>
    <id>https://future-architect.github.io/articles/20251208a/</id>
    <published>2025-12-07T15:00:00.000Z</published>
    <updated>2025-12-07T15:00:00.000Z</updated>
    <author><name>澁川喜規</name></author>
    <content type="html"><![CDATA[<p>技育祭でも聞かれた「AI時代にどうやって経験を積んでいけばいいのか」というテーマです。</p>
<h2 id="オーガニックな時代は学習速度が遅かった">オーガニックな時代は学習速度が遅かった</h2><p>僕はパソコン通信やインターネットがない時代にプログラミングを始めました。まあ当時の話を書いても若い人とは環境が違いすぎるので、細かいことは書きません。インターネットが使えるようになったら、確かに学習の速度は上がりました。わかならないことを調べるとだいたい同じような悩みを持っている人が世の中にはいて、すでに解決策を見つけてネット上に書いてくれたりします。またツールや言語の発展とも関連して、具体例を説明した本とか記事とかも増えているように思います。</p>
<p>とはいえ、何かしらを実装するには何か作りたいものを決め、それに合わせてアーキテクチャを決めて実装してみて・・・上手くいかなくて作り直し、みたいなのを何ヶ月か単位でやっていました。</p>
<p>だいたい入門書ばかりが書籍では売れるのですが、一通り本に書いてあることをなぞっておしまい。じゃあ応用して自分で作りたいものを作るには・・・？というところはよく初心者のつまづきポイントとして知られていましたので、そもそも一本何かオリジナルで作るというところに辿り着かないことも多かったと思います。</p>
<p>また、まずは画面周りを頑張ってみる、次はサーバーを頑張ってみる、など1つ1つできることを増やしていってそれに合わせて手を動かすということをしてきました。戦略的に使える技術を増やすためにちょっとずつ新しいものを触れるような作戦をとっている人もいれば、作りたいものありきでやりたいことを増やしてきた人もいるでしょう。どちらにしても1度に1つしか学べなかったし、学ばないと物も作れなかったというのが過去の時代ですね。</p>
<h2 id="誰しもが上から下まで全部書けたわけではない">誰しもが上から下まで全部書けたわけではない</h2><p>オーガニックプログラミング時代はAIがなかったので、みんなが上から下まで経験を積んで開発できた・・・というわけではありません。結局はサンプルコードのコピー&amp;ペーストでものを作っている、という人は一定数いるでしょう。以下のようなジョーク画像が作られるほどです。</p>
<img fetchpriority="high" src="/images/2025/20251208a/スクリーンショット_2025-11-29_21.24.10.png" alt="" width="605" height="970">

<p>かくいう僕も、UI周りのボタンとかデザインが苦手で、Bootstrapが出たらそれを使い、Material Designが出たらそれのCSSを使うという感じでUIデザインをしていました。世の中もそういう人が多かったらしく、デザインにこだわりがある人からすると「Bootstrap臭がするダサいデザイン」としか見えないと判断されるのは知っていましたが、まあデザインセンスはそんな身につかないし、それしかできないので仕方がないですね。</p>
<p>結局1つずつ積み上げで勉強しないと手は動かないし、手が動かない領域はコピー&amp;ペーストやほぼ完成品がサンプルコピーで動くようなフレームワークを使って誤魔化すのが当たり前に行われてきた、というのが実態かなと思います。おっさんたちが「今の若者は学習のチャンスが減ってかわいそう」というのは余計なお世話というか、自分の胸に手を当てて考えな、と返すのが正しいお作法と言えるかと思います。</p>
<h2 id="学習方法には2つある">学習方法には2つある</h2><p>今までは自分の思うものを作れるような知識を下から貯めてボトムアップで積み上げてアプリケーションに必要な知識をボトムアップで作るというのが王道パターンとされてきたように思います。現在の「AIを使って開発すると経験値がつかない」というのはこちらを意識してのことだと思います。</p>
<p>しかし、AI以前から、逆方向のトップダウンの学習ステップは確かにありました。たとえば、ウェブのフレームワークを使い、アプリが作られるようになってから、その下のレイヤーのHTTPを学ぶとか。あるいは、PythonやRubyでコードを書けるようになってから、高速化のために一部をC言語で再実装みたいな。僕もRubyを大学時代に触って初めてオブジェクト指向が理解できて、C++で意図を持ってコーディングができるようになりました。ソフトウェアはこう書くべき→そのためにはこの文法を使って組み上げよう、というところに補助線とか補助輪が提供されている環境を使うことで、流れを知って、その流れを埋めるためにどのように手を動かせば良いかがわかると。</p>
<p>最初に説明した「入門書で文法とか一通り理解したけど、自分の作りたいプログラムの組み方がわからない」はまさにボトムアップ型学習しかしてなくて、トップダウンの「全体の流れからブレークダウンしてコードにしていく作法」を学んでいないからこそ起きると言えます。</p>
<p>AI時代はこのトップダウン型の学習を意識すると良いかと思います。作りたいWebサービスがある・ファイルを操作するCLIのユーティリティがある、といったゴールがあって、コマンドラインのパラメータ解釈とかそういうのは自動で書いてくれて、コアのロジックもさっとは書いてくれる。ただその流れを見てパフォーマンスの悪い部分を自分でさっと直したりをしながら少しずつより効率の良い書き方を学んでいく、という流れになっていくはず。</p>
<p>以前もフレームワークを使ってコードは書ける、と言う人は多くいました。そこから興味を持って深ぼって仕組みを調べたりして、原理原則を学んで開発者として成長できる人というのはいました。そうじゃない人もいました。今後はそのような「還元して学んでいく」姿勢が大事になるはずです。</p>
<h2 id="ファッションと技能">ファッションと技能</h2><p>RubyやPythonの拡張機能を書くだけであればコマンドライン引数をどう処理するかとか、メモリ管理とか多くの部分はもうレールに乗っているのでC言語でゼロからアプリケーションを書くのと比べて圧倒的に必要な知識は減ります。なにせ今時の言語ランタイム（配列とか文字列とか）相当が欠けていて、そこを自分で組み上げるなり外部ライブラリを利用するなりして初めて効率よくコードが書ける言語ですし。枠組みの中でコードを書くだけであればかなり考えることは減ります。</p>
<p>ファッションとか洋服とかも、「着こなす」とか「ファッションセンス」とかは、自分で洋服をゼロから作っているわけではなくて既製品の組み合わせ方です。もちろん型紙からミシンで洋服を縫い上げる人もいますが、組み合わせにフォーカスする人と完全自作の間には、既製品に装飾を足したりちょっと手を加えたりするという市場が大きく広がっているわけで、AI以降のプログラミングはそういう着こなしスタイルに寄っていくのは間違いないと思います。</p>
<p>「プログラミングできないとAIの成果物の評価なんてできない」といった言説は早晩聞かれなくなるかと思います。「プログラミングができるから、一部こだわりをもってパフォーマンスの高いコードが書けた」みたいなプラスアルファの技能としてプログラミングが変わってくるんじゃないかと思います。</p>
<h2 id="AIでしか学びにくいスキルもある">AIでしか学びにくいスキルもある</h2><p>「プログラミングできない人には良い設計は書けない」みたいなことがたまにXで話題になります。個人的にはそれはもう過去の話になると思っています。確かに過去には言語とかフレームワークの表現力と合わない設計を無理強いして現場が困る、みたいな例もあったかと思いますが、AIを活用した設計は決してその言語の詳しい知見がなくてもまあまあできてしまうのですよね。自分が知らない言語のコードを生成AIに書いてもらった人はもう気づいていると思いますが。ただ、だからと言って何にも技能がいらないかというと、システム全体を適切な塊に分割したりそもそも業務の流れをシンプルになるように整理したりといった、分析・設計のスキルは必要です。また、ファッションセンス的な技能が重要になります。</p>
<p>今までは複数人で開発するような大きな案件に入る、アーキテクトの人にレビューしてもらうという観点でしかなかなか一人では身につけにくかったそのようなアプリケーション設計スキルがAIとやりとりしながら身につけられる、というのは良いことかなと思います。これまでは下積みを積んで何年目かになってから仕事をやりながら身につけるような能力ですね。あとは、うまく情報を整理してAIに伝える言語化力とか、サボろうとするAIを働かせるマネジメント法とかも。</p>
<p>できなくなる経験とかに目が向きがちですが、逆に、身につけられる技能がいくつもあります。</p>
<h2 id="あえて勧めるとしたらGitHub-CopilotとKiro">あえて勧めるとしたらGitHub CopilotとKiro</h2><p>さて、AIを使いつつ設計などを身につけつつプログラミングもプラスアルファで身につけていくにはどうしていけばいいかと言う最初の問いですが、個人的にはトークン数が限られている環境でAIを使ってみると言うのが良いかなと思っています。ある程度枠組みを作ってもらってから中身の実装を自分でやる、というステップでやるにはちょうど良いかと思います。フルに稼働して上から下まで全部書いてもらおうとすると月のクオータ制限にひっかったりします。難しいところはどこか自分で書けるところはどこかを考えながら必要なところだけAIにやってもらうとかそんな使い分けをすると安価なプランでも月末まで使い切ることなく十分に使えるかと思います。</p>
<p>おすすめは以下の2つです。</p>
<ul>
<li>GitHub Copilotの$10&#x2F;月のProプラン</li>
<li>Kiroの$20&#x2F;月のProプラン</li>
</ul>
<p>CopilotのProプランは比較的安価ですが、それに加えてGitHub Educationに登録すると無料になります。OpenAI、Anthropic、Googleなどのいろんなモデルが使えると言うところが良いですね。いろんなモデルを試食してみたい時にも良いです。あと、0xと書かれているモデルは利用量にカウントされないで使い放題なので、使い切ってもちょっとドキュメント整理とかシンプルなタスクはお願いできたりするところも良いです。</p>
<img src="/images/2025/20251208a/スクリーンショット_2025-12-01_9.43.24.png" alt="スクリーンショット_2025-12-01_9.43.24.png" width="288" height="498" loading="lazy">

<p>Kiroは使い切ったら無料の救済措置はないのですが、SteeringとSpecによる仕様駆動開発は、要件を整理してレールにのっとって開発ができるという点ではアーキテクト経験がなくてもシステムを作るガイドがしてもらえるという点ではかなりやりやすいですね。以下の記事で発表してきましたが、当時と比べてSonnet-4&#x2F;4.5デコーディング能力アップ(当時は3.7&#x2F;4だった)のと、軽いタスクを任せられる軽量モデルの3倍使えるHaiku-4.5が追加されたのと、Amazon Q Developerと呼ばれていたCLIツールが統合されたり進歩しています。</p>
<ul>
<li>Kiro Meetup Japan #1で発表してきました</li>
</ul>
<p>Kiroの使い方はAWSのKiroを使うハッカソン「Kiroween」に参加してみたでも紹介しています。</p>
<h2 id="まとめ">まとめ</h2><p>生成AIが出たあとに言われる「時計の針は戻らない」という言葉ですが、この時代にあった教育とかそういうのをみんなで一緒に考えていく必要があるのは間違いないです。少なくとも現在ITで仕事をしている年長者が今後のAIを使った開発について考えると同時に若手の育成も一緒に知恵を絞らないといけないのは間違いないです。11&#x2F;30に参加した技育展でも教育関係のプロジェクトが2つあり、対象はプログラミングと学校教育で違うものの、AIを解答を教えるのではなく考え方を身につける目的で使うみたいな内容でした。若手側からも危機感があってそこに取り組んでいる人たちもいるので、お互い知恵を出し合って良い教育について考えていけたら、「AIのせいで・・・は杞憂だったね」となるのでは、と思います。</p>
<p>悪いオタク的姿勢あるあるなんですが、「自分が学んできたのと同じものを実現する」というのにこだわる人は一定数います。自分の苦労した下積みがあるから若者も10年は泥水をすすってがんばれ、みたいな。あとは自分がウルトラマンティガ&#x2F;初代ガンダムから順番に見てきたから、最新作を見るにはお前らもその順番に履修しろ、だって前作のアレが出るじゃん（なぜか履修と呼ぶ）、みたいな。自分と違う立場で考えるのを放棄した考えなのでそういうのは無視していけばいいかと思います。</p>
<p>とりあえず、手軽に始められる学習手段ということで利用制限枠が小さい契約でAIを使ってみるというのを紹介しました。少なくとも今まで数ヶ月とか年単位でかかって動くものがようやく作れた、みたいなところからだいぶ時短で動くものは作れて、とAI以前よりはアウトプットも出しやすくなっています。より短期間で、今まで身につけられなかったスキルもセットを高速で身につけられるような、そんな時代はつくれると思っています。僕も今まで開発してなかった言語でコードを書いてみたり、高速に学習できているなというのを実感しています。</p>
<p>ともすればAIが出てきたからジュニアな開発者はいらない、という話も聞こえてきますが、しっかりと理解してスキルを持っている人の必要性はゼロにはならない（そうじゃないとAIのコードの質の評価ができない）です。コーディングスキルの必要性は下がるかもしれませんが、マネジメント的なスキルを最初から要求されて身に着ける流れにはなるはずです。きちんと新卒を採用して育てていく、というのはIT業界の継続発展には必要だと思っていますし、それを以前からきちんとやっているからこそ僕もフューチャーを選んだ、ということもあります。大きな変化の荒波ですが、みんなで知恵を絞っていきたいですね。</p>
]]></content>
    <summary type="html">技育祭でも聞かれた「AI時代にどうやって経験を積んでいけばいいのか」というテーマですが、僕はパソコン通信やインターネットがない時代にプログラミングを始めました。まあ当時の話を書いても若い人とは環境が違いすぎるので、細かいことは書きません。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="Copilot" scheme="https://future-architect.github.io/tags/Copilot/"/>
    <category term="Kiro" scheme="https://future-architect.github.io/tags/Kiro/"/>
    <category term="技育祭" scheme="https://future-architect.github.io/tags/%E6%8A%80%E8%82%B2%E7%A5%AD/"/>
    <category term="生成AI" scheme="https://future-architect.github.io/tags/%E7%94%9F%E6%88%90AI/"/>
  </entry>
  <entry>
    <title>AWSのKiroを使うハッカソン「Kiroween」に参加してみた</title>
    <link href="https://future-architect.github.io/articles/20251203a/"/>
    <id>https://future-architect.github.io/articles/20251203a/</id>
    <published>2025-12-02T15:00:00.000Z</published>
    <updated>2025-12-02T15:00:00.000Z</updated>
    <author><name>澁川喜規</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20251203a/スクリーンショット_2025-11-22_8.19.49.png" alt="" width="1200" height="745">

<p>生成AI搭載の新しい開発環境が毎月のように登場したりしていて、大盛り上がりの中、Kiroを使ったハッカソンが行われていたので参加してみました。AWSのCCoE担当の方から紹介を受けたので社内に共有すると共にせっかくなので自分でもやってみました。</p>
<p>Kiroは、アイコンのかわいさなら圧倒的覇権、グッズが欲しいと言われる生成AI開発環境で、VSCodeクローンをベースにspec drivenなAIエージェントが搭載されている開発環境です。無料期間にがっと使ってみてそれを元に登壇もさせてもらいました。今回は利用量無制限ではないキャップのある状態できちんとしたものを作るのハッカソンということで、当時とはまた違った感覚で楽しめました。</p>
<ul>
<li>Kiro Meetup Japan #1で発表してきました</li>
</ul>
<p>KiroweenはPro+相当のクレジット(2000)が無料で提供され、その中で開発するというものです。お化けアイコンにちなんだ、名前の通りハロウィンをモチーフにしていて、カテゴリーは以下の4つ。</p>
<ul>
<li>リザレクション：お気に入りの死んだテクノロジーを生き返らせます。時代遅れの技術を今日のイノベーションや明日の問題の解決で再考しましょう。</li>
<li>フランケンシュタイン：テクノロジーのキメラを1つのアプリにつなぎ合わせます。一見互換性のない要素をまとめて、思いがけず強力なものを構築します。</li>
<li>スケルトンクルー：スケルトンコードテンプレートを作成し、明確で柔軟性があり、さまざまなユースケースをサポートできます。あなたの基盤から2つの異なるアプリケーションでその汎用性を私たちに示してください。</li>
<li>コスチュームコンテスト：どんなアプリでも構築しますが、洗練され、忘れられない忘れられないユーザーインタフェイスを見せてください。アプリの機能を強化する不気味なデザイン要素を取り入れましょう。</li>
</ul>
<h2 id="作ったもの">作ったもの</h2><p>次の場所にソースコードやビルド済みパッケージなどがあります。一応インストールしなくても試せるようなプレイグラウンドもあります。コード規模としては6万行ぐらいですかね。</p>
<ul>
<li>リポジトリ</li>
<li>VSCodeマーケットプレース</li>
<li>OpenVSX (KiroなどのVSCodeクローンのエディタ向けのプラグインリポジトリ)</li>
<li>ブラウザ用のプレイグラウンド</li>
</ul>
<img src="/images/2025/20251203a/スクリーンショット_2025-11-22_8.31.16.png" alt="スクリーンショット_2025-11-22_8.31.16.png" width="1200" height="543" loading="lazy">

<p>リザレクション部門で作りました。最近の推しのDFD[1] [2] [3]を復活させるぞ、ということで、ウェブフロントエンドのソースコードを渡すとその構造をDFDっぽく可視化するツールです。VSCode拡張で作り始めましたが、審査用にウェブのリンクがあってウェブで見られる必要がありそう？ と思ってGitHub Pagesで動くバージョンも追加で作りました。後から見たら実際にはなくても良さそうでしたが・・・まあブラウザ版はデバッグに便利でした。</p>
<p>ハッカソンなのでだいぶ割り切りで機能を絞っており、Reactは関数コンポーネント、VueはVue3のscript setupのSFC。SvelteはSvelte5に限定しています。Reactは人気のライブラリ類もカバーするようにしています。Vueは3のscript setup方式、Svelteは5の文法飲みサポートで、Vue Router, Pinia、Svelte Kit、Svelte Storeの公式のライブラリをサポートしてます。</p>
<p>今回はほぼコードは見ておらず、指示だけに徹するスタイルでやりました。途中でなんどか大きなリファクタリングなどは発生しましたが、最終的には結構質が高くて便利なプロダクトになったんじゃないかと思います。</p>
<h2 id="スペック駆動を誤解していた">スペック駆動を誤解していた</h2><p>Kiroといえば特徴的なのがこちらのスタートアップ画面ですね。VibeかSpecか。Specを選ぶと、要求のヒアリングが始まり、設計、計画が立てられてそれを元に実行するというものです。スペックは「仕様」だし、以前使った時にはここに使う技術からなにからじっくりやり取りをしながら煮詰めて入力していました。</p>
<img src="/images/2025/20251203a/スクリーンショット_2025-11-22_8.27.12.png" alt="スクリーンショット_2025-11-22_8.27.12.png" width="664" height="428" loading="lazy">

<p>ただ、本来はここはsteering&#x2F;hooksが担う部分も大きいのではということで、今回は作りたいものの青写真やアーキテクチャなどはsteeringに入れました。hooksは迷ったのですが、最近の開発ツールは優秀だし、下手にコミット時にAIでコードをチェックみたいなのを入れるとクレジットを無駄うちしそうなのでwatchでチェックを実行ぐらいにしておきました。これならクレジット使わない。</p>
<img src="/images/2025/20251203a/スクリーンショット_2025-11-22_8.28.05.png" alt="スクリーンショット_2025-11-22_8.28.05.png" width="448" height="623" loading="lazy">

<p>で、スペック駆動は何かというと「作業指示」ある程度まとめて作業をお願いしたい単位でspecを作るのが良いかと思いました。プロダクトのビジョン(どんなシステムなのか)はsteering、外部設計(どんなモジュールで構成するか)は脳内でやり、スプリントゴール(検証可能なユーザー価値)単位でspecかなと。あるいはビッグリファクタリングもspec。tasks.mdになっていると、コンテキストの管理はKiroがやってくれます。実装が終わった後にVibeモードでちまちまやりとりすると「コンテキストが切れた」と言われがちですが、specでの作業はそこも自動でやってくれるのでまとめて大きめの作業が依頼できて完了まで丸投げできます。11&#x2F;20に1100クレジットを一気に使ってVue対応とSvelte対応をまとめて初めて完了できたのもこれです。</p>
<p>今回の実装や、これまでCopilotやCodexも使って学んできたのをパターンとしてまとめたのがこちらのエントリーです。</p>
<ul>
<li>ラジカルAIプログラミング</li>
</ul>
<h2 id="Kiroバージョンアップ">Kiroバージョンアップ</h2><p>Kiroそのものも以前触った時から大幅に良くなっていますね。使えるモデルがSonnet-4.5となって、途中でパニックになってファイルを丸ごと消すということは完全になかったですね。こまめにコミットしておくというのは今回しなかったけど大丈夫でした。</p>
<p>あと、途中から利用クレジットが1&#x2F;3の低コストモデルのHaiku-4.5とOpus-4.5が追加されました。細かいデバッグとかはこちらに任せても良いかもしれません。今回は動くものを早く作るの重視でSonnet-4.5でほぼやりましたが。なお、Opus-4.5は、ライブラリのリポジトリ側にアクセスしにいって「まだ未解決の不具合と関連があります」とか言いながら回避策を実施してくれたのでかなりレベルが高いです。人間だと1週間とかかかりそうなものを1時間で。Opus-4.5はだいたいSonnet-4.5の2倍、Haiku-4.5の5倍強のコストですが、Opus-4はVSCodeでは他のモデルの10倍だったので、だいぶ安いです。</p>
<img src="/images/2025/20251203a/スクリーンショット_2025-12-02_18.10.05.png" alt="スクリーンショット_2025-12-02_18.10.05.png" width="381" height="255" loading="lazy">

<p>また、Q Developerと統合されて、Kiro CLIとなりました。こちらと併用というのも良さそうですが今回はそれを試すのは時間的にちょっとできませんでした。</p>
<h2 id="めちゃくちゃ大事なクレジット管理">めちゃくちゃ大事なクレジット管理</h2><p>参加前から付与されていた1000クレジットの残り600と、今回付与された2000クレジット、12月の追加無料50クレジットでの参加となりしました。初アカウント作成な人は500の追加無料クレジットと、無料の50クレジットの11月&#x2F;12月分で合計2600となるので大体一緒ぐらいのリソースといえますね。仕事しながらですがだいたい次のようなスケジュールで作りました。</p>
<ul>
<li>11&#x2F;8: エントリー。MCPとかsteeringとか開発環境周り整備</li>
<li>11&#x2F;11: シンプルケースでだいたいのMVPが見えてきた。階層表現ができないので可視化をVis.jsからMermaid.jsに切り替え</li>
<li>11&#x2F;14: Reactの基本機能を実装完了。ウェブのPlayground作成開始</li>
<li>11&#x2F;17: Reactサードパーティ対応実装開始。受け入れテスト追加</li>
<li>11&#x2F;20: Vue対応実装、Svelte対応実装</li>
<li>11&#x2F;21: デバッグ(残り0.5)</li>
<li>12&#x2F;2: 12&#x2F;1に付与された追加クレジット50でデバッグ(残り20)</li>
</ul>
<p>消費量的には以下のような感じですかね。</p>
<ul>
<li>Reactの基本機能で500クレジット程度</li>
<li>デモ用ウェブサイト版で200程度</li>
<li>Reactサードパーティ追加で500程度</li>
<li>Vueで500</li>
<li>Svelteで500</li>
</ul>
<p>ぐらいのトークン消費であとはデバッグですね。</p>
<p>特にハッカソンだとクレジット管理がかなり大切ですね。そもそも動かないとか、プロジェクトが完了しないとかそういうリスクを避けるためにどのような手順で開発をしていくかの作戦が大切ですね。今回は…</p>
<ul>
<li>コアとなる機能を先に作る</li>
<li>終わらなくても問題ないような機能はプラグイン的に切り離して後回し</li>
</ul>
<p>…という作戦でいきました。世界では圧倒的人気のReactをまず最初に。その後は金太郎飴みたいにどこで切られても何かしら実用になるプロダクトは残せるようにオプショナルな要素を並列追加できるようなアーキテクチャや受け入れテスト態勢を早めに作り上げました。まあ実際にはやりたいと当初思っていたところまで完了したので結果としては作戦は不要だったもいえますが、コアができているとだいぶ心の余裕があります。</p>
<p>あと、デバッグでも時間とクレジットは取られたので、早めに受け入れテストをやっていくのは大事です。Kiroのスペックだとだいたい10項目に分かれるスペックだと残り3つぐらいがテストとかドキュメントのタスクなりますが、最小限の実装ができたら手元で動かして見て方向修正が必要か見極めると。</p>
<p>このあたり、完全にチームリーダーとかテックリード的な動き方が必要とされます。作戦間違えて最後まで完成しないかもしれない！ みたいな状態になると1人でも病む可能性すらありそう。作戦大事です。</p>
<h2 id="まとめ">まとめ</h2><p>Kiroweenは参加して良かったです。なかなか1つのツールを使い尽くすというのはあまりチャンスがないので、やってみると学びが大きいですね。2000クレジットはそこそこの量で、きちんと実用に足る開発ツールを作れました。</p>
]]></content>
    <summary type="html">生成AI搭載の新しい開発環境が毎月のように登場したりしていて、大盛り上がりの中、Kiroを使ったハッカソンが行われていたので参加してみました。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="AWS" scheme="https://future-architect.github.io/tags/AWS/"/>
    <category term="Kiro" scheme="https://future-architect.github.io/tags/Kiro/"/>
    <category term="スペック駆動開発" scheme="https://future-architect.github.io/tags/%E3%82%B9%E3%83%9A%E3%83%83%E3%82%AF%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA/"/>
    <category term="ハッカソン" scheme="https://future-architect.github.io/tags/%E3%83%8F%E3%83%83%E3%82%AB%E3%82%BD%E3%83%B3/"/>
  </entry>
  <entry>
    <title>ラジカルAIプログラミング</title>
    <link href="https://future-architect.github.io/articles/20251127a/"/>
    <id>https://future-architect.github.io/articles/20251127a/</id>
    <published>2025-11-26T15:00:00.000Z</published>
    <updated>2025-11-26T15:00:00.000Z</updated>
    <author><name>澁川喜規</name></author>
    <content type="html"><![CDATA[<h2 id="目的と定義">目的と定義</h2><p>本ドキュメントは、従来のプログラミング手法の限界を超えた、AI中心のプログラミング手法として、ラジカルAIプログラミングの概念を定義する。</p>
<p>2002年ごろにブームを起こしたeXtreme Programming (XP) は「昔からあるベストプラクティスを極端(eXtreme)なレベルで適用する」という思想で作られた。コードレビューが有効なら常にペアで開発する。テストが有効なら全てのコードでテスト書いてから実装する。ユーザーとの対話が必要ならいつでも会話できる場所にユーザーを座らせるといった具合である。それと同じようなチャレンジをAIを使った開発でもやってみようというのをモチベーションとして自分がAIを使った開発を半年ほど行って得た経験をもとに作成した。RadicalはeXtremeとは違う単語だが似たようなクレイジーさを表現できる単語として選んでいる。4つの価値と12のプラクティスなど、XPのオマージュとして作ったものではあるものの、eXtremeをそのまま拝借するのは陳腐なので避けた。</p>
<p>生成AIを活用するにあたり、オーガニックに行ってきた開発プロセスをAIに模倣させるというのはスタートとしてはやりやすいやり方だが、ツールなどが発展するにしたがってそこから離れていくと予想される。そこで既存のやり方を完全にアンラーニングしてリセットし、全く新規で開発プロセスを考えるとしたらどうかというのを思考実験しつつ、さまざまなエージェントで検証しつつ書いているのが本エントリーである。最終的には現在のオーガニックな開発とこのエントリーの中間点のどこかに落ち着くと思われるが、考えられうる極北を考えるのがこのエントリーの趣旨である。</p>
<p>ある程度モデルの性能は今後も伸びていき、ツールの機能が向上する前提で書いている。現時点で「こういう指示をしたらうまくいった」といったテクニック的な内容はモデルの変化で変わる可能性が高い。なるべくそういうのは排除している。また、マルチエージェントといった、具体的すぎるツールの形態には依存しないような、ジェネリックな内容となるようにしている。想定以上の発展があった場合にはここに書いてある内容も変化を受けるかもしれないが、その時が来たときに更新することを考えている。多くのエージェントがTODOリストを表示しつつ作業するなど、以前は指示が必要だったことも何も指示せずに行うように進化している。どのように手を動かせば良いかというHOWに対する指示は今後はどんどん不要になっていく想定で書いている。</p>
<p>これは個人として考えたパターンランゲージ集であるが、もちろんこれとは違う思想にたどり着いた人もいると思う。そういうのは是非フィードバックして欲しい。</p>
<h2 id="4つの価値">4つの価値</h2><h3 id="1-継続的学習">1. 継続的学習</h3><p>AIと人間がお互いの理解を更新し続ける「共進化」そのものを開発の中心価値とする。</p>
<p>AIモデルにとっては、人間から提供されたテキストやログなどを元に人間が期待するものを学習しアウトプットを出す活動がAIによる開発のAI側から見た視点である。いかに効率よく学習するかが鍵となる。人間側はAIがどのように理解しているのかを学び、それに対して軌道修正をさせることが主な活動になる。言い換えると、AIを使った開発はAIをいかに学習させるかというゲームである。</p>
<p>どんなに完璧な要件が頭にあったとしても、それを間違いや誤解を生まないテキストにしてAIに渡すというのは大変時間がかかるプロセスである。もちろん、完璧な要件があるというケースはまれである。その場合は多くのステークホルダーと調整などが発生する。巨大なシステムはそれだけでも年単位でかかる。アジャイル方法論やDDDに登場する、1人で要件を判断できるドメインエキスパートなどは存在せず、何人も手分けをしてヒアリングして話を統合し、1つの要件としてまとめる必要がある。</p>
<p>完璧な計画を作りそれを元に一発で自動で望むものができる、というのはそもそも完璧な計画を作るリードタイムが無駄となりボトルネックになる。その完璧な情報を用意する前に、断片的でもどんどんAIに情報を与えて学習をスタートし、その知見をsteeringドキュメントなどの形でプロジェクトの知見、あるいはspecとして活用可能な形に変換していくことが大切である。人間が情報を言語化する裏でAIがコードを書き、と人間とAIが並列かつインクリメンタルに作業を進めていくことが効率アップには不可欠である。</p>
<p>また、AIが大量の文章やプログラムを生成するのが得意といってもそれを人間が読んで理解して判断するのには時間がかかる。高速に生成されたプログラムを判断して判断を下すというのも時間がかかる。どんなに大量に並列度を上げても、その作られたプロダクトの検収を上げるところが最後のボトルネックになる。人間側の学習の効率も考慮が必要である。間違っても、AIが作成する文章やソースコードをユーザーに渡すのは間違いであるし、AIから出てくる部分のアウトプットをもって生産性を測るのは間違いである。</p>
<h3 id="2-ビジョン駆動">2. ビジョン駆動</h3><p>AIを使って開発すると、従来よりもHowの比率が劇的に下がり、What、そしてWhyをいかに明文化して伝えるかが大切になる。プロジェクトのビジョンをより明確に持つほどに各エージェントをスムーズに利用できる。</p>
<p>ビジョンは今までも大切といわれてきたが、あくまでも企業理念のような少し浮ついた概念で、実際の普段の業務のKPIとの差などはあった。リーダー陣は別として、多くの作業者はHowを中心に考えてきたからである。しかし、生成AIにより文章やコードの生成は劇的に低コストで行えるようになったことで、重要度の比重が変わった。</p>
<p>以前は実装というのは時間がかかるし、手戻りがあるとさらに余計な工数がかかる作業とされた。そのため、フロントローディングでしっかり設計を作り込むことであと工程の手戻りや複数人の作業の結果の統合で時間を無駄にしないようにしてきた。アジャイル開発もバグによる手戻りなどを防ぐためにテスト駆動で開発するなどしてコードを書き、ボトムアップでパーツを組み上げてシステムを作っていた。</p>
<p>AIの開発になると、「何を実現したいか」「今後の発展の方向性はこうだ」というのを明確にすることで、HowはAIがカバーしてくれるようになる。その一方で、どの言語やフレームワークを使い、どのようなアーキテクチャにしていくかといったHowの比率は大きく下がる。フレームワーク切り替えの負担を減らすために、手間暇をかけてクリーンアーキテクチャを採用して切り替え時のコストを下げるといったことは考える必要性が減る。フレームワークを切り替える、データベースを別の製品にする、別の言語で再実装と言ったことが可能になる。これは今まで不可能であったプロトタイプを完全に捨てて、実装順を整理した上で再実装する、といったことも可能になる。</p>
<h3 id="3-品質と境界の再定義">3. 品質と境界の再定義</h3><p>AIと人間では成果物のスピードが異なるため、作業フローだけではなく品質などの基本的な考え方が大きく変化する。</p>
<p>1つ1つ、モジュールの完成度を上げて組み上げていくというのがウォーターフォールでもアジャイルでも行われてきたことである。だが、そもそもモジュールをどう分割して作るのかというのを人間が判断して指示をするとなると人間のスループットが落ちてしまう。なるべく細かい判断を人間が行わないことが効率の最大化には必要である。</p>
<p>そうなるとモジュール境界などは明確に白黒ついたものはなく、追加される要件によって多少の変動がありえる。それは品質が安定していない、低品質ということになるが、それを許容することでAIに任せられるレベルが1段アップする。モジュールの境界や責務がはっきりしていないということは品質があまり高くないということであるが、その分AIの物量でカバーできる。周りのモジュールも同時並行で設計・実装をし、ある程度方向性が定まったところで完成度を上げる、というのが必要である。</p>
<p>実際に蓋を開けてみると、条件分岐がベタ書きの冗長なロジックの可能性がある。そういうのも動作しているのであれば許容し、時間的な余裕を動作可能なプログラムによって稼げ、より品質を高めるのが重要であると判断した段階でそれを改めて手実装するなり、リファクタリングを行うなどする。AIをフル活用し、ボトルネックが実装以外のところに移動したところで稼いだ時間で品質を確保するのである。</p>
<h3 id="4-流動的な構造">4. 流動的な構造</h3><p>仕様・モジュール境界・フレームワークなど、構造を「固めない」こと自体をパフォーマンスの源泉となる。</p>
<p>AIにおける開発では仕様だけではなく、モジュールの境界、選択したフレームワークなどすべてが流動的である。うまく行かなければ、アドホックに部分的に仕様駆動開発を開始して、それまでの設計を再構成してプロトタイプを丸ごと作り変えることもある。</p>
<p>これまでのオーガニックな開発は人間が手を加えて行なってきた。人間が手を加えるまでシステムが変わることはないし、人間の手は早くないため、効率よく少ない工数システムを作るためにみんな知恵を絞ってきた。再利用性という言葉は長らくソフトウェア業界でマーケティング用語としてよく使われてきた。共通部分をモジュール化して使い回したり、既成の小さい硬い部品を組み合わせてシステムを作ろうとしてきた。その試みはうまく行ったり行かなかったりした。変更が少ないのが価値である。</p>
<p>AIは補間（Interpolation）は得意であり、外挿（Extrapolation）は比較的苦手といわれる。AIにおいては「事前に仕様や要件を固める」「ボトムアップで作り上げる」のは外挿にあたるため、精度があがらず、結果的にコストとなる。前後の枠を決めてその中で自由に要件に合わせてフレキシブルな開発の方が得意となる。中途半端に固く作ろうとすると、モジュール境界を人間が監修して設計する必要があり、人間の事前設計のリードタイムが長くかかり、AIのパフォーマンスは発揮できなくなる。</p>
<h2 id="12のプラクティス">12のプラクティス</h2><h3 id="1-モデルオーケストレーション">1. モデルオーケストレーション</h3><p>AIエージェントには様々なモデルがある。計画が得意な高性能モデル、コーディングに特化した高速なモデルなどである。将来的にはテストに特化したモデル、クラッカーの思考を模倣して侵入的テストを行うモデルなどさまざまなカスタムのモデルが出る可能性もあるだろう。それぞれの得意領域を組み合わせることで低コスト（コンテキストやクレジットの面で）かつ高速にタスクが推進できる。</p>
<p>人間も1つのモデル／エージェントと考えるべきである。実装速度は遅いし、働き続けられないし、欠点ばかりである。ただ、対外ヒューマンインターフェースとしては他のAIには真似できない（生成AIは指示して初めて動く受動的な存在である）だろうし、AIが不得意な外挿的な発想でどのような方向性でシステムを作っていくかのグランドデザインは人間が行う。また、実装速度は遅いがどうしてもテキストでは表現しにくい微妙な高難易度のアルゴリズムの実装などのタスクを行なったりもある。</p>
<p>AI同士を組み合わせることでボトルネックを減らすということは今後も行われるが、人間もその一部であり、人間もボトルネックにならないようにしつつ、対外的な活動によってボトルネックを取り除くと言った活動をする必要がある。</p>
<p>時にはAIではうまく実装できない案件を人間が肩代わりする、人間が怒っていた確認作業をAIが行うといったように、モデルの選択も流動的に変化する。</p>
<h3 id="2-マクロマネジメント">2. マクロマネジメント</h3><p>人間とAIは1つの組織となる。組織のアウトプットを増やすにはマイクロマネジメントではなく、移譲がポイントである。マイクロマネジメントをすると、1+1が1.2とかにしかならず、なおかつ、マネージャロールの人が見られる人数をあまり増やせなくなる。マクロマネジメントでは、1人のマネージャが見られる担当者の数を増やし、並列度を上げ、全体のスループットを向上させる。</p>
<p>マクロマネジメントを行うには、なるべく指示を減らす必要がある。生成AIが持っている素のコーディングのスタイルをなるべく生かし、細かいところはすべて移譲する。コーディング標準やテスト駆動開発をするかどうかなども、モデルに任せる。幸い、AnthropicのSonnet-4.5やHaiku-4.5、OpenAIのGPT-5-Codexなどはコーディングに特化したモデルとして、従来のモデルよりもコーディングの知識があり、細かい作法も指示する必要はない。また、TODOを作って開発させるなども、最近のモデルでは指示しなくても行ったりもする。</p>
<p>タスクレベルの細かい作業指示などは状況に応じてどのistructionを読み込ませるかなどのマイクロマネジメントを必要とする場合がある。また、その分、コンテキストを消費することになる。モデルごとの特徴を考慮してモデルの使い分けなどを行う。外挿にあたるような、未知の領域の最初の実装などはどうしてもAIによる予測の正確性が上がらない場合があり、その場合も人間によるマイクロマネジメントが必要となる。</p>
<p>何をやるかの共通理解は必要であるため、外部設計レベルのドキュメントは必須とする。ユニットテストは生成AIエージェント自身が必要だと判断して作るのにまかせる。テストというものはそこの断面で品質のチェックを行うものであるが、そもそもテストを自動で作らせるのであれば、その仕様について人間の意志は入らないため、形としてユニットテストを行わせることは意味がない。静的型の定義なども全て任せる。また、人間にとって解読困難であることを許容する。あくまでも後述の受入テストで品質は確保する。</p>
<p>この信頼と移譲に必要なのは最低限2つのドキュメント群である。</p>
<ul>
<li>instruction<ul>
<li>ADR(architecture decision record)とも呼ばれる。またKiroはSteeringと呼んでいる</li>
<li>どのようなシステムか、どのような言語を使うか、どのような環境を想定するかなどの大雑把な指示</li>
</ul>
</li>
<li>spec<ul>
<li>機能ごとの外部仕様書。お互いのspecは独立している。多くのAIエージェントは追加で必要な知識をリンクの形で表現できるが、漏れなくリンクを張り巡らせるよりかは共通知識をinstructionとして切り出す方が低コストだと思われる</li>
</ul>
</li>
</ul>
<p>ソフトウェアとしては以下のチェックツールを利用する。</p>
<ul>
<li>受入テスト</li>
<li>Linterなどの静的解析ツール</li>
</ul>
<p>利用する技術も、AIが良く知識を持っている言語やデータベースなどを選ぶようにする。こだわりがない限りは独自ツールなどを新規に作りこむ必要性は減っていくと思われる。多くの人が独自にAI向けにツールやinstructionを作りこむという状況はタコつぼ化をもたらすが、より効率の良いツールが登場すればそれが一般化するはずである。</p>
<p>ただし、高難易度のタスクで、生成AIに丸投げしてもうまくいかないというケースがある。その場合はマイクロマネジメントに切り替え、細かく内部仕様レベルのdesign documentを作成し、伴走しながら実装するものとする。</p>
<h3 id="3-受入テスト駆動開発">3. 受入テスト駆動開発</h3><p>開発サイクルのチェックポイントは、静的なテストと、受入テストのみとする。受入テストはなるべく自動化をする。あるいは生成AIにシナリオに従って実施してもらう。受入テストはAIが得意な補完としてタスクを定義することにもなる。</p>
<p>受入テストは事前に作らせる必要はない。まずいったん一通り入力から出力までつながるようなコードが完成してから、その入力に対してアウトプットが何になるか、というペアをもとにテストを行う。ユニットテストとは異なり、人間が知覚できるレベルでの入力と出力のペアでテストを行う。例えば、ウェブ画面でのテストはバックエンドAPIでいったんテストを行うというのが主流であるが、画面と裏のDBのみを見て、途中のAPIなどは不要であれば見る必要はない。</p>
<p>受入テストはオーガニックプログラミングにおけるユニットテストに相当する品質ゲートになるため、バリエーションの網羅などはきちんとここで行う。またエラーケースの検証も行う。どのバリエーションに対応するか、最初に網羅して作り切ることが大切。</p>
<p>テストそのものは人間がすべて手で作る必要はなく、やり取りしながらAIに作成してもらう方法でもよい。テスト項目だけ決めて実際に作らせ、実際の出力を見て問題なさそうと判断してからそれを期待値としてコピーして保存するのもよい。ユニットテストの場合はテストケースは手で作成できるレベルのものが多いはずであるが、受入テストの場合は実行結果がファイルだったり大きくなることがある。そのような場合にはテストケースに従った出力をさせ、問題ないか目視確認したうえで、それをお手本とする。</p>
<p>一方で、ユニットテストはAI中心開発ではほとんど不要である。モジュールの単位では補完的なAIの特性を生かしたコーディングを進める方法を提供するが、そもそもどのようなモジュールがあればシステムが組みあがるのか？というボトムアップのモジュール分割設計は外挿的な思考を必要とする。モジュールの境界や役割がどんどん変化する中にあって、ユニットテストのカバレッジを計測してテストが通るように守らせるのは無駄にクレジットを消費してAIのフットワークを落とすことになる。AI自身が自発的に作る場合にわざわざ止める必要はないが、マクロマネジメントを行うためにもユニットテストにはこだわらないことが大切となる。</p>
<h3 id="4-光速イテレーション">4. 光速イテレーション</h3><p>アジャイルでは1週間から2週間単位でイテレーションを回し顧客のフィードバックを得てタスクを調整するが、AI開発ではそれを5分、10分の単位で行ってクオリティを上げていく。</p>
<p>そのためには生成AIには上から下までそろっていて、まずは動くシステムを作らせる。マイクロサービスなどプロセス単位で別れるもの、また分割個所が明確に分かれており、その単位で受入テストが可能なところで分割するが、それ以下のモジュールやパッケージ分割は生成AIに任せる。</p>
<p>オーガニックなコーディングほどの細かく分割してユニット単位でユニットテストを書きながら実装して積み上げていく必要があったが、生成AIではそのような手順での実装は不要である。おそらく最初の実装は仕様駆動開発である程度の方向性を持った実行可能なプログラムを作る必要がある。「とりあえずログイン画面」のような小さすぎるゴールを与えると必要なイテレーション回数が増えてしまうため、MVP相当の指示は必要となる。そのため、イテレーションゼロはやや大きくなる。</p>
<p>細かい単位で仕様駆動開発をすると、それぞれのモジュールの結合部分で時間がかかるため、細かすぎる分割指示をしてはならない。コアロジックの複雑さによっては複数に分割してコアロジックを固めてから実装したほうが良いこともあるが、最初から細かく分けて考えるのではなく一度プロトタイピングとして実装をして、ブラッシュアップではなかなかゴールにたどりつけないもぐらたたきになってきたと思ったタイミングでコア部分の再実装を仕様駆動開発で行ってモジュールを置き換えていく。</p>
<p>型情報を付けていくというのも過去の話である。生成AIが生成したコードを何度も動かして検証する。オーガニックではボトムアップで品質を積み上げるしか方法がなかったが、なども動かすことによって品質を確保する。</p>
<h3 id="5-フィードバック・ファースト">5. フィードバック・ファースト</h3><p>イテレーションを高速に行う目的はお互いの理解している情報をぶつけ合い、学習スピードを向上させるのが目的である。出来上がったソフトウェアはAIが理解した結果である。それに対して実際に動かしてみせて何がおかしいかを伝えることは、ユースケースの具体例をAIに伝える行為である。</p>
<p>いかに早くフィードバックを返せる体制を作るか最適化するのが人間の役割である。</p>
<p>素早いフィードバックが大切だからといって、小さくモジュール分割を指示してそのレベルで開発を高速に回しても適切なフィードバックは得られない。その分割が正しいか、そのモジュールを組み合わせると期待するシステムになるかどうか、すべては仮説でしかないからである。人間も動くシステムを触れることにより、仮説が正しかったかどうかを人間も学習できる。</p>
<p>もちろん、全てを人の手で行うことはできないため、受け入れテストによるリグレッションの防止は大切となる。</p>
<h3 id="6-プロトタイピング">6. プロトタイピング</h3><p>生成AIのメリットは手が早いことである。生成されたコードは場合によって捨てて作り直すということが常に選択肢として入ってくる。イテレーションの途中で実装が詰まることがあり得る。その場合はその知見をもとに、不要な機能をそぎ落として新しく実装を開始する。生成AIでは実装が高速なため、プロトタイプを捨てることは躊躇する必要はない。</p>
<p>生成AIが生成するコードは冗長なことが多く、オーガニックな実装と比べて3倍程度のコード量になる。まずはプロトタイプを固めたうえで、ベンチマークを取り、そこの実装を手動実装（AIを使う場合も、エージェントではなく、コード補完）に切り替えて効率を稼ぐ方法が良い。</p>
<p>テンプレートは最終的な実装と同じアーキテクチャである必要はない。Pythonやシェルスクリプトである程度固めてから、GoやRustに清書させる方法もある。</p>
<p>生成AIの機能がどれだけ向上しても、「一発で欲しいシステム」ができあがることはない。世の中に既にあるソフトウェアの模倣（たとえばテトリスやぷよぷよ）であればできる可能性が高いが、そのようなソフトウェアを作る価値は基本的にない。ソフトウェア開発者は常に「現在存在しない」システムを作る必要があるからである。「一発で欲しいシステム」を生成AIに作らせるには、欲しいシステムの隅から隅まで事細かに定義されたドキュメントが必要であるが、過去の実績から、そのようなドキュメントは可能ではあっても、かなりの時間を要する。またドキュメントの整合性を隅々まで確保するのも人間が行うのはラジカルではない。そうなると、骨組みになるような機能から少しずつ指示をして作らせることになる。プロトタイプを作って指示をして方向修正をして、というのが開発のメインのサイクルとなる。</p>
<p>なお、それなりの情報を与えて動くアプリケーションを作らせても、細かいユースケースには対応できないコードになっていることが多い。一通り動くようになってから、細かいブラッシュアップを行い、受け入れテストを網羅的に用意して行い、リリースできるようになるには、最初の動く状態から数倍の時間がかかる可能性がある。</p>
<h3 id="7-テンプレート開発">7. テンプレート開発</h3><p>生成AIは類似した機能を量産することが得意であるため、1つの機能をしっかり作りこみ、それをもとに水平展開できるような計画をする。</p>
<p>アーキテクチャとしてはお手本のアーキテクチャがあればそれをもとに実装していく補完系のタスクとなるため、実装効率があがる。細かい実装方法などを逐次指示する必要もなくなる。ただし、定期的にそこから外れていないかは確認が必要となる。難易度が高いコア部分、量産できる部分をうまく分け、マイクロマネジメントか、マクロマネジメント、どちらが適しているかを決定し、実装する。なるべくマクロマネジメントできるような構成を検討する。</p>
<p>また、テンプレートから量産する場合は、1つの機能を増やすたびにどれぐらいの時間で行えるか、どれぐらいのプレミアムクレジットの消費で済むかなどの見積もりもしやすいメリットがある。</p>
<h3 id="8-知識の非永続化">8. 知識の非永続化</h3><p>生成AIを利用すると、ドキュメントの生成も高速に行えてしまうため、ドキュメントを作りすぎてしまうなど、中間生成物が大量になりがちである。不要なドキュメントを作らないように心掛けたり、一時的に作ったドキュメントは削除するなりする。</p>
<p>そもそも生成AIが高速に出力したドキュメントを「読む」前提にしてしまうと、人間の読解スピードがボトルネックになる。生成AIに読ませるにしてもクレジットを消費することになる。</p>
<p>中間生成物が多すぎると、方針転換するときに仕様を追従するために多くの作業が必要になったり、古い情報が残ったりするし、成果物を検索するときにノイズが増えて開発効率を落とす原因となる。また、Sonnet-4.5などのモデルが作成するドキュメントはソースコードが大量に入っており、半分はソースコードのようなドキュメントである。</p>
<p>実装中にマイクロマネジメントが必要になった場合などは細かいロジックを説明したドキュメントを生成させて確認などを行ったりするが、そのようなドキュメントは外部仕様にかかわる記述だけを残して削除する。</p>
<p>ドキュメント同士は矛盾などを検知しあう機能はないため、ソースコードという形が一番望ましい。もし今後セマンティックを表現し相互の矛盾検証などができるようなドキュメントフレームワークが登場した場合はこの前提は大きく変わる可能性はあるが、定理証明系は自然言語のような速度では読み書きできず、人間がボトルネックというところを解消しつつ問題を解決する解にはならないだろう。セマンティックウェブのような記述も同様である。</p>
<h3 id="9-ブラックボックスなインスペクション">9. ブラックボックスなインスペクション</h3><p>最終的にシステムが完成したあとは、ブラックボックスとして他のAIエージェントに解析させながら、実装をベースにドキュメントを作らせる。それにより、生成AIが誤解して理解していたこと、試行錯誤の過程で作られて残った機能などが洗い出せる。AIの学習をアンラーニングし、実際の作り上げたシステムを再学習するということである。</p>
<p>ベースとなった仕様書を読ませることなく、コードだけを見てインスペクション（検収）させることで、実際のコードがどうなっているかを明らかにする。また、内部の細かい挙動や過去の経緯でなくなった機能などが書き込まれたような密度の低いドキュメントが作られるのを防ぎ、人間が読んで価値のあるドキュメントが作れる。</p>
<p>生成AIはコーディングのリスクを下げるために、なるべく既存のコードを変更せずに後方互換性を残そうとするため、不要な関数パラメータ、不要な実行時フラグなどが高確率で残る可能性がある。不要な機能が検知されたら、その影響範囲の調査と削除を生成AIに行わせる。また、Linterの未使用の変数や関数などを検知する機能なども駆使して無駄なコードを取り除いていく。</p>
<p>知識はドキュメントとして保持しておくのではなく、その都度解析して出力させることで、当時あった願望（実装されていない）などを排除して今現在の情報が得る、という方法もあり、その手段でカバーできるか検討する。</p>
<h3 id="10-ログによる対話">10. ログによる対話</h3><p>生成AIと一緒に作業するにあたっては、printデバッグが一番効率が良い。何か動作がいまいちな場合はログを追加することを指示し、そのログを渡してデバッグを行わせる。もともとデバッガ―があっても並列処理などはこの方法でやっていたし、ログの解読と問題個所の特定は生成AIが得意な領域である。並列動作があっても、素早くログの発行元を特定し、問題の解析につながる。そのため、ログを気軽に追加し、終わったら削除させる。または生成AIにどのようなフィルタでログを選別すればよいかを確認し、そのログを取得して出力する。ログこそはAIとシステムの「動きに」について共有するインターフェースである。</p>
<p>そのため、質の高い必要な情報を高密度でログを出力することが必要である。間違ってもJavaのスタックトレースのような情報を、本当の例外以外で出力してはならない。</p>
<h3 id="11-データモデルファースト">11. データモデルファースト</h3><p>生成AIにとっては、JSONなどから画面を生成するほうが得意である。また、アプリケーションの持つデータ構造の変更は簡単でも、運用中のデータの変更は簡単ではない。ソースコードは必要に応じて生成できるものだし、後方互換性さえ維持すれば入れ替えられるが、データ構造はそれよりも変更に弱い。そのため、データ設計は最初にしっかり行い、変更を少なくする必要がある。</p>
<p>また、プログラムの変更がしやすいといっても、マイクロサービスを作り替えや境界変更などは大事となってしまうだろう。生成AIは機能を減らすという指示に対しては後方互換性の維持をしようとしてなかなか思う通りの方向性で作業を進めてくれないことが多い。人間の意思を過去からの積み上げで理解するという現在の仕組みを考えれば、モデルがとてつもなく進化したとしてもこの傾向は大きく変わらないと思われる。</p>
<p>データモデルをきちんと作っておくことで、同一トランザクション内で同時に変更しなければならないデータ群が明確になり、プロセスの境界を置くべき箇所がはっきりする。そのため、マイクロサービスとして切り出すと、分散トランザクションといった、DBがやってくれることをユーザーランドで手作りするという無駄実装が大きく減らせる。またAPIでいくつか追加のデータが必要になったとしても、正しくプログラムで必要なデータが定義されていればDBクエリーの項目とAPIに項目を足すだけで済むはずである。サービス間で情報取得のためのAPI連携など大事にはならない。別テーブルだとしてもシステム上近い位置にあり、JOINやCTEで1クエリーで取得できるはずだ。</p>
<p>サーガパターンは設計レベルの失敗を実装で回避しようとする愚者のパターンである。月次締め後の訂正など、本当に修正が日単位で遅れて同一トランザクションで処理できずに必要なケースというのはありえるが、ウェブで議論されている構成では必要ないと思われるものがほとんである。</p>
<p>APIファーストという言葉もあるが、設計としてはモデルをきちんと考慮してサービス分割を事前に行った上で、コーディングの順序として先にAPIを作るのは問題ないが、設計としてAPIファーストというのはAIとは関係なくありえないだろう。</p>
<p>ドメイン知識はデータモデルの形でなるべく表現しておく。どの順番でデータを蓄積していくのか、ファイル単位のデータをどのように分割していくのか、どのように統合していくのかなど、データの変遷をきちんと設計する必要がある。</p>
<h3 id="12-品質、デリバリー、スコープ、コンテキスト、クレジット">12. 品質、デリバリー、スコープ、コンテキスト、クレジット</h3><p>アジャイルでは品質、デリバリー、スコープは相関関係にあり、それぞれのバランスを取ることを推奨していたが、実際には品質を犠牲にすることはありえないため、スコープとデリバリーの調整をするという教義となっていた。</p>
<p>生成AIを大規模活用するにあたっては、品質も変数となる。最高のコードではなく、AIに任せて作った冗長なコードというのを許容する。これはプロトタイピングによってシステムの検証を早めるための「時間の前借り」である。</p>
<p>コンテキストはAIが活動するにあたって逃れられないものである。巨大なドキュメントを渡すことでパフォーマンスは落ちる。小さく処理しやすいブロックに分割して指示をする必要がある。また、作業中に中断、リセットなどしやすいように、永続化は必要ないが現在のタスクに必要なドキュメントだけを残すようにする。コンテキストが足りなくなってくると、指示した内容が守れなくなり、正しいコードが書けなくなって、ファイルを全削除しては微妙なファイルを作り直すという酔っ払ったような動きになる。現代の技術では限界はあるものの、適切な狭いタスクにフォーカスさせることで長く思考し続けられるようになる。ただし、デバッグをしつづけたりするとやはり途中でコンテキストが欠乏してきたりする。</p>
<p>生成AIの活動には大量の電力が必要である。今のところは月額で決まった量のクレジットが使えるサービスが主流であり、その中で活動する。将来的にクレジットが無限に使えるようになるかどうかは現時点では不明である。メモリ効率の良いローカルLLMと、CPUに内蔵のNPUで十分という時代が来る可能性はあるし、コーディングなど特定のタスクに限定してローカルで行うといったことはそのうちにできるようになるだろう。小さくコアを作り、そのテンプレートを増やしていくなど、どの段階でクレジットが切れても問題なく進められるようにすることが大切である。また、計画は高性能なモデルを、実装はクレジット消費が少なく高速なモデルを、といった具合に効率よく組み合わせることも必要となる。設計の良しあしも、「機能追加に必要なクレジット数が多いか低いか」で見積もれるようになる。</p>
<p>今までの見積もりはどうしても「ある程度の平均的な開発者が実装したとして」「ほぼ同一の構成のタスクに分解して」数値を出さざるを得なかった。またそのためにはある程度タスクを分解して平準化するといった事前の分析が必要であった。タスクを分解するというのはAIを使った開発でも並列で行うために必要であるが、やり方の平準化まではしなくてもおそらく精度が高い見積もりができるようになる。アジャイルでもベロシティといった、別組織では役に立ちにくい近似値、なおかつフィボナッチ数のような1桁の離散的数値などを使っていたが、クレジット数という（モデルごとに違いは出るが）、2-3桁の数値で計測できるようになるため、この分野でも相当な進歩があると思われる。</p>
<p>タスクごとの消費クレジット数は、指示の出し方やアーキテクチャによっても変化しうるため、いままでは定性的だった「よいアーキテクチャ」「よい設計」「よい指示」といった内容に対する議論が定量的に変化していくことが期待される。</p>
<h2 id="まとめ">まとめ</h2><p>XPのようなパターンランゲージ集として、AI中心開発の方法論をまとめてみた。20万行ほどコードを作成してみて得た感覚や、他のAIエージェントを使って開発している人との議論をもとに書いている。新しいモデル、パラメータ数が多いモデルが出たとしても変わらないだろうなというところを予想して書いている。</p>
<p>勢いで書いてみて2週間ほど寝かせて、おおよそ言いたいことは盛り込めたと思うので2025年の感覚のスナップショットとして公開しようと思う。なお、本記事の執筆にはAIは直接的には利用していない（推敲はちょっとしてもらった）。AIの作成する文書はまだ目が滑るような中身が薄い長文となりがちで、読者の時間を頂戴して読んでもらうのに失礼だという考えからである。</p>
]]></content>
    <summary type="html">本ドキュメントは、従来のプログラミング手法の限界を超えた、AI中心のプログラミング手法として、ラジカルAIプログラミングの概念を定義する。</summary>
    <category term="AIDD" scheme="https://future-architect.github.io/categories/AIDD/"/>
    <category term="設計" scheme="https://future-architect.github.io/tags/%E8%A8%AD%E8%A8%88/"/>
  </entry>
</feed>
