TECH PLAY

サイオステクノロジー(Tech.Lab)

サイオステクノロジー(Tech.Lab) の技術ブログ

601

はじめに こんにちは、サイオステクノロジーの小野です。 Kubernetesを利用する中で、yamlファイルの差分を確認することがよくあります。 差分確認コマンドと言えばdiffコマンドが一般的ですが、yamlファイルは行単位ではなく設定単位で比較を行いたいケースが多いので、diffコマンドを使うのが難しいです。 そんな時に役立つdyffというツールをご紹介します。 dyffとは dyffはテキストの「行」ではなく「データ構造(意味)」に基づいてYAMLやJSONファイルを比較するコマンドラインツールです。 dyffは以下の特徴があります。 キーの順序を無視して比較 人間が読みやすい出力形式 ツール連携 dyffインストール方法 dyffのインストール方法について詳しくは 公式のリポジトリ を参照してください。 バイナリファイルを用いてインストールする場合、以下のように実行します。 $ curl -LO https://github.com/homeport/dyff/releases/download/<バージョン>/dyff_<インストール環境>.tar.gz $ tar -xvzf dyff_<インストール環境>.tar.gz $ sudo chmod +x dyff $ sudo mv dyff /usr/local/bin/ Macをお使いの場合はHomebrewでインストールが可能です。 $ brew install homeport/tap/dyff LinuxやMacにdyffの最新バージョンを手軽にインストールしたい場合は以下のスクリプトを使うことが可能です。 $ curl --silent --location https://git.io/JYfAY | bash dyff使い方 例として以下の2つのyamlファイルを比較します。 # deployment-A.yaml apiVersion: apps/v1 kind: Deployment metadata: name: frontend-app namespace: production labels: app: frontend spec: replicas: 2 selector: matchLabels: app: frontend template: metadata: labels: app: frontend spec: containers: - name: nginx-container image: nginx:1.24.0 ports: - containerPort: 80 env: - name: ENVIRONMENT value: "prod" # deployment-B.yaml apiVersion: apps/v1 kind: Deployment metadata: name: frontend-app namespace: production labels: app: frontend team: ui-dev # ラベルが追加されている spec: replicas: 3 # レプリカ数が増えている selector: matchLabels: app: frontend template: metadata: labels: app: frontend team: ui-dev # ラベルが追加されている spec: containers: - name: nginx-container image: nginx:1.25.0 # イメージがバージョンアップしている env: # 環境変数設定とポート設定の記載が入れ替わっている - name: ENVIRONMENT value: "prod" ports: - containerPort: 80 ちなみにdiffコマンドを使って上記のyamlを比較すると以下のようになります。diffでは、ぱっと見でどんな設定差分があるのかわかりません。また、環境変数設定とポート設定の記載が入れ替わっただけだと設定的には差分がないですが、diffは行単位で差分比較するので、差分として出力されてしまいます。 $ diff deployment-A.yaml deployment-B.yaml 7a8 > team: ui-dev 9c10 < replicas: 2 --- > replicas: 3 16a18 > team: ui-dev 20,23c22,23 < image: nginx:1.24.0 < ports: < - containerPort: 80 < env: --- > image: nginx:1.25.0 > env: 25a26,27 > ports: > - containerPort: 80 使い方① yamlファイルの比較 dyffは以下のコマンドを実行することで、yamlファイルAからyamlファイルBはどのように差分があるのかを出力します。dyffはyamlファイルのどこが変更されたか、設定単位で確認することが可能です。したがって、一目で差分を確認することができます。また、記載が変更されても設定が変更されていなければ出力されないようになっています。 $ dyff between deployment-A.yaml deployment-B.yaml _ __ __ _| |_ _ / _|/ _| between deployment-A.yaml / _' | | | | |_| |_ and deployment-B.yaml | (_| | |_| | _| _| \__,_|\__, |_| |_| returned four differences |___/ metadata.labels + one map entry added: team: ui-dev spec.replicas ± value change - 2 + 3 spec.template.metadata.labels + one map entry added: team: ui-dev spec.template.spec.containers.nginx-container.image ± value change - nginx:1.24.0 + nginx:1.25.0 dyffのロゴが鬱陶しい場合はオプションで消すことも可能です。 $ dyff between -b deployment-A.yaml deployment-B.yaml metadata.labels + one map entry added: team: ui-dev spec.replicas ± value change - 2 + 3 spec.template.metadata.labels + one map entry added: team: ui-dev spec.template.spec.containers.nginx-container.image ± value change - nginx:1.24.0 + nginx:1.25.0 使い方②Kubernetesクラスターにデプロイされているリソースと yamlファイルの比較 Kubernetesにデプロイされているリソースとyamlファイルを比較する際に、以下のコマンドを実行することが良くあります。しかし、こちらもdiffコマンドと同様に行単位での比較を行っているため、どこの設定が変更されたのかわかりづらいです。 # deployment-A.yamlがデプロイされているKubernetesクラスターに対して、以下のコマンドを実行 $ kubectl diff -f deployment-B.yaml diff -u -N /tmp/LIVE-2041829305/apps.v1.Deployment.production.frontend-app /tmp/MERGED-4236462828/apps.v1.Deployment.production.frontend-app --- /tmp/LIVE-2041829305/apps.v1.Deployment.production.frontend-app 2026-03-26 18:24:38.226501887 +0900 +++ /tmp/MERGED-4236462828/apps.v1.Deployment.production.frontend-app 2026-03-26 18:24:38.227501925 +0900 @@ -6,16 +6,17 @@ kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app":"frontend"},"name":"frontend-app","namespace":"production"},"spec":{"replicas":2,"selector":{"matchLabels":{"app":"frontend"}},"template":{"metadata":{"labels":{"app":"frontend"}},"spec":{"containers":[{"env":[{"name":"ENVIRONMENT","value":"prod"}],"image":"nginx:1.24.0","name":"nginx-container","ports":[{"containerPort":80}]}]}}}} creationTimestamp: "2026-03-26T09:22:34Z" - generation: 1 + generation: 2 labels: app: frontend + team: ui-dev name: frontend-app namespace: production resourceVersion: "3430046" uid: 9602c096-6931-4278-ba8d-8dbaf9fd2faa spec: progressDeadlineSeconds: 600 - replicas: 2 + replicas: 3 revisionHistoryLimit: 10 selector: matchLabels: @@ -30,12 +31,13 @@ creationTimestamp: null labels: app: frontend + team: ui-dev spec: containers: - env: - name: ENVIRONMENT value: prod - image: nginx:1.24.0 + image: nginx:1.25.0 imagePullPolicy: IfNotPresent name: nginx-container ports: kubectl v1.20以降ではdyffを組み込んで、差分比較することが可能です。デフォルトのkubectl diffよりかなり見やすく設定差分を確認することができます。 $ export KUBECTL_EXTERNAL_DIFF="dyff between --omit-header --set-exit-code" $ kubectl diff -f deployment-B.yaml metadata.generation ± value change - 1 + 2 metadata.labels + one map entry added: team: ui-dev spec.replicas ± value change - 2 + 3 spec.template.metadata.labels + one map entry added: team: ui-dev spec.template.spec.containers.nginx-container.image ± value change - nginx:1.24.0 + nginx:1.25.0 –omit-headerのオプションは上記の-bオプションと同じでロゴを表示しないようにします。–set-exit-codeのオプションは差分が存在した場合は終了コード 1、差分がない場合は 0 を返すようにします。これによって、CI/CDなどの差分確認スクリプトの中に組み込んでも、差分があるかどうかを判定できるようになります。 使い方③Gitリポジトリ内の比較 Gitの過去のコミットを比較する際に、以下のコマンドを使います。Gitでよく見るコミット差分比較の出力です。 $ git log -u commit *** Author: *** Date: *** yamlの更新 diff -git a/deployment.yaml b/deployment.yaml *** @@ -5,8 +5,9 @@ namespace: production labels: app: frontend + team: ui-dev spec: - replicas: 2 + replicas: 3 selector: matchLabels: app: frontend @@ -14,12 +15,13 @@ metadata: labels: app: frontend + team: ui-dev spec: containers: - name: nginx-container - image: nginx:1.24.0 - ports: - - containerPort: 80 - env: + image: nginx:1.25.0 + env: - name: ENVIRONMENT value: "prod" + ports: + - containerPort: 80 Gitのコミット差分比較機能に対してもdyffを組み込むことが可能です。yamlのみですが、見やすく設定差分を確認することができます。 $ git config --local diff.dyff.command 'dyff_between() { dyff --color on between --omit-header "$2" "$5"; }; dyff_between' echo '*.yaml diff=dyff' >> .gitattributes $ git log --ext-diff -u commit *** Author: *** Date: *** yamlの更新 diff -git a/deployment.yaml b/deployment.yaml *** metadata.labels + one map entry added: team: ui-dev spec.replicas ± value change - 2 + 3 spec.template.metadata.labels + one map entry added: team: ui-dev spec.template.spec.containers.nginx-container.image ± value change - nginx:1.24.0 + nginx:1.25.0 –ext-diffオプションをつけることで、dyffの差分が確認できます。オプションを外せば従来の差分比較もできるので、使い分けたい方は上記の設定にして、オプションをつけるのがめんどくさい方はGitのエイリアスに登録してもよいかもしれません。 おわりに yamlの比較の確認がしやすいdyffの紹介をしました。Kubernetesの運用の中で、yamlの設定差分の見落としは危険です。しかし、ServiceやDeploymentといった単純な単体リソースの比較であればdiffでもなんとなく差分比較できますが、HelmアプリケーションのValuesといった数百、数千行あるような設定ファイルをdiffで比較するのはかなりの苦行だと思います。ぜひともdyffをインストールして、その苦行から解放されてください。 参考 dyff公式リポジトリ: https://github.com/homeport/dyff ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post YAMLの変更点を見落とさない!diffより強力なYAML差分確認ツール『dyff』のすすめ first appeared on SIOS Tech Lab .
アバター
こんにちは、伊藤です。 佐賀大学では2000年より認証情報の統合を進めており、その成果を踏まえて2007年度から毎年統合認証に関するシンポジウムが開催されています。 統合認証シンポジウムURL: https://www.cc.saga-u.ac.jp/center/workshop/ias 2026年3月6日には「第18回 統合認証シンポジウム」が実施され、弊社エンジニアの服部が登壇しました。 昨年プレスリリースした、「SIOS Shibboleth IdP サービス」( https://sios.jp/products/it/auth/sios-ldp/ )の紹介とデモを行い、ご参加いただいた方々から多くのご質問をいただき、大きなご関心をお寄せいただきました。 ご登壇者 「紙の証明から、検証できる証明へ ― 日本におけるデジタル証明書の可能性」  鈴木彦文 様(国立情報学研究所) 紙の証明書を今後電子証明書に変換していく(デジタル化)際に、現状IdPが持ってる情報は少なく、別途提供手段が必要である課題があります。 NIIで標準のソリューションやIHV共通基盤を提供することで、各大学での導入ハードルを下げられるのではないかといった意見が出されました。 また、鈴木様は、デジタル化の推進にはDX(デジタルトランスフォーメーション)の観点が不可欠であり、関係者にその重要性を深く理解してほしいと訴えられていました。弊社としても、既存の認証基盤と新しいデジタル証明の橋渡しが今後の重要なテーマになると再認識しました。 「統合認証のクラウド化に向けた基礎情報整理のすすめ」  三島和宏 様(大阪教育大学) 大学の停電等でシステムを停止する際に統合認証は稼働させ続けたいという背景から、統合認証のクラウド化を進めたいというニーズが高まっています。今回の講演では、クラウド化を進める前に、まずは「学務・人事システム(情報源)→情報連結→ID管理→各連携システム」の流れの整理が必要であると主張されていました。 特に情報源の整理が重要であり、場所・自動化の可否・信頼性・手を加える必要性・いつ入力され入手できるかを整理した上で、ID管理へ連携する必要性を三島様ご自身の経験や学内環境の現状を踏まえて強調されていました。 各大学でクラウド化が進む中、クラウド化を単なるシステム移行ではなく、情報源の再評価の機会と捉える視点は、弊社のインテグレーション業務においても非常に共感できるポイントでした。 「マイナンバーカードを活用した証明書発行システムの認証について」  大林正人 様(NTT西日本株式会社ビジネス営業本部エンタープライズビジネス営業部文教営業部) マイナンバーカード認証を用いて大学の証明書を発行するソリューションの紹介と、その利点について述べられていました。 デジタル庁提供のデジタル認証アプリ(既存機能の活用)でマイナンバーを使用して認証することで、卒業後のアカウント申請が不要になり、在学中から卒業後まで同一人物であることを継続して保証できる点が強みとして挙げられていました。 マイナンバーカードの活用によるライフサイクルを通じた本人保証は、これからの大学のアイデンティティ管理における新しいスタンダードになる可能性を感じます。 「学生生活とマイナンバーカード: 様々な利用シナリオ」   佐藤周行 様(国立情報学研究所) National IDとしてのマイナンバーカードの民間利用促進に対し、大学としてどう向き合うべきかのプランが示されました。例として、東京大学では認証器のre-binding(再紐付け)時の当人確認にマイナンバーカードを利用するケースが紹介されました。 National IDは利便性に優れる一方で、現状ではプライバシーリスクの評価や信頼性の担保が必要といった意見も出ていました。 また、現状のマイナンバーは日本国内でしか通用しないため、海外でも通用するような枠組みも検討していく必要があると問題提起され、利用者が選択でき、各サービスでNational IDを受け入れられる形が理想であるという意見も出ておりました。 利便性とプライバシーのバランス、そして国際的な相互運用の課題は、IdPサービスを提供する弊社にとっても常に注視していくべき領域です。 「AXIOLEパスキー版の実装について」  上田秋成 様(株式会社ネットスプリング) ユーザ管理も行える認証アプライアンス「AXIOLE」が、今年で20周年を迎えたとのことです。昨年リリースされた同製品の「パスキー認証機能」とその特長についてご紹介いただきました。 パスキー認証機能により、ログインIDやパスワードの入力が不要になります。パスワードレス認証の普及はユーザビリティとセキュリティの両立において急務であり、アプライアンス製品におけるパスキー対応の広がりは、認証業界全体にとって大変喜ばしい潮流だと感じました。 「SIOS Shibboleth IdPサービスが実現する、持続可能な認証基盤の最適解」  服部祥大(サイオステクノロジー株式会社) 弊社で昨年プレスリリースした、「SIOS Shibboleth IdPサービス」を紹介しました。Azure基盤を活用し、Shibboleth IdPの運用管理を大幅に簡略化したソリューションとなっております。本ソリューションの詳細につきましては、後日別の記事やYouTubeでの紹介を予定しております。 まとめ 今回の統合認証シンポジウムでは、学認からマイナンバーカードの最新の活用事例まで幅広い題材の講演があり、終始学ぶことが多く大変有意義な時間となりました。 また講演終了後には情報交換会が行われました。大学のシステムご担当者様や統合認証ソリューションの開発企業など、多くの方々と直接交流することができ、貴重な意見交換の場となりました。 今回の経験でさらに広がった知見を、今後の弊社の製品開発やサポート活動にしっかりと活かしていきたいと感じています。 関係者の皆様、ご参加いただいた皆様、誠にありがとうございました。 関連記事 参考 第17回 統合認証シンポジウムで登壇しました SIOS Tech Lab 参考 第12回 統合認証シンポジウムで登壇しました SIOS Tech Lab ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 第18回 統合認証シンポジウムに参加しました first appeared on SIOS Tech Lab .
アバター
ChatGPTの登場以降、多くのWebサービスやアプリに「AI機能」が搭載されるようになりました。現在、生成AIをアプリケーションのUIに組み込むアプローチには、次のような例があります。 対話型(Chatbot) プロンプトビルダー型(Parametric UI) インライン補完型(Ghost Text) コンテキストメニュー型(Contextual Actions) キャンバス型(Artifacts / Workspace) ジェネレーティブUI型(Generative UI) 本記事では、これら6つのUIパターンの特徴を整理し、ユーザーの作業を文脈から支援するAIインターフェース設計の勘所について解説します。 UIパターン 1. 対話型(Chatbot) 画面の右下などにアイコンを置き、クリックするとチャットウィンドウが開く、現在最も一般的な実装形式です。 メリット: チャットボットの表示・非表示をユーザーが簡単に切り替えられるため、既存のアプリケーションのUIを大きく変更せずに導入できるのが特徴です。AIによる補助が不要なユーザーの作業を邪魔しないため、ユーザー数の多い既存サービスでも抵抗感を持たれにくいという大きなメリットがあります。 対話形式は万能で強力なインターフェースであり、幅広い質問やゼロからのアイデア出しに向いています。 例: Nulab Backlog AI アシスタント 限界: 一方で、ユーザーはいちいち作業の手を止め、チャットを開き、「〇〇をして」とプロンプトを入力し、出力された結果をコピーして元の作業画面に貼り付ける……という 「コンテキストスイッチ(文脈の切り替え)」 が発生します。 また、最大の課題となるのが 「言語化のコスト(プロンプトの壁)」 です。 例えば、画像編集ソフトで「右上の画像の明るさを10%上げて、背景を少しぼかして」と文字で指示するのは、スライダーを直接操作するよりも遥かに手間がかかります。人間はやりたいこと全てを簡単に言語化できるわけではありません。そのため、ここから紹介するような、よりワークフローに溶け込んだUIパターンとの使い分けが重要になります。 2. プロンプトビルダー型(Parametric UI) 自由記述のチャット欄にすべてを委ねるのではなく、ドロップダウン、スライダー、タグ選択といった 従来のGUIパーツを使って、AIへの指示(プロンプト)を組み立てさせるUI です。 文章作成ツールで「トーン:丁寧 / カジュアル」「長さ:短め / 長め」をボタンで選ばせたり、画像生成ツールで「アスペクト比:16:9」「スタイル:水彩画」をメニューから選ばせたりする形式です。 メリット: ユーザーは「上手なプロンプトの書き方」を知らなくても、意図した結果を得られます(言語化のコストを下げる)。 システムの裏側で、GUIの選択状態を良質なプロンプト文字列に変換してAPIに渡すため、出力の精度が安定します。 例: Canvaのマジック生成 3. インライン補完型(Ghost Text / Inline Completion) 現在、テキスト入力において最も成功しているAI支援のUIです。 GitHub Copilot (コード補完)などで採用されています。 ユーザーが文字を入力している最中に、AIが予測した続きを薄いグレーの文字(ゴーストテキスト)で表示します。ユーザーは Tab キーを押すだけでそれを採用(Accept)でき、気に入らなければそのまま入力を続けて無視(Reject)できます。 メリット: ユーザーの思考を中断させません。 プロンプトを入力する必要がありません(現在のカーソル位置までのテキストがそのまま文脈になる)。 採用・不採用の判断が極めて高速に行えます。 実装のポイント: レイテンシ(反応速度)が命です。ユーザーのタイピング速度に追いつくためには、数百ミリ秒以内の応答が求められます。 例: Visual Studio Core 上の GitHub Copilot 4. コンテキストメニュー型(Contextual Actions) ユーザーが選択したオブジェクト(テキスト、画像、表など)に対して、AIが特定の処理を提案するパターンです。 Notion AI などのエディタで見られます。 テキストをハイライト選択すると、通常の「コピー」「貼り付け」の隣に、「要約する」「翻訳する」「短くする」といったAIアクションがポップアップ表示されます。 メリット: 「ここでAIに何を頼めるか」をユーザーに視覚的に提示できます。 選択範囲という明確な「対象(Subject)」があるため、AIがコンテキストを誤解せず、精度が高まります。 例: Notion AI 5. キャンバス型(Artifacts / Workspace) チャット画面とは別に、プレビュー・編集専用の画面(キャンバス)が左右や上下に分割して用意され、 「AIとの対話」と「生成物の直接編集」をシームレスに行えるUI です。 Claudeの「Artifacts」や、ChatGPT、Geminiの「Canvas」機能が代表例です。AIにコードや長文を書かせると、専用の領域にそれがレンダリングされ、人間がそこを直接手直ししたり、さらにAIに「ここだけ修正して」と指示を出しできます。 メリット: 対話型(Chatbot)の弱点だった「結果をコピペして手元に持ってくる手間」を完全に排除します。 コーディング、Webデザイン、企画書の作成など、何度も推敲を重ねる「反復的な作業(イテレーション)」に最適です。 例: Google GeminiのCanvas 6. ジェネレーティブUI型(Generative UI) AIの出力結果を単なるテキスト(Markdown)として表示するのではなく、 目的に応じた「操作可能なUIコンポーネント」そのものを動的に生成して表示する 手法です。 例えば、「来週の東京の天気は?」と聞いたときに、「晴れです」というテキストを返すのではなく、 「天気ウィジェット(グラフやアイコン)」 をチャット欄の中にレンダリングします。「フライトを予約したい」と言えば、日付選択カレンダーと便のリストUIが表示されます。 メリット: テキストを読むよりも直感的に情報を把握できます。 表示されたUIを使って、さらに詳細な操作(ボタンを押す、選択するなど)がチャット欄から離れることなく可能になります。 例: Google Gemini UX設計の要:Human-in-the-loop これら多様なUIパターンを取り入れる上で、最も重要な哲学があります。それは、 「最終決定権は常に人間にある(Human-in-the-loop)」 ということです。 AIは確率的に次の言葉や操作を予測しているに過ぎず、平気で嘘をついたり(ハルシネーション)、的外れな提案をしたりします。したがって、AIの提案は常に 「暫定的」 であり、ユーザーが簡単に修正・破棄できなければなりません。 Undo/Redo の保証 AIによる自動編集が行われた後、Ctrl + Z (Undo) で即座に元の状態に戻せることは必須要件です。「AIが勝手に書き換えて、元に戻せなくなった」という体験は、ツールへの信頼を損ないます。 差分の可視化 (Diff View) AIがコードや文章を自動で修正した場合、どこがどう変わったのかをハイライト表示(Diff表示)することで、ユーザーは安心して変更を受け入れることができます。 まとめ:AIはユーザーの能力を拡張する「道具」 業務アプリやプロフェッショナルツールにおけるAIは、人間と会話するだけの存在から、ユーザーの能力を拡張する高度な「道具(コパイロット)」へと進化しています。 AIを道具として捉えたとき、プロンプトを入力させる対話型だけでなく、GUIパーツで指示を組み立てる「プロンプトビルダー型」や、カーソルの先で文脈を汲み取る「インライン補完型」、そして共同作業空間である「キャンバス型」など、多様なアプローチが有効です。 それぞれの特性を理解し、ユーザーのワークフローに最も適したUIを選択・組み合わせることが、これからの生成AI時代の標準的なインターフェース設計となっていくでしょう。 Illustration by Google DeepMind on Unsplash ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 生成AIツールを便利にする6つのUIアプローチ first appeared on SIOS Tech Lab .
アバター
こんにちは。香西です。 今回は、生成AI(Claude Code や GitHub Copilot など)と MCP(Model Context Protocol)を組み合わせて、AIに自由度が高く人間にもわかりやすいシステム構成図を .drawio.svg 形式で描かせるためのツール「 mcp-drawio-svg 」を作成し、OSSとして公開しました。 この記事では、開発に至った背景や、既存の作図ツール(Mermaidなど)が抱えていた課題、そして本ツールを使った実際の作図フローについて解説します。 コードは GitHub で公開しているので、ぜひ試してみてください。 https://github.com/Takashi-KOZAI-sti/mcp-drawio-svg なぜ作ったのか? — 「AIにdraw.ioを書かせる」の落とし穴 生成AIが開発スタイルの主流になってくる中で、「AIにMarkdown形式でドキュメントを記述させる」というフローはすでに当たり前になりつつあります。それに伴い、インフラ構成図やシステム概要図などもAIに作図させたいというニーズが急速に高まっています。 「AIで図を描く」となれば、Mermaid や PlantUML が第一候補に挙がりますが、上流設計においては表現力不足(自由な配置、色味、公式アイコンの利用など)が否めません。そこで昨今トレンドになっているのが、**「生成AIに draw.io のファイルを作らせる」**というアプローチです。実際、ネット上でもこの手法を紹介する記事を多く見かけるようになりました。 しかし、真に業務でのドキュメント運用を考えたとき、既存のアプローチには 重大な課題 があると私は考えています。 それは**「エクスポートの手間と、ファイルの二重管理」**です。 通常、AIが出力した .drawio ファイルはそのままでは Markdown に埋め込めません。そのため、一度 draw.io でファイルを開き、PNG や SVG 形式でエクスポートして Markdown から参照する、という作業が必ず発生します。これを繰り返すと「図を更新したのにエクスポートし忘れて、設計書の画像が古いまま」という惨事が必ず起きます。 本当に私たちが欲しいのは、**「AIが作図してくれて、そのままMarkdownでプレビューでき、かつ後から人間が直接手直しできる」**という、シームレスな体験のはずです。 解決策:最初から「.drawio.svg」を出力させ、ローカルで完結させる この課題を根本から解決するために作成したのが、AIが構成図を .drawio.svg 形式 で直接生成するための MCP サーバー「 mcp-drawio-svg 」です。 本ツールの最大の強みであり、既存の「生成AI × draw.io」手法との明確な差別化ポイントは以下の2点です。 ① 「表示」と「編集」を1ファイルで両立するデュアルフォーマット 本ツールは .drawio ではなく、最初から .drawio.svg 形式を出力します。 これは draw.io が定義する特殊なSVGファイルで、画像データの中に編集用のソースデータが内包されています。 Markdownにそのまま埋め込める : ![](diagram.drawio.svg) と書くだけで、設計書に画像として表示されます。エクスポート作業は一切不要です。 1ファイルでそのまま編集可能 :生成されたファイルを VS Code の draw.io 拡張などで開けば、通常の draw.io と同様にノードの移動やスタイル変更が可能です。 ② 機密情報を守る「完全ローカル動作」 社内システムのインフラ図やアーキテクチャ図には、外部に漏らしたくない機密情報が多く含まれます。外部の作図SaaSやAPIに構成データを投げて図をレンダリングさせる手法は、セキュリティの観点からエンタープライズでの導入ハードルが高くなります。 mcp-drawio-svg は、図のレイアウト計算やファイル生成の処理が すべてローカルマシンのMCPサーバー内で完結 します。構成データが外部に漏洩する心配がなく、セキュアな業務環境でも安心して利用できます。 mcp-drawio-svg ができること そこで作成したのが、AIが構成図・インフラ図・システム概要図を .drawio.svg 形式で生成するための MCP サーバー「 mcp-drawio-svg 」です。 AIに対して「こんな構成の図を作って」とお願いすると、AIがこのMCPサーバー( create_drawio_svg ツール)を呼び出します。MCPサーバー側では、図の構成要素(ノード・エッジ・グループ)を JSON で受け取り、以下の処理を全自動で行います。 自動レイアウト計算 : 座標の指定は不要です。フロー方向やレイヤーを考慮して自動で配置を決定します。 アイコン解決 : simple-icons などから適切なアイコンを自動検索して適用します。(もちろん指定することもできます) ファイル生成 : 最終的な .drawio.svg ファイルを出力します。 実装・使い方 — AIに構成図を生成させる ここからは実際の設定と使い方です。今回は Claude Code などに組み込む例を紹介します。 1. セットアップ まずはリポジトリをクローンしてビルドします。 Bash git clone <https://github.com/Takashi-KOZAI-sti/mcp-drawio-svg.git> cd mcp-drawio-svg npm install npm run build 2. MCPサーバーとして登録 お使いの AI クライアントの MCP 設定に本ツールを追加します。 Claude Code の場合 ( ~/.claude/settings.json ) JSON { "mcpServers": { "drawio-svg": { "command": "node", "args": ["/path/to/mcp-drawio-svg/dist/index.js"] } } } 3. AI に「ざっくり」プロンプトを投げる ここが生成AI×MCPの便利なところです。人間がいちいちノードやエッジを細かく指定する必要はありません。 例えば、Claude Code であれば、以下のように 非常にざっくりとした依頼 をplan mode で投げると、MCPのインプットに合わせて構成等を考えてくれます。 あなた(ユーザー): azure 上で構築するwebアプリの、典型的なインフラ構成図を記述してください。 アイコンについては、./icons ファオルダに使えそうなものを配置してあるので、適当に使ってください。 アイコンについては、あらかじめしてしておけばそれを使って図を作成してくれますし、なければsimple-icons からそれっぽいものをMCP側で当て込んでくれます。それでも該当するものがなければ、矩形での表示となります。 Claude Code(AIによる構成案の提案): Azure Web App 典型インフラ構成図の作成プラン ノード & アイコンパス ノード アイコンパス Users general/10783-icon-service-Browser.svg Front Door & CDN web/10073-icon-service-Front-Door-and-CDN-Profiles.svg Application Gateway (WAF) networking/10076-icon-service-Application-Gateways.svg … グループ構成 Azure Subscription  (全体) Virtual Network  グループ Application Gateway, App Service, Function Apps Data Services  グループ SQL Database, Cache for Redis, Blob Storage Security & Identity  グループ Key Vault, Entra ID エッジ(接続) Users → Front Door Front Door → App Gateway App Gateway → App Service … レイアウト algorithm: layered, direction: RIGHT spacing: 70 あなた(ユーザー): 「それでOK!」 これで、あとは、Claude Code がノードとエッジの情報を組み立てて、 create_drawio_svg ツールを実行し、アイコン付きの立派な .drawio.svg ファイルを出力してくれます。例えば、こんな感じ(中身のシステムの妥当性は今は気にしないでくださいね)。      4. 手修正と追加 自動生成らしく、一部エッジが交錯していたり、見にくかったりするので、ここは残念ながら手修正をおねがいします。ある程度調整しているつもりですが、残念ながら完璧な配置はなかなか難しいです。お手数ですが、例えば、こんな感じに修正してみてください。 これに対して、追加/削除を行っていくことも問題なくできます。   あなた(ユーザー): 作成した drawio.svg をベースに、新しいdrawio.svg を作って欲しいです。 構成要素として、github を追加して、cicd をこの構成図に増やしてください。 今の構成図のレイアウトはそのままにしてください。 アイコンはやはり./icons フォルダから適当に探してください。 そうすると、AIは対象となる drawio.svg の構成を読み取るため read_drawio_svg を実行し、現在の図の構造を読み取ります。そして、 edit_drawio_svg を用いて手修正で変更したものを壊すことなく、新しいノード(画像右端のCI/CD Pipeline)を追加することができました。 デフォルトでは以前のレイアウトを残したまま新しいノードを追加する動作ですが、 layout_mode: "recompute” を指定すれば(AIにレイアウトを再計算しろと指示すれば)、すべてのノード位置を改めて再計算し、配置し直すことも可能です。ただし、自分で加えた微修正(ノード位置の修正やエッジの接続点の変更など)は再計算でもとに戻ってしまいますし、手作業で作成した図などは場合によっては原型を全く留めない場合もあるので、図を完全に新規で作り直したいときなどに限定して使用してください。 あとはこれの繰り返しで、期待する図を作成していくことができます。 人間とAIの理想的な役割分担(設計思想) このツールの根底にある設計思想は、**「人間が主導権を持ちつつ、面倒な作業をAIに任せる」**という理想的な役割分担です。 私が目指したのは、以下のような人間とAIの協働プロセスです。 【人間】大枠の指示を出す 「AzureでよくあるWebアプリの構成にして」「セキュリティ要件としてPrivate Endpointは必ず入れて」など、大まかなプランや絶対に外せないポイントを指示します。 【AI】詳細化と提案(たたき台の作成) 人間の指示を受けて、AIが必要な構成要素(各サービス、ネットワーク、接続関係)を具体的に洗い出し、構成案を提案します。 【人間】レビューと承認 提案内容を確認してOKを出します。これにより、**「論理的には100点、見栄え的には70点」**の構成図( .drawio.svg )が全自動で出力されます。 【人間】見栄えの最終仕上げ 出力された図を draw.io で開き、ノードの配置バランスや色使いなど、人間の感性が問われる「見栄え」の部分を100点に仕上げます。 つまり、 「大きな方向性の決定」と「感性が必要なデザインの最終調整」は人間が担当し、その間にある「構成要素の洗い出しから初期レイアウト(たたき台)の作成」をAIが担う 、という形です。 最初から真っ白なキャンバスに向かってアイコンを一つずつ配置していくのは、結構な手間ですよね。すでに論理的な接続が完了している70点の図を100点に引き上げる作業から始められるため、設計ドキュメント作成の負担が圧倒的に軽くなるというところです。 実運用に向けた環境構築とTips 第2章で触れた通り、 mcp-drawio-svg は完全にローカル環境で動作しますが、実際に業務等のプロジェクトに組み込む際には以下の点にご注意ください。 1. AIクライアントとMCPサーバー間のファイルアクセス 本ツールはローカルで .drawio.svg ファイルを直接生成・上書き保存します。そのため、Claude Code などの生成AIクライアントと、このMCPサーバーのプロセスは、 出力先のディレクトリに対して共通の読み書き権限 を持っている必要があります。 もしAIクライアントを Docker などのコンテナ内で実行している場合は、ホスト側のディレクトリを適切にマウントし、両者が同じファイルパスを正しく参照・編集できるように設定してください。 2. プロンプト(LLM)側のデータ管理 MCPサーバーの処理(レイアウト計算やファイル生成)において、構成データが外部の作図APIなどに送信されることは一切ありません。ただし、ユーザーがAIクライアントに入力する指示(「こういうシステム構成を作って」というプロンプト)自体は、当然ながらバックエンドのLLM(AnthropicやOpenAIなど)へ送信されます。 業務利用の際は、作図の処理自体はローカルで安全に行われていることを理解しつつ、利用するLLMプロバイダのデータ規約(学習への利用オプトアウト設定など)を自社のポリシーに合わせて適切に管理した上でご活用ください。 まとめと今後の展望 今回は、生成AI時代の新しい作図アプローチとして、 mcp-drawio-svg を紹介しました。 Mermaid のような「コード駆動の手軽さ」と、 draw.io の「表現力・直感的な編集のしやすさ」を両立させることで、開発現場のドキュメント作成・設計体験が劇的に向上するはずです。 コードは GitHub で公開していますし、ここでは紹介していない機能も実装していますので、ぜひ手元の環境で動かしていただき、日々のドキュメント作成に役立ててみてください! https://github.com/Takashi-KOZAI-sti/mcp-drawio-svg おわりに(余談):マネージャー職が「片手間」でOSSを作れる時代 最後に少しだけ、個人的な所感を書かせてください。 実は私、現在の会社ではマネージャー的ポジションにおります。旧来の日本企業の考え方からすれば、「管理職が自らコードを書いてツールなんか作っていないで、組織の利益最大化のためのマネジメント業務に専念しなさい」とお叱りを受けかねない立場です(笑)。 しかし、私は**「業務改善のヒントや組織の本当の強さの源泉は、常に現場にある」**と強く信じています。現場のペイン(今回の例で言えば、ドキュメント作図の煩わしさ)を解消する細やかな改善こそが、最終的に組織全体の大きな生産性向上に繋がるからです。 これまでは、こうした現場向けのニッチなツールを管理職が自ら作るのは、時間的にもリソース的にも困難でした。しかし、生成AIの登場で状況は一変しました。 AIに対して「こんな課題があるから、こういうアプローチで解決したい」とイメージを伝え、少しのやり取り(壁打ち)を行うだけで、 日々の業務の隙間時間に「ペロッと」具体的な動くものが作れてしまう のです。今回の mcp-drawio-svg も、季節柄の胃の痛くなる業務を行う傍らで、少し息抜きしながらAIという優秀なアシスタントの力を借りてサクッと形にしたものです。 さらに白状すると…… 実はこのブログ記事自体も、私がゼロから執筆したわけではありません。 私が「こういう構成で、こういうアピールポイントを入れて」と要点を箇条書きで投げただけで、あとは生成AIがいい感じに文脈を汲み取って、この原稿を書き上げてくれました。この余談くらいでしょうか、自分で書いたのは。(^_^;) アイデアと現場への課題意識さえあれば、ポジションに関わらず誰でも素早く形にできる。本当に素晴らしい時代になりましたね。 皆さんもぜひ、生成AIという強力なツールを使って、日々のちょっとした業務改善を形にしてみてください! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【ローカルで完結】AIエージェントに”いい感じの”構成図を描かせるMCP「mcp-drawio-svg」を作りました first appeared on SIOS Tech Lab .
アバター
はじめに こんにちは!サイオステクノロジーのなーがです。前回はGoogle CloudのVertex AIをAzureから使用するための手順ということで主にインフラ関連の内容を書きましたが、今回はAIコーディングエージェントの開発プロセスを強化するフレームワーク「obra/superpowers」について書こうと思います。 AIエージェントは本当に便利なのですが、使い込んでいると「あれ、テスト書かずにいきなり実装してる…」「原因調査なしにとりあえずパッチを当てようとしてる…」という場面に気づくことがあります。エージェントは高性能ですが、プロセスを省略するクセがあるんですよね。 そんな悩みを解決してくれるのが、今回紹介する obra/superpowers です。92K以上のGitHubスターを獲得している注目のフレームワークで、AIコーディングエージェントに規律ある開発プロセスを注入してくれます。 今回は、superpowersの概要からインストール方法、実際の活用シナリオまでを詳しく紹介します。 obra/superpowers とは obra/superpowers は、AIコーディングエージェント向けの アジャイルスキルフレームワーク です。 「スキル」と呼ばれるMarkdownベースの指示ファイルをエージェントに読み込ませることで、テスト駆動開発・体系的なデバッグ・コードレビューといった規律ある開発プロセスを強制します。 “coding agents produce better results when they follow systematic processes rather than ad-hoc approaches” この思想が、superpowersの根幹にあります。エージェントの能力を引き出すのではなく、 プロセスをガイドする というアプローチです。 誕生の背景 開発者の Jesse Vincent 氏(Prime Radiant)が、AIエージェントが以下のような「近道」を取ろうとする問題に直面したことがきっかけです。 テストを後回しにして実装を先に書く 根本原因を調査せずにパッチを当てる 完了確認なしに「できました」と報告する superpowers はこれらのアンチパターンを明示的にドキュメント化し、エージェントが自分でそれを認識・拒否できるように設計されています。 参照: obra/superpowers – GitHub 対応プラットフォーム superpowers は主要なAIコーディングプラットフォームすべてに対応しています。 プラットフォーム 対応状況 Claude Code 公式マーケットプレイス対応 Cursor プラグインマーケットプレイス対応 Gemini CLI Extension対応 Codex .codex/ ディレクトリ方式 OpenCode .opencode/ ディレクトリ方式 スキル一覧とアーキテクチャ 14種類のスキル一覧 superpowers には14種類のスキルが含まれており、4つのカテゴリに分類されています。 カテゴリ スキル名 目的 テスト・品質 test-driven-development テスト先行の RED-GREEN-REFACTOR サイクルを強制 systematic-debugging 4フェーズの根本原因分析を強制 verification-before-completion 証拠なき完了宣言を禁止 計画・実行 brainstorming 実装前の設計対話・仕様レビューを実施 writing-plans 2〜5分タスク単位の実装計画を作成 executing-plans 実装計画を順序通りに実行 subagent-driven-development サブエージェント×2段階レビューで高品質実装 dispatching-parallel-agents 独立タスクを並列サブエージェントへ委譲 Git・コラボレーション using-git-worktrees 独立したGit worktreeで安全に並列開発 finishing-a-development-branch テスト確認→マージ/PR/破棄の構造化フロー requesting-code-review 適切なタイミングでコードレビューを依頼 receiving-code-review レビューフィードバックを技術的に評価 メタ using-superpowers スキルの発見・呼び出しのメタプロトコル writing-skills 新しいスキルをTDDで作成 コアスキル12種に加え、フレームワーク自体を拡張するメタスキル2種( using-superpowers ・ writing-skills )が含まれています。 ディレクトリ構成 リポジトリのトップレベル構成は以下の通りです。 superpowers/ ├── skills/ # 14種類のスキルモジュール ├── agents/ # エージェント設定・振る舞い定義 ├── commands/ # CLIコマンド実装 ├── hooks/ # プラットフォーム統合フック ├── docs/ # プラットフォーム別ドキュメント ├── tests/ # テストスイート ├── .claude-plugin/ # Claude Code 統合 ├── .cursor-plugin/ # Cursor 統合 ├── .codex/ # Codex 統合 └── .opencode/ # OpenCode 統合 各スキルは skills/<スキル名>/ ディレクトリに格納されており、必須ファイル SKILL.md の他に必要に応じてテンプレートやスクリプトが含まれます。 アーキテクチャ図 superpowers の全体アーキテクチャを図で示します。AIコーディングプラットフォームからスキルレイヤーを通じ、フック・Git統合へと流れる構造になっています。 スキルはプロジェクトのコンテキストに応じて **自動的に発火** します。特別な構文や手動呼び出しは不要です。 次に、スキルのカテゴリ構成をマップで示します。 SKILL.md のフォーマット 各スキルは SKILL.md ファイル一枚で定義されます。構造は以下の通りです。 --- name: skill-name description: "Use when ... (最大1024文字、トリガー条件を記述)" --- ## Overview ## When to Use ## Core Pattern ## Quick Reference ## Implementation ## Common Mistakes フロントマターの description フィールドが特に重要で、エージェントがスキルを自動選択する際の判断基準になります。「Use when…」という形式でトリガー条件を記述することがベストプラクティスです。 Token効率も設計の考慮事項で、頻繁に読み込まれるスキルは200語以内に収めることが推奨されています。 インストール方法 Claude Code 公式マーケットプレイス経由(推奨): /plugin install superpowers@claude-plugins-official カスタムマーケットプレイス経由: # ステップ1: マーケットプレイスを追加 /plugin marketplace add obra/superpowers-marketplace # ステップ2: インストール /plugin install superpowers@superpowers-marketplace インストール後、新しいセッションを開始して「この機能を計画したい」などと話しかけると、 brainstorming スキルが自動的に発火します。 アップデート: /plugin update superpowers Cursor Cursor のプラグインマーケットプレイスから検索してインストールするか、以下のコマンドを使用します。 /add-plugin superpowers Gemini CLI gemini extensions install https://github.com/obra/superpowers 参照: obra/superpowers – Installation 活用シナリオ 活用シナリオは無限に考えられますが、今回は下記の6つのシナリオを紹介します。 TDD を強制する test-driven-development スキルを使うと、エージェントはテストなしに実装コードを一切書かなくなります。 鉄則: “NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST.” テストより先にコードを書いた場合は、そのコードをすべて削除して最初からやり直す。 サイクルは以下の通りです。 RED — 失敗するテストを書く RED確認 (必須)— テストが実際に失敗することを確認する GREEN — テストが通る最小限のコードを書く GREEN確認 (必須)— テストが通ることを確認する REFACTOR — コードを整理する 手動テスト済みであることを理由にテストを省略しようとすると、スキルがそのアンチパターンを認識して拒否します。 長時間の自律開発セッション executing-plans スキルと subagent-driven-development スキルを組み合わせると、2時間以上の自律開発セッションが可能になります。 各タスクは2〜5分単位に分割され、サブエージェントが1タスクずつ実装・レビューを繰り返します。人間が介入しなくても、スキルがプロセスの品質を担保します。 複数機能の並列開発 dispatching-parallel-agents スキルを使うと、互いに独立した複数のタスクを並列で処理できます。 適用条件: 3つ以上の独立した失敗ファイル・サブシステムがある タスク間で共有状態がない それぞれ異なる根本原因を持つ using-git-worktrees と組み合わせることで、各エージェントが独立したGit worktreeで安全に作業できます。 公式ドキュメントによると、6件の失敗を3つの並列エージェントで解決したケースでは、コンフリクトゼロで解決できたとのことです。 根本原因を特定してからデバッグする systematic-debugging スキルは、エージェントが「とりあえずパッチを当てる」近道を防ぎ、4フェーズの体系的なデバッグを強制します。 4フェーズ: Root Cause Investigation — エラーを読み込み、再現手順を確認し、直近の変更を調査する Pattern Analysis — 動いている似た箇所と比較して差分を特定する Hypothesis and Testing — 仮説を1つ立て、最小限の変更で検証する Implementation — 根本原因のみを修正し、再発防止のテストを追加する 原因が特定できていない段階で修正コードを書き始めようとすると、スキルの Iron Law「NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST」に引っかかり、Phase 1 に差し戻されます。「直ったと思ったらまた壊れた」という状況を根本から防げます。 実装前に設計を固める brainstorming スキルを使うと、エージェントはコードを書く前に設計対話フェーズを実施します。 対話の流れ: 要件の確認と曖昧な点の洗い出し 複数の実装アプローチを提示して比較 トレードオフ(速度・保守性・複雑度)を明示 合意した方針をドキュメント化 このスキルは writing-plans と連携しており、対話が終わると自動的に実装計画の作成に移行します。「とりあえず実装してみて後で考える」というエージェントの傾向を、設計ファーストに切り替えます。 1問ずつ丁寧に要件を深掘りし、合意が取れた段階で設計書を作成→ writing-plans スキルへ引き渡します。 コードレビューのサイクルを回す requesting-code-review と receiving-code-review の2スキルを使うと、エージェントがレビューの依頼から反映まで一貫して対応します。 レビュー依頼時( requesting-code-review ): PR の目的・変更の概要・テスト状況を整理してレビュアーに提示 レビューしやすい粒度に変更を分割 フィードバック受け取り時( receiving-code-review ): 指摘内容を「必須修正」「提案」「議論」に分類 技術的な根拠をもとに修正の要否を判断 反映した変更を明示してレビュアーに返答 感情的に反応したり、すべての指摘を無条件に受け入れたりするのではなく、技術的な評価に基づいて対応します。 技術的に不要な変更はプッシュバックし、根拠のある修正のみを行います。 さいごに 今回は obra/superpowers について紹介しました。 AIエージェントは非常に高性能ですが、プロセスを省略しがちという弱点があります。superpowers はそこに「規律」を注入することで、エージェントを本来の実力で動かし続けるための仕組みです。 特に test-driven-development スキルと systematic-debugging スキルは、エージェントが近道を取ろうとする典型的な場面で真価を発揮します。まずはこの2つからインストールして試してみることをおすすめします。 興味を持った方は、ぜひ公式リポジトリや writing-skills スキルを使って、自分だけのスキルを作ってみてください! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post AIコーディングエージェントの弱点を補う「obra/superpowers」 first appeared on SIOS Tech Lab .
アバター
こんにちは! 今月も「OSSのサポートエンジニアが気になった!OSSの最新ニュース」をお届けします。 3/2、高市早苗首相が自身の名前入りの仮想通貨「SANAE TOKEN」が発行・取引されていると X に投稿し、自身や事務所と関係はなく誤解しないよう注意を呼びかけました。 高市首相名の仮想通貨に注意を 「全く存じ上げず」と投稿 https://news.yahoo.co.jp/articles/36178752c01fbee50efe23bd46332d36f34ee8b0 3/17、Linux Foundation はオープンソースソフトウェアの持続的なセキュリティ強化を目的として Anthropic、AWS、GitHub、Google、Google DeepMind、Microsoft、OpenAI が総額 1250 万ドル(約 19億 8300万円)を助成したと発表しました。 米テック大手7社、オープンソースセキュリティに1250万ドルを助成 https://japan.zdnet.com/article/35245203/ 3/17、LayerX Security は Web サイトに表示される悪意のあるテキストを AI から隠蔽する手法を発見したと発表しました。 フォント改ざんでAI検出を回避、主要ツールがすべて失敗 https://news.mynavi.jp/techplus/article/20260321-4237821/ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【2026年3月】OSSサポートエンジニアが気になった!OSS最新ニュース first appeared on SIOS Tech Lab .
アバター
こんにちは、伊藤です。 今回は、Exchange OnlineのIMAP移行ツールを使用したメール移行を実施する機会がございましたので、その仕様と注意事項をまとめました。 Exchange Onlineの標準機能である「IMAP移行ツール」を使用したメール移行を実施しました。 公式ドキュメントには表れにくい仕様や、エラーとその対処法、注意事項が見えてきましたので、備忘録としてまとめます。 公式の考慮事項については、以下のMicrosoft公式ドキュメントも併せてご参照ください。 Exchange Online の IMAP メールボックスを移行する 移行可能なメールアイテムの最大サイズについて 公式ドキュメントの考慮事項には「移行できるメールの最大サイズは 35 MB です。」と記載されています。しかし検証の結果、これはExchange Onlineの既定のメッセージ受信サイズ制限に基づいた記述であり、 実際には設定を引き上げることでそれ以上の移行が可能 であることが確認できました。 既定の最大メッセージサイズ : 送信用 35 MB / 受信用 36 MB 引き上げ上限 : 最大 150 MB まで設定変更可能 ただし、150MBのメールがそのまま移行できるわけではありません。移行時の変換(Base64エンコード等)により、データサイズは実質約33%大きくなります。そのため、 実際の移行可能な最大サイズは約 112 MB となります。 これ以上のサイズのメールは移行不可となり、移行バッチにおいてスキップされます(スキップされたアイテムの種類は LargerItem と表示されます)。 サイズ制限の詳細については、以下のドキュメントをご参照ください。 Exchange Online の制限 スキップされるアイテムの種類と対処 移行バッチの実行時、アイテムによっては移行がスキップされます。主なスキップ理由として以下の3つが挙げられます。 LargerItem(サイズ超過) 前述の通り、Exchange Online側で設定されている最大メッセージサイズを超過しているメールです。上限の150MBまで設定を引き上げてもなおスキップされる巨大な添付ファイル付きメール等は、仕様上移行できません。 CorruptItem(破損アイテム) 移行バッチが移行元からデータを取得する際、正常に読み取れなかった破損アイテムです。Exchange Onlineへは移行されません。 厄介な点として、件名や日付が記載されていない CorruptItem の場合、エラーログから該当メールを特定するのが非常に困難です。そのため、「スキップされたアイテムの詳細画面」に表示される フォルダ名 を頼りに、移行元とExchange Online側のメールボックスを目視で比較し、該当アイテムを特定することをおすすめします。 MissingItemInTarget(移行先でのアイテム不在) 「移行済みのメールアイテムが、Exchange Online側のフォルダ内に存在しない」というエラーです。 これは移行失敗ではなく、移行バッチによる同期期間中に、 ユーザがExchange Online側で該当メールを削除したり、別のフォルダへ移動させたりした場合 に発生します。移行元のフォルダとExchange Online側の格納先が一致しなくなったため、システムが同期をスキップしたという証拠であり、すでに移行自体は完了している状態と言えます。 増分同期が走らない場合の解決策 移行バッチは初回同期完了後も差分を埋めるために増分同期を行いますが、稀にこれが正常に実行されない場合があります。増分同期が機能しているかは、対象メールボックスの「同期されたアイテム」の値が増加しているかで確認できます。 【対処方法】 増分同期が正常に実行されない場合は、 現在の移行バッチを一度削除し、再度同一のユーザで新規の移行バッチを作成 してください。 新規バッチを作成しても、すでにExchange Onlineへ移行されているメールは「同期済み」としてスキップされるため、重複することなく必要な増分のみを取り込むことが可能です。 (※ただし、移行済みのメールをExchange Online側で別のフォルダに移動していた場合、移動前の元のフォルダに同一メールが再度コピーされてしまうためご注意ください。) 移行元でメールを削除した場合の挙動 並行稼働中などに移行元(旧サーバ)でメールアイテムを削除した場合、次回の同期でExchange Online側の該当メールも削除状態となります。 この時、メールは完全に消去されるわけではなく、Exchange Onlineの 「復元可能なアイテム(回復可能なアイテム)」 領域へ移動します。デフォルトの保持ポリシーが適用されている場合、この領域では、14日間アイテムが保持され、その期間を経過した後にシステムによって完全削除されます。 上述のスキップされるアイテムの種類「MissingItemInTarget」や移行バッチの再作成の仕様も踏まえて、並行稼働中などでは、基本的に移行元(旧サーバ)でメールアイテムを操作することが望ましいと考えています。 容量限界に関する移行エラー(QuotaExceededException) 移行エラーで エラー: QuotaExceededException が表示された場合、Exchange Onlineメールボックスの容量上限に達していることを示しています。 ここで注意すべき点があります。 受信トレイなどの目に見える使用状況が100%になっていなくてもエラーになるケースがある という点です。 これは、削除状態のメールが移動する「復元可能なアイテム」領域自体が容量制限に達している場合に発生します。移動先がパンクしているため、移行処理そのものがエラー扱いとなります。「復元可能なアイテム」領域自体が容量制限に達しているかどうかは、以下で確認可能です。 【確認方法】 Exchange Online PowershellでExchange Onlineに接続し、「復元可能なアイテム」領域の使用状況を確認します。 Recoverable Itemsの最大サイズは30GBですが、以下の例では、「復元可能なアイテム(Recoverable Items)」は30GBと容量制限に達していることが分かります。 > Connect-ExchangeOnline > Get-MailboxFolderStatistics -Identity <ユーザID> -FolderScope RecoverableItems | Select-Object Name, FolderAndSubfolderSize Name FolderAndSubfolderSize ---- ---------------------- Recoverable Items 30 GB (32,212,361,236 bytes) Audits 0 B (0 bytes) Calendar Logging 394.7 KB (404,136 bytes) Deletions 0 B (0 bytes) DiscoveryHolds 0 B (0 bytes) Purges 30 GB (32,211,957,100 bytes) SubstrateHolds 0 B (0 bytes) Versions 0 B (0 bytes) エラー: QuotaExceededException の対処方法は以下となります。 【対処方法】 ユーザご自身で「復元可能なアイテム」内の不要なメールアイテムを削除する。 「復元可能なアイテム」の保持ポリシーを一時的に変更し、完全削除処理を強制実行して空き容量を作る。 アーカイブメールボックスを有効化して古いメールを逃がす。 ヘッダーの文字コードに関する移行停止エラー 移行中に以下のようなエラーが発生し、処理が停止してしまうことがあります。 エラー: ExchangeDataException: Decoding of header Subject failed; raw value: XXXXXX --> Character set name (XXX) is invalid or not installed. これは、メールのヘッダー(主に件名など)の文字コード(エンコード)が破損している、あるいは不正な規格が使われていることが原因です。このエラーに該当するメールが移行元に存在すると、スキップ処理されずエラー扱いとなり、バッチが停止してしまいます。 【対処方法】 エラーメッセージの raw value: 以降に記載されている件名などの情報を元に、移行元のサーバで検索をかけ、原因となっているメールファイルを特定します。そのメールを別フォルダに退避させる等して移行対象外にすることでエラーが解消し、他の正常なメールデータの移行が再開されることを確認してください。 「復元可能なアイテム」に覚えのないメールが存在する理由 移行後、ユーザの「復元可能なアイテム」領域に、本人が移行元で削除した覚えのないメールが含まれていることがあります。 これは、移行元ですでに削除されていた本来のデータに加え、移行バッチが同期処理を行う過程で、安全のために一時的に作成された「重複アイテム(過去のバックアップの残骸)」が含まれているためです。 システムが一時的に残したこれらの重複アイテムについては、最新の同一メールが正しいメールフォルダへ正常に移行されているため心配いりません。「復元可能なアイテム」内にある残骸は、アイテム保持ポリシーによって自動的に削除されるため、放置して問題ありません。 まとめ 今回は、Exchange OnlineのIMAP移行ツールを使用したメール移行の仕様と注意事項をまとめました。 これからIMAP移行を計画されている管理者の方の参考になれば幸いです。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Exchange OnlineのIMAP移行ツールの仕様と注意事項について first appeared on SIOS Tech Lab .
アバター
今号では、Linux における「拡張子」の取り扱いについて説明します! Linux での「拡張子」の役割とは 「拡張子」とは、 ファイルの種類を分かりやすく判断するための仕組み であり、 ファイルの末尾に付けられる文字 のことです。 例えば Windows の場合、.exe は実行ファイル、.txt はテキストファイル、となっています。この、あらかじめ決められている拡張子を他のものに変えてしまうと、そのファイルを開いたり実行できなくなるなどの問題が発生します。 しかし、Linux の場合は「ファイルヘッダ」というファイルの情報を読み取り、どのようなファイルであるかを判断しています。そのため、 拡張子がなくてもどのようなファイルかを判別できます。 とはいえ…システム的にはすぐにファイルを判別できても、人間はそうはいきませんよね。そこで、Linux でも Windows と同様にファイルに拡張子を付けることで、どのようなファイルかを判別しやすくしているのです。 Linux で使われる拡張子 Linux でよく使用される拡張子と、その内容についてご紹介します。 スクリプト系 .sh シェルスクリプト。Linux のコマンドや、if文、for文などの制御構文から構成される。 .py Python で実装されたスクリプト。 他にも様々なスクリプト言語があり、それによって拡張子も様々です。 設定ファイル系 .conf アプリケーション、システムの設定ファイル。 .cfg GRUB (ブートローダー) などに使用される、独自の書式を持つ設定ファイル。 .yaml、.yml コンテナ関連や Ansible で使用される設定ファイル。 設定ファイルは、主に /etc ディレクトリ配下に配置されます。 パッケージ、リポジトリ系 .rpm RHEL 系 OS で使用される、ソフトウェアのパッケージファイル。 .repo RHEL 系 OS で使用される、リポジトリの設定ファイル。 .rpm ファイルは、yum / dnf コマンドでダウンロードした場合 /var/cache/yum や /var/cache/dnf ディレクトリ配下に一時的に保存されます。 .repo ファイルは、通常 /etc/yum.repos.d ディレクトリ配下に配置されます。 システム管理系 .service サービス (インストール済みのプログラム) の起動、停止などを定義するファイル。 .timer プログラムの定期実行を定義するファイル。 .target 「マルチユーザーモード」や「グラフィカルモード」など、システムの起動状態をグループ化するためのファイル。 鍵・証明書系 .pem 秘密鍵や証明書の情報を格納する、テキストベースのファイル。 .crt SSL/TLS の証明書ファイル。 .key SSL/TLS の秘密鍵ファイル。 .pub SSH の公開鍵ファイル。 その他 .log ログファイル。 .tar アーカイブファイル。 .tar.gz 圧縮アーカイブファイル。 補足事項 file コマンドでファイルの形式を表示する file コマンド を使用すると、指定したファイルの形式を表示します。 私は、このファイルはテキスト?バイナリ?どちらなのか?を確認するのに使ったりします。 # file text.txt # テキストファイル text: ASCII text # file archivefile.gz # アーカイブファイル archivefile: gzip compressed data, was "test.txt", last modified: Mon Feb 9 00:40:33 2026, from Unix, truncated # file /var/log/messages # ログファイル /var/log/messages: UTF-8 Unicode text, with very long lines ※file コマンドは、あくまで ファイルの形式 を表示するためのものです。 例えば、.conf ファイルや .service ファイルはいずれもテキスト形式で保存されているため、file コマンドで確認すると ASCII text という結果が返ってきます。 「これは設定ファイル」「これはサービスファイル」という、 具体的なファイルの種類については判別できません ので注意してください。 拡張子を変更した場合、各ファイルは認識されるのか 結論から言いますと、 ちゃんと認識されます。 例えば .sh ファイルを .txt に変えても、シェルスクリプトとして実行できます (ただし実行権限は必要)。 ただし、GUI (デスクトップ環境) を利用している場合は Windows と同様に拡張子とアプリケーションが紐付けられている場合があるため、注意が必要です。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 知っておくとちょっと便利!Linux の拡張子について first appeared on SIOS Tech Lab .
アバター
はじめに こんにちは!年度末ということもあり、最近はもっぱら業務に追われているなーがです。今回は業務でAzure FunctionsからVertex AIのClaudeモデルを呼び出す機会があったので、その設定手順をまとめてみました。 AnthropicのClaudeモデルは直接Anthropic APIを使う方法が一般的ですが、Google Cloud Vertex AIを経由して利用することもできます。既にGCPを利用している組織であれば、Vertex AI経由のほうがIAMによるアクセス管理や既存の請求体系との統合ができて便利です。 ただ、Vertex AIからClaudeを使うための設定手順は、GCPの認証周りも絡むため少し複雑です。今回は、GCPの基本的な設定からPython SDKを使った動作確認まで、一通りの手順をまとめてみました。 Vertex AIのClaudeモデルとは Vertex AIでは、AnthropicのClaudeモデルをModel Garden経由で利用できます。自分のGCPプロジェクト内でClaudeを呼び出せるため、データの送信先やアクセス制御をGCPの仕組みで管理できるのがメリットです。 利用可能なモデル一覧 2026年2月時点で、Vertex AIで利用可能なClaudeモデルは以下の通りです。 モデル名 モデルID 特徴 Claude Opus 4.6 claude-opus-4-6@20260205 最高性能モデル。高度な推論・分析向け Claude Opus 4.5 claude-opus-4-5@20251101 高性能モデル。複雑なタスク向け Claude Sonnet 4.5 claude-sonnet-4-5@20250929 パフォーマンスとコストのバランスが良い Claude Opus 4.1 claude-opus-4-1@20250805 高性能モデル Claude Opus 4 claude-opus-4@20250514 高性能モデル Claude Sonnet 4 claude-sonnet-4@20250514 コスト効率の良い汎用モデル Claude Haiku 4.5 claude-haiku-4-5@20251001 高速・低コストモデル 以下のモデルは非推奨(Deprecated)となっています。既存のコードで使用している場合は、上記のモデルへの移行を検討してください。 モデル名 非推奨開始日 Claude 3.7 Sonnet 2025年11月11日 Claude 3.5 Haiku 2026年1月5日 参照: Vertex AI – Claude models サポートリージョン Claudeモデルを利用できるリージョンはモデルによって異なります。 モデル 利用可能なリージョン Opus 4.6 us-east5 , global Opus 4.5 / Sonnet 4.5 us-east5 , europe-west1 , asia-southeast1 , global Sonnet 4 us-east5 , europe-west1 , asia-east1 , global Haiku 4.5 us-east5 , europe-west1 , asia-east1 , global Opus 4.1 / Opus 4 us-east5 , global リージョンによっては利用できないモデルがあるため、事前に確認しておきましょう。最新情報は Model Garden でチェックしてください。 前提条件 この記事の手順を進めるにあたり、以下が必要です。 Google Cloudアカウント GCPプロジェクト(課金が有効になっていること) gcloud CLIがインストール済みであること(インストール方法は 後述 ) 以下のいずれかの権限を持つIAMロール ロール 用途 roles/serviceusage.serviceUsageAdmin APIの有効化 roles/iam.workloadIdentityPoolAdmin Workload Identityプール/プロバイダの管理 roles/iam.serviceAccountCreator サービスアカウントの作成 roles/resourcemanager.projectIamAdmin プロジェクトIAMの管理 roles/iam.serviceAccountAdmin サービスアカウントIAMの管理 Vertex AIの有効化 まず、GCPプロジェクトで必要なAPIを有効化します。 gcloud services enable \ iam.googleapis.com \ sts.googleapis.com \ iamcredentials.googleapis.com \ aiplatform.googleapis.com 各APIの役割は以下の通りです。 API 役割 iam.googleapis.com IAMの管理 sts.googleapis.com Security Token Service(トークン交換) iamcredentials.googleapis.com サービスアカウントの認証情報生成 aiplatform.googleapis.com Vertex AI本体 Model GardenからClaudeモデルを有効化する APIを有効化しただけでは、まだClaudeモデルは使えません。Model Gardenから利用したいモデルを有効化する必要があります。 Google Cloud Console にログインします 左側メニューから Vertex AI > Model Garden を選択します 検索バーで「Claude」と入力します 利用したいClaudeモデル(例: Claude Sonnet 4.5)を選択します 有効化 ボタンをクリックします 利用規約を確認し、同意して有効化を完了します 有効化が完了すると、そのモデルをVertex AI API経由で呼び出せるようになります。 参照: Model Garden でパートナー モデルを有効にする 認証設定: ADCによるローカル開発 ローカル環境からVertex AIを利用する最も簡単な方法は、Application Default Credentials(ADC)を使う方法です。 gcloud CLIのインストール ADCの設定にはgcloud CLIが必要です。まだインストールしていない場合は、以下の手順でインストールしてください。 Windows : Google Cloud CLI インストーラー からダウンロードしてインストール macOS(Homebrew) : brew install --cask google-cloud-sdk Linux(Debian/Ubuntu) : sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates gnupg curl curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | \ sudo gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | \ sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list sudo apt-get update && sudo apt-get install -y google-cloud-cli 参照: gcloud CLI をインストールする ADCの設定 ADC( Application Default Credentials )は、Google Cloudクライアントライブラリが自動的に認証情報を検索する仕組みです。以下のコマンドでローカルにADCファイルを作成できます。 gcloud auth application-default login このコマンドを実行するとブラウザが開き、Googleアカウントでのログインが求められます。ログインが完了すると、認証情報が以下のパスに保存されます。 Linux/macOS : ~/.config/gcloud/application_default_credentials.json Windows : %APPDATA%\gcloud\application_default_credentials.json Python SDKなどのクライアントライブラリは、このファイルを自動的に読み取って認証します。 参照: アプリケーションのデフォルト認証情報を設定する 認証設定: Workload Identity FederationによるGCP外からのアクセス Azure FunctionsなどのGCP外の環境からVertex AIにアクセスしたい場合は、Workload Identity Federationを使います。 Workload Identity Federationは、外部のIDプロバイダ(Azure AD、AWS IAMなど)が発行したトークンをGCPの短命トークンに交換する仕組みです。サービスアカウントキーを直接配布する必要がないため、セキュリティ面で優れています。 最初に設定するときは「プール?プロバイダ?SA Impersonation?」と概念が多くて面食らいましたが、一度理解すると鍵を一切管理しなくていいのが本当に楽です。 ここでは、Azure Functions の Managed Identity を使った連携を例に手順を説明します。WIF のベストプラクティスとして、 --allowed-audiences にはAzure側でアプリの登録(サービスプリンシパル)を作成し、そのApplication IDを指定します。これにより、認証を受け付けるトークンの対象を自分のアプリケーション固有のIDに絞り込むことができます。 Azureサービスプリンシパルの作成 まず、Azure側でアプリの登録(サービスプリンシパル)を作成します。このApplication IDをWIFの --allowed-audiences に指定することで、Managed Identityが取得するトークンの受け入れ対象を自分のアプリケーションに限定できます。 Azure Portal で以下の手順を実行してください。 Microsoft Entra ID > アプリの登録 > 新規登録 を選択 名前を入力(例: gcp-workload-identity )し、 登録 をクリック 登録後に表示される アプリケーション(クライアント)ID をメモしておく このApplication IDが --allowed-audiences に指定する値になります。 Workload Identityプールの作成 外部IDを管理するためのWorkload Identityプールを作成します。 gcloud iam workload-identity-pools create "azure-pool" \ --location="global" \ --display-name="Azure Pool" OIDCプロバイダの追加 作成したプールにAzure ADをOIDCプロバイダとして追加します。 gcloud iam workload-identity-pools providers create-oidc "azure-provider" \ --location="global" \ --workload-identity-pool="azure-pool" \ --display-name="Azure Provider" \ --issuer-uri="https://sts.windows.net/<AZURE_TENANT_ID>/" \ --allowed-audiences="<SERVICE_PRINCIPAL_APPLICATION_ID>" \ --attribute-mapping="google.subject=assertion.sub,attribute.tid=assertion.tid,attribute.oid=assertion.oid" \ --attribute-condition="attribute.tid == '<AZURE_TENANT_ID>' && attribute.oid == '<MANAGED_IDENTITY_OBJECT_ID>'" 各パラメータの説明です。 パラメータ 説明 --issuer-uri Azure ADのIssuer URI。Managed Identityが発行するv1トークンの場合はこの形式 --allowed-audiences 作成したサービスプリンシパルのApplication ID(Client ID) --attribute-mapping OIDCトークンのクレームをGCP属性にマッピング。 assertion.oid はObject IDに対応 --attribute-condition 受け入れるトークンの条件。テナントIDとObject IDを指定して不正なトークンを弾く Azure Functionsが複数ある場合など、複数のManaged IdentityのObject IDを許可したい場合は、 in 演算子で列挙できます。 --attribute-condition="assertion.tid == '<AZURE_TENANT_ID>' && assertion.oid in ['<MANAGED_IDENTITY_OBJECT_ID_1>', '<MANAGED_IDENTITY_OBJECT_ID_2>']" --allowed-audiences にテナントIDを使う場合 --allowed-audiences にはサービスプリンシパルのApplication ID(Client ID)の代わりに、認証用テナントのテナントIDを指定することもできます。 ただし、Azure Functionsが属するテナント(呼び出し側テナント)と、サービスプリンシパルが登録されているテナント(認証用テナント)が 異なる場合 は、サービスプリンシパルを マルチテナント として設定する必要があります。シングルテナントのサービスプリンシパルは、自テナント以外のManaged Identityからのトークン取得を受け付けないためです。 マルチテナント設定は以下のいずれかの方法で行います。 Azure Portal の場合: アプリの登録 > Authentication を選択 Supported account types を Accounts in any organizational directory に変更 Save をクリック Azure CLI の場合: az ad app update \ --id "<SERVICE_PRINCIPAL_APPLICATION_ID>" \ --sign-in-audience AzureADMultipleOrgs サービスアカウントの作成と権限付与 Vertex AI呼び出し用のサービスアカウントを作成し、必要な権限を付与します。 # サービスアカウントの作成 gcloud iam service-accounts create vertex-ai-workload \ --display-name="Vertex AI for Azure Workload" # Vertex AI利用権限の付与 gcloud projects add-iam-policy-binding YOUR_GCP_PROJECT_ID \ --member="serviceAccount:vertex-ai-workload@YOUR_GCP_PROJECT_ID.iam.gserviceaccount.com" \ --role="roles/aiplatform.user" YOUR_GCP_PROJECT_ID は自分のGCPプロジェクトIDに置き換えてください。 Azure Managed Identityへの権限借用許可 Azure FunctionsのManaged Identityがこのサービスアカウントを借用できるように設定します。 attribute.oid 属性を使ってバインディングするため、 principalSet:// 形式で指定します。 gcloud iam service-accounts add-iam-policy-binding \ "vertex-ai-workload@YOUR_GCP_PROJECT_ID.iam.gserviceaccount.com" \ --role="roles/iam.workloadIdentityUser" \ --member="principalSet://iam.googleapis.com/projects/YOUR_PROJECT_NUMBER/locations/global/workloadIdentityPools/azure-pool/attribute.oid/MANAGED_IDENTITY_OBJECT_ID" YOUR_PROJECT_NUMBER はGCPプロジェクトの番号(プロジェクトIDではありません)、 MANAGED_IDENTITY_OBJECT_ID はAzure FunctionsのManaged IdentityのObject IDに置き換えてください。 サンプルコード(WIF認証フロー) 以下のサンプルは、credential config ファイルを使用せず、辞書形式で load_credentials_from_dict に渡す方式です。実際の実装では AzureFunctionsWIFCredentials と Azure Functions のエントリポイントを別ファイルに分けることが多いですが、ここでは見通しよく1ファイルにまとめています。 google.auth ライブラリが IMDS へのトークン取得・WIF STS との交換・SA Impersonation をすべて自動で処理します。 credential_config 辞書の構造は、Google Cloud が定義する 外部認証情報設定ファイル の仕様に準拠しています。各フィールドの意味は以下のとおりです。 フィールド 説明 type: "external_account" WIF を使った外部アカウント認証であることを示す audience 認証先の WIF プール・プロバイダーの識別子 subject_token_type Azure から取得するトークンの種別(JWT) token_url GCP の Security Token Service (STS) エンドポイント credential_source トークン取得元(Azure IMDS)の URL・ヘッダー・形式 service_account_impersonation_url SA への権限借用リクエスト先 URL 参考: 外部 ID のワークロード ID 連携を構成する – Google Cloud 参考: google-auth: load_credentials_from_dict – Python クライアントライブラリ import os from google.auth import load_credentials_from_dict from google.auth.credentials import Credentials from anthropic import AnthropicVertex class AzureFunctionsWIFCredentials: """Azure Functions の Managed Identity を使った Workload Identity Federation 認証""" def __init__( self, service_principal_app_id: str, project_number: str, pool_id: str, provider_id: str, service_account_email: str, ) -> None: self.service_principal_app_id = service_principal_app_id self.project_number = project_number self.pool_id = pool_id self.provider_id = provider_id self.service_account_email = service_account_email def get_credentials(self) -> Credentials: identity_endpoint = os.environ["IDENTITY_ENDPOINT"] identity_header = os.environ["IDENTITY_HEADER"] credential_config = { "type": "external_account", "audience": ( f"//iam.googleapis.com/projects/{self.project_number}/locations/global/" f"workloadIdentityPools/{self.pool_id}/providers/{self.provider_id}" ), "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", "token_url": "https://sts.googleapis.com/v1/token", "credential_source": { "url": f"{identity_endpoint}?resource={self.service_principal_app_id}&api-version=2019-08-01", "headers": {"X-IDENTITY-HEADER": identity_header}, "format": {"type": "json", "subject_token_field_name": "access_token"}, }, "service_account_impersonation_url": ( f"https://iamcredentials.googleapis.com/v1/projects/" f"-/serviceAccounts/{self.service_account_email}:generateAccessToken" ), } credentials, _ = load_credentials_from_dict( credential_config, scopes=["https://www.googleapis.com/auth/cloud-platform"] ) return credentials class VertexAIClaudeClient: """Vertex AI Claude クライアント""" def __init__(self, project_id: str, region: str, credentials: Credentials) -> None: self._client = AnthropicVertex( region=region, project_id=project_id, credentials=credentials, ) def generate(self, prompt: str, model: str = "claude-sonnet-4-5@20250929") -> str: message = self._client.messages.create( model=model, max_tokens=1024, messages=[{"role": "user", "content": prompt}], ) return message.content[0].text import azure.functions as func app = func.FunctionApp() # クライアントはコールドスタートのコストを避けるためモジュールレベルで初期化 _wif = AzureFunctionsWIFCredentials( service_principal_app_id="<SERVICE_PRINCIPAL_APPLICATION_ID>", project_number="<YOUR_PROJECT_NUMBER>", pool_id="azure-pool", provider_id="azure-provider", service_account_email="vertex-ai-workload@<YOUR_GCP_PROJECT_ID>.iam.gserviceaccount.com", ) _client = VertexAIClaudeClient( project_id="<YOUR_GCP_PROJECT_ID>", region="us-east5", credentials=_wif.get_credentials(), ) @app.route(route="generate", methods=["POST"]) def generate(req: func.HttpRequest) -> func.HttpResponse: prompt = req.get_json().get("prompt", "") if not prompt: return func.HttpResponse("prompt is required", status_code=400) result = _client.generate(prompt) return func.HttpResponse(result, mimetype="text/plain") 参照: Workload Identity Federation の構成 認証設定(ローカルDevContainer環境) ローカルDevContainerを使用する場合の手順です。DevContainerはコンテナ内で開発環境を構築するため、コンテナ内にgcloud CLIをインストールし、ADCの設定を行う必要があります。また、コンテナ内ではブラウザを直接起動できないため、認証方法にも工夫が必要です。Google Cloudのアカウントが割り当てられている前提です。 私もDevContainerでの認証は最初かなりハマりました。「ブラウザが開かない!」となったところで、後述する --no-launch-browser オプションに気づいて解決した経験があります。同じところで詰まっている方はぜひ参考にしてみてください。 gcloud CLIのインストール(DevContainer内) DevContainerではコンテナビルド時にgcloud CLIをインストールしておくのがベストプラクティスです。 Dockerfile に以下を追記することでインストールできます。 RUN apt-get update && apt-get install -y apt-transport-https ca-certificates gnupg curl \ && curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | \ gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg \ && echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" \ > /etc/apt/sources.list.d/google-cloud-sdk.list \ && apt-get update && apt-get install -y google-cloud-cli \ && apt-get clean && rm -rf /var/lib/apt/lists/* Dockerfileを更新したら、コンテナを再ビルド( Rebuild Container )してください。これでDevContainer内からgcloudコマンドが使えます。 参照: gcloud CLI をインストールする サービスアカウントの権限借用設定 ユーザーアカウントがサービスアカウントのトークンを生成できるように、 serviceAccountTokenCreator ロールを付与します。 この操作には resourcemanager.projects.setIamPolicy 権限が必要です。 gcloud iam service-accounts add-iam-policy-binding \ SERVICE_ACCOUNT_EMAIL \ --member="user:YOUR_EMAIL_ADDRESS" \ --role="roles/iam.serviceAccountTokenCreator" ADCの設定(権限借用) サービスアカウントの権限借用を使って、ローカルADCファイルを作成します。 gcloud auth application-default login \ --impersonate-service-account=SERVICE_ACCOUNT_ID@PROJECT_ID.iam.gserviceaccount.com このコマンドを実行するとブラウザ認証が開き、認証完了後にサービスアカウントの権限借用を使ったADCが設定されます。以降、Python SDKなどがこの認証情報を自動的に利用します。 参照: サービスアカウントの権限借用を使用して、ローカル ADC ファイルを作成 代替手段: –no-launch-browserオプション DevContainer内でブラウザが起動できない場合の代替手段として、 --no-launch-browser オプションも利用できます。 gcloud auth application-default login --no-launch-browser このコマンドを実行すると、認証用のURLがターミナルに表示されます。ホストマシンのブラウザでそのURLを開いて認証を完了し、表示された認証コードをターミナルに貼り付けてください。 サービスアカウントの権限借用を設定せずに済むため手軽ですが、認証されるのはユーザーアカウント自身のため、サービスアカウントベースのアクセス制御が必要な場合は前述の権限借用方式を使用してください。 Global vs Regionalエンドポイント Vertex AIのClaudeモデルには、 Global と Regional の2種類のエンドポイントがあります。 項目 Global Regional ルーティング 動的に最適なリージョンへ 指定リージョンのみ 料金 追加料金なし 10%のプレミアム料金 データ所在地の保証 なし あり ユースケース 一般的な利用 データ所在地の要件がある場合 Globalエンドポイントは、リクエストを自動的に最適なリージョンにルーティングするため、可用性が高く、追加料金もかかりません。特にデータ所在地の要件がなければ、まずはGlobalを選んでおけば間違いないでしょう。 Regionalエンドポイントは、指定したリージョン内でのみデータが処理されることが保証されます。コンプライアンス上、データの所在地を制御しなければならないケースで活躍します。 なお、10%のプレミアム料金が発生するのはClaude Sonnet 4.5・Haiku 4.5以降のモデルのみ。それ以前のモデル(Sonnet 4、Opus 4など)は既存の料金体系のままです。 コード上では、 region パラメータで使い分けます。 # Globalエンドポイント client = AnthropicVertex(region="global", project_id="your-project-id") # Regionalエンドポイント(例: 東京に近いリージョン) client = AnthropicVertex(region="asia-southeast1", project_id="your-project-id") 参照: Vertex AI – Claude models Python SDKのセットアップ Vertex AI経由でClaudeを使うには、Anthropic Python SDKのVertex AI拡張をインストールします。 pip install -U google-cloud-aiplatform google-auth "anthropic[vertex]" anthropic[vertex] をインストールするだけで、 AnthropicVertex クライアントがすぐ使えます。 サンプルコード 基本的なテキスト生成 最もシンプルなテキスト生成のサンプルです。 from anthropic import AnthropicVertex # クライアントの初期化 client = AnthropicVertex( region="us-east5", project_id="your-gcp-project-id", ) # メッセージの送信 message = client.messages.create( model="claude-sonnet-4-5@20250929", max_tokens=1024, messages=[ {"role": "user", "content": "Vertex AIとは何ですか?簡潔に教えてください。"} ], ) print(message.content[0].text) region と project_id は自分の環境に合わせて変更してください。ADCが設定されていれば、認証情報は自動的に読み取られます。 ストリーミングレスポンス 長い応答を逐次的に受け取りたい場合は、ストリーミングを使います。 from anthropic import AnthropicVertex client = AnthropicVertex( region="us-east5", project_id="your-gcp-project-id", ) # ストリーミングでメッセージを送信 with client.messages.stream( model="claude-sonnet-4-5@20250929", max_tokens=1024, messages=[ {"role": "user", "content": "Pythonで簡単なWebサーバーを作る方法を教えてください。"} ], ) as stream: for text in stream.text_stream: print(text, end="", flush=True) print() # 最後に改行 messages.stream() を使うと、レスポンスが生成されるたびにチャンク単位でテキストを受け取れます。チャットアプリケーションのようなリアルタイムな応答表示に便利です。 トラブルシューティング Vertex AIからClaudeを利用する際によく遭遇するエラーとその対処法をまとめました。 「Permission denied」エラー google.api_core.exceptions.PermissionDenied: 403 Permission denied on resource project 原因 : ADCに設定されたアカウントにVertex AIの利用権限がない。 対処法 : roles/aiplatform.user ロールが付与されているか確認する gcloud auth application-default login を再実行して認証情報を更新する 「Model not found」エラー anthropic.NotFoundError: 404 Model not found 原因 : 指定したモデルがModel Gardenで有効化されていない、またはリージョンで利用できない。 対処法 : Model Gardenで対象モデルが有効化されているか確認する 指定したリージョンでモデルが利用可能か確認する( サポートリージョン を参照) 「Could not automatically determine credentials」エラー google.auth.exceptions.DefaultCredentialsError: Could not automatically determine credentials. 原因 : ADCファイルが存在しない、または読み取れない。 対処法 : gcloud auth application-default login を実行してADCを再設定する 環境変数 GOOGLE_APPLICATION_CREDENTIALS が意図しないパスを指していないか確認する 「Quota exceeded」エラー anthropic.RateLimitError: 429 Rate limit exceeded 原因 : APIの呼び出しレートまたはトークン数の上限に達した。 対処法 : しばらく待ってからリトライする Google Cloud Consoleの IAMと管理 > 割り当て でクォータの引き上げをリクエストする さいごに 今回は、Vertex AIからClaudeモデルを利用するための設定手順を一通り紹介しました。 ポイントをまとめると以下の通りです。 Model Gardenでの有効化 が必要(APIの有効化だけでは不十分) ローカル開発にはADC が最も手軽( gcloud auth application-default login ) GCP外からのアクセスにはWorkload Identity Federation を使うことでサービスアカウントキーの配布が不要になる。 --allowed-audiences にはサービスプリンシパルのApplication IDを指定し、 --attribute-condition で認証対象を絞り込むのがベストプラクティス Globalエンドポイント は追加料金なしで可用性が高いのでおすすめ Python SDKは anthropic[vertex] をインストールするだけで使える GCPを既に利用している方にとっては、Vertex AI経由でClaudeを使うことで既存のIAM管理や課金体系にそのまま統合できるのが大きなメリットです。ぜひ試してみてください! 最後までお読みいただき、ありがとうございます! 参照: Vertex AI – Use Claude models Anthropic Vertex AI ドキュメント Workload Identity Federation ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Vertex AIでClaudeモデルを使おう!GCPからの設定ガイド完全版 first appeared on SIOS Tech Lab .
アバター
要約 PRのレビューコメントをAzure OpenAIで分析し、GitHub Copilotの設定ファイルを半自動で更新します AIが直接コミットしないHuman-in-the-Loop設計で、誤ったルール混入を防げます gpt-5-miniなら1回の分析コストは1円未満。Before/Afterで実際にGitHub Copilot Code ReviewのPRレビュー精度が変わることを確認しました はじめに こんにちは、サイオステクノロジーの藤井です。 Pull Requestのレビューをしているときに「この指摘、前のPRでも同じことを言った気がする」ってことありますよね。私はありました。 今回は、そんなレビュアーの不満を解消する仕組みを作ってみました。日々同じ指摘を繰り返すことに疲弊している、シニアエンジニアやテックリードの方々の一助になれば幸いです。 背景:レビューコメントは「暗黙知の宝庫」である チーム開発には、明文化されていない「暗黙知」がつきものです。昨日の設計会議で決まった命名規則、先週の障害対応で得た「このパターンは使わない」という教訓、半年かけて育ててきたプロジェクト固有のベストプラクティス、「ほかの実装者のPRレビューで指摘された事」なんていう実装者側にもどうにも知りえない場合も多々あります。 PRをレビューする際、これらを踏まえた視点は品質担保に不可欠ですが、これらがわざわざWikiやREADMEに書き起こされることは稀です。 ここにGitHub Copilot Code Reviewを導入しても、素のAIはチーム固有の暗黙知を知りません。設定ファイルでルールを教えることは可能ですが、新たな暗黙知が生まれるたびに手動で設定ファイルをメンテし続ける運用は、どう考えても面倒で長続きしません。 結果として、AIがスルーした暗黙知を、人間のレビュアーが何度も繰り返し指摘する羽目になります。 こうして人間のレビューコストは膨らみ、開発のボトルネックになっていきます。レビュアーは疲弊しますが、一方で、彼らが残したレビューコメントは生きた知見が詰まった「情報の宝庫」です。 毎回コストをかけて宝(知見)を生み出しているのに、PRがマージされると同時に、それはコードの海に沈んで捨てられてしまう。これは非常にもったいない状態です。 その情報を拾い上げて、GitHub Copilotの設定に自動で反映できれば、同じ指摘を繰り返す手間を減らせると思います。そう考えて、 PRのレビューコメントをAIで分析して、GitHub Copilotの設定ファイルを半自動で更新するフィードバックループ を作ってみました。次の章では、その仕組みを説明していきます。 作ったものの概要 システム構成図 登場するコンポーネントとその関係は次の通りです。 分析に使用するAIとしては、ソースコードがAIに学習されるリスクを避けたかったので、Azure OpenAIを選びました。 エンタープライズ契約の環境であれば、機密情報である実装コードを入力しても、モデルの学習に二次利用されないことが保証されているからです。 ユースケースのワークフロー 「何が起きるか」の流れはシンプルです。 ①実装者が問題のあるPRを作成します ②GitHub Copilotにレビューさせますが、汎用的な指摘しかできないので、当然、暗黙知な問題は見逃します。 ③仕方が無いので、人間のレビュアーが指摘します。 ④実装者が指摘内容を直し、マージします。 ⑤PRマージをトリガーにGitHub Actions が起動し、PRのコメントやdiffを取得します。 ⑥Python スクリプトがAOAIにコメント等の分析を依頼し、どのようにCopilotの設定ファイルを更新すべきが出力されます ⑦設定ファイルの更新PR(学習PR) を自動作成します。 ⑧人間が内容を確認してマージ ⑨同様な問題を持つ次のPR が作成されます ⑩Copilotno設定ファイルにその問題点を指摘する様に記載されたので、GitHub Copilotは見逃さずに指摘できます。 直接コミットせずPRを挟むHuman-in-the-Loop設計を採用しています。AIの誤判断や個人の好みがそのままルールになるのを防ぐための安全弁です。人間が「このルールはおかしい」と判断すればPRを閉じるだけで済みます。 2種類の設定ファイルを使い分ける GitHub Copilot の設定ファイルには2種類あります。 ファイル 役割 例 .github/copilot-instructions.md コーディングルール :コードを書くときに守るべき規約 「エラー出力には print() ではなく必ず共通の logger.error() を使用すること」 .github/instructions/review.instructions.md レビュー観点 :レビュー時に確認すべき視点 「ログ出力の内容に、パスワードや個人情報(PII)などの機密情報が含まれていないか確認する」 AIはレビューコメントを読み、「これはコードの書き方の規約か、それともレビュー時のチェック観点か」を判断して適切なファイルを更新します。1つの指摘が両方に当てはまる場合は両方に反映します。 詳しくはこちらの記事をご覧ください。 GitHub Copilot設定5種を網羅!生産性を最大化する使い分け術 リジェクトPRされたPRからも知見を得る ワークフローのトリガーによって学習モードが変わります。 トリガー 学習モード PRがマージされた Best Practices として抽出(「こうすると良い」) learn-from-rejection ラベル付きでクローズされた Anti-Patterns として抽出(「DO NOT …」形式) リジェクトされたPRは「やってはいけないパターン」の宝庫だと思います。問題のあるコードを含むPRに learn-from-rejection ラベルを付けてクローズするだけで、アンチパターンとして学習させられる様にしました。 セットアップ方法 必要なもの Azure OpenAI リソース (gpt-5-mini を推奨。コストが低く1回のPR分析は1円未満) Endpoint・API Key・デプロイ名を用意します GitHub リポジトリ (導入先) Step 1: 3ファイルをコピーする 以下の3つのファイルを導入先リポジトリの同じパスに配置します。 付録として、この記事の最下部にファイル全体を置いておきます。 .github/workflows/learn-from-review.yml ワークフローの定義ファイルです。PRがマージされたとき、または、’learn-from-rejection’ラベル付きでクローズされたときに発火します。 jobs: analyze-and-update: # 「マージされた」 OR 「'learn-from-rejection'ラベル付きでクローズされた」場合に実行 # マージした場合は学習対象 # 破棄するPRの内、学習対象にしたいものは'learn-from-rejection'ラベルを付ける # ai-learning/* ブランチからのPRは除外(再帰防止) if: >- !startsWith(github.head_ref, 'ai-learning/') && ( github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true || (github.event.pull_request.state == 'closed' && contains(github.event.pull_request.labels.*.name, 'learn-from-rejection')) ) runs-on: ubuntu-latest # 同一PR番号の並列実行を防止 concurrency: group: "learn-${{ github.event.pull_request.number || github.event.inputs.pr_number }}" cancel-in-progress: true env: TARGET_PR_NUMBER: ${{ github.event.inputs.pr_number || github.event.pull_request.number }} steps: ・・・ scripts/learn_from_pr.py PRの内容を取得し、AOAIにクエリーを投げるpythonスクリプトです。プロンプトはここに記載しているので、必要に応じて調整してください。 ・・・ def main(): """PRレビューから学習してルールファイルを更新するメイン処理。""" # --- 1. GitHubデータの取得 --- g = Github(auth=github.Auth.Token(os.environ["GITHUB_TOKEN"])) repo = g.get_repo(os.environ["GITHUB_REPOSITORY"]) pr = repo.get_pull(int(os.environ["PR_NUMBER"])) # --- 1-a. Issue コメント (PR全体のコメント): 人間のみ --- comments = pr.get_issue_comments() all_comments_text = "\n".join( [f"{c.user.login}: {c.body}" for c in comments if c.user.type != "Bot"] ) # --- 1-b. Review コメント: スレッド構造で再構築 (Bot方針変更) --- review_comments = pr.get_review_comments() all_reviews_text = build_review_threads(review_comments) # --- 1-c. PR変更ファイル情報 --- pr_files_summary = get_pr_files_summary(pr) # 何も人間の会話がなければ終了 if not all_comments_text and not all_reviews_text: print("No human comments found. Skipping.") return ・・・ scripts/requirements-action.txt ワークフローでpythonを動かすときに必要なライブラリの定義です。 Step 2: GitHub Secrets を登録する Settings > Secrets and variables > Actions で以下を追加します。 Secret名 値 AZURE_OPENAI_API_KEY AzureポータルのAPIキー AZURE_OPENAI_ENDPOINT https://your-resource.openai.azure.com/ GITHUB_TOKEN はGitHub Actionsが自動生成するため不要です。 Step 3: Actionsの権限を設定する Settings > Actions > General > Workflow permissions で以下を有効化します。 ✅ Read and write permissions ✅ Allow GitHub Actions to create and approve pull requests この設定がないとPRの自動作成がエラーになります。 Step 4: 指示書ファイルを初期化する AIが書き込む先のファイルを作っておきます。 .github/copilot-instructions.md : # プロジェクト コーディングルール ## 言語・フレームワーク - Python 3.8以上 - Webフレームワーク: FastAPI ## コーディング規約 - PEP 8に従う - 型ヒントを積極的に使用する .github/instructions/review.instructions.md : --- applyTo: "**/*.py" excludeAgent: "coding-agent" description: "Python PR Review専用ガイドライン - Code Reviewのみに適用" --- # Python Code Review Guidelines このファイルはGitHub Copilot Code Review専用の指示です。 PRレビュー時にのみ適用され、Copilot ChatやCoding Agentには適用されません。 ## 出力形式 - **日本語で出力してください** - 問題の重要度を明記してください - Critical: 必ず修正が必要(セキュリティ、データ損失リスク) - High: 修正を強く推奨(バグ、パフォーマンス問題) - Medium: 修正を推奨(コード品質、保守性) - Low: 改善提案(スタイル、軽微な改善) ## レビュー観点(Best practices) フロントマター( --- ブロック)を先に書いておくことには意味があります。スクリプト側でフロントマターを本文から分離して保持し、AI出力の本文に再結合してから書き込む設計になっています。AIにフロントマターを渡さないことで、AI出力がフロントマターを上書き・削除するリスクをゼロにできます。 def split_frontmatter(content: str) -> tuple[str, str]: """Markdownコンテンツからフロントマター(---~---)を分離する。 Returns: (frontmatter, body) のタプル。 フロントマターがない場合は ("", content) を返す。 """ match = re.match(r"^(---\n.*?\n---\n?)(.*)", content, re.DOTALL) if match: return match.group(1), match.group(2) return "", content def join_frontmatter(frontmatter: str, body: str) -> str: """フロントマターと本文を再結合する。 フロントマターが空の場合は本文のみ返す。 フロントマターの末尾に改行がない場合は追加する。 """ if not frontmatter: return body if not frontmatter.endswith("\n"): frontmatter += "\n" return frontmatter + body 実際に動かしてみた — Before/Afterで確認する 「本当に学習でレビュー精度が上がるのか」を確かめるために、検証してみました。 Before:Copilotは何も言わない GET /items/{item_id} を以下のように実装したPRを作成し、Copilot Code Reviewを実行しました。 このプロジェクトでは主キーで検索するときは、db.query().filter()ではなく、db.get()を使ってほしいのですが、完全にスルーされました。ルールを知らないので当然ですね。 学習させる このPRに人間がインラインコメントを追加しました。 PRをマージすると、ワークフローが起動します。Azure OpenAIがコメントを分析し、学習PRが自動生成されます。 生成された学習PRには次の変更が含まれています。 この学習PRをマージしました。 After:Copilotが「リポジトリのルール違反」を指摘する 同パターンのコードを含む別のPR( DELETE /items/{item_id} )を作成してCopilot Code Reviewを実行します。 Copilot Code Reviewからこんな指摘が来ました。 学習がレビュー内容に反映されました。しかも「一般的なベストプラクティスとして」ではなく「このリポジトリのルールとして」指摘しています。チームの知見がCopilotに渡ったことが確認できました。 まとめ この記事で紹介したこと: PRレビューのコメントをAzure OpenAIで分析し、GitHub Copilotの設定ファイルを半自動で更新するフィードバックループを作ってみました 一度レビューすれば、同じ指摘を二度としなくて良いようになります Human-in-the-Loop設計で誤ったルール混入のリスクを抑えながら自動化できます gpt-5-mini なら1回の分析コストは1円未満。シニアエンジニアのレビュー時間削減と比べれば十分ペイすると思います セットアップは3ファイルのコピーとSecretsの設定だけです。下のコードをそのままコピーして使っていただき、チームの文化・課題・プロセスに合わせてプロンプトやトリガー条件を自由にカスタマイズしてみてください。 このループが育てば、Copilotはチームのことを少しずつ知っていくと思います。 コード全体 .github/workflows/learn-from-review.yml name: Learn from PR Review (AOAI) on: pull_request: types: [closed] # PRが閉じられた時だけ発火 workflow_dispatch: inputs: pr_number: description: '対象のPR番号' required: true type: number permissions: contents: write # copilot-instructions.md を更新してPRを作るために必要 pull-requests: write # PRを作成するために必要 issues: read # コメントを読むために必要 jobs: analyze-and-update: # 「マージされた」 OR 「'learn-from-rejection'ラベル付きでクローズされた」場合に実行 # マージした場合は学習対象 # 破棄するPRの内、学習対象にしたいものは'learn-from-rejection'ラベルを付ける # ai-learning/* ブランチからのPRは除外(再帰防止) if: >- !startsWith(github.head_ref, 'ai-learning/') && ( github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true || (github.event.pull_request.state == 'closed' && contains(github.event.pull_request.labels.*.name, 'learn-from-rejection')) ) runs-on: ubuntu-latest # 同一PR番号の並列実行を防止 concurrency: group: "learn-${{ github.event.pull_request.number || github.event.inputs.pr_number }}" cancel-in-progress: true env: TARGET_PR_NUMBER: ${{ github.event.inputs.pr_number || github.event.pull_request.number }} steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install dependencies run: | pip install -r scripts/requirements-action.txt - name: Analyze PR and Generate Instructions id: analyze env: # Azure OpenAIの接続情報 AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }} AZURE_OPENAI_DEPLOYMENT: "gpt-5-mini" # デプロイ名 AZURE_OPENAI_API_VERSION: "2024-12-01-preview" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ env.TARGET_PR_NUMBER }} run: | python scripts/learn_from_pr.py - name: Create Pull Request with Updates # 変更なし時はPR作成スキップ if: steps.analyze.outputs.files_updated == 'true' uses: peter-evans/create-pull-request@v6 with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: "docs: update review instructions from PR #${{ env.TARGET_PR_NUMBER }}" title: "🤖 Update Review Instructions (Learning from PR #${{ env.TARGET_PR_NUMBER }})" # Pythonスクリプトが出力した change_log をここに埋め込む body: | ## AI Analysis Report ${{ steps.analyze.outputs.pr_body }} --- *Auto-generated by Learn-from-Review Workflow* Reference PR: #${{ env.TARGET_PR_NUMBER }} branch: ai-learning/pr-${{ env.TARGET_PR_NUMBER }} base: main # 【対策: ルールの肥大化・間違いの防止】 # AIが直接mainにコミットするのではなく、必ず「PR」を作成する。 # 人間が最終確認(Merge)しない限り、ルールは適用されない。 scripts/learn_from_pr.py import os import re import sys import json import time from typing import Any import github from github import Github from github.GithubException import GithubException from github.PaginatedList import PaginatedList from github.PullRequest import PullRequest from github.PullRequestComment import PullRequestComment from openai import AzureOpenAI # 設定 REVIEW_INSTRUCTIONS_FILE = ".github/instructions/review.instructions.md" CODING_RULES_FILE = ".github/copilot-instructions.md" # --- トークン制限対策の定数 --- # コメント+diff_hunk の合計文字数がこの閾値を超えたら要約LLMを発動 COMMENT_CHAR_THRESHOLD = 15_000 # TODO 要約するLLMのコンテキストサイズに入らないぐらいやり取りが長い場合の対策 # 要約後もこの文字数を超える場合は切り捨て SUMMARY_MAX_CHARS = 12_000 # --- フロントマター保護・空コンテンツガードの定数 --- MIN_CONTENT_CHARS = 50 # 最低限の文字数(これ未満は異常とみなす) # --- APIリトライ設定 --- MAX_RETRIES = 3 RETRY_BASE_DELAY = 2 # 秒 def split_frontmatter(content: str) -> tuple[str, str]: """Markdownコンテンツからフロントマター(---~---)を分離する。 Returns: (frontmatter, body) のタプル。 フロントマターがない場合は ("", content) を返す。 """ match = re.match(r"^(---\n.*?\n---\n?)(.*)", content, re.DOTALL) if match: return match.group(1), match.group(2) return "", content def join_frontmatter(frontmatter: str, body: str) -> str: """フロントマターと本文を再結合する。 フロントマターが空の場合は本文のみ返す。 フロントマターの末尾に改行がない場合は追加する。 """ if not frontmatter: return body if not frontmatter.endswith("\n"): frontmatter += "\n" return frontmatter + body def validate_content(content: str, file_label: str) -> bool: """生成されたコンテンツが有効かどうかを検証する。 空白のみ・改行のみ・最低文字数未満の場合はFalseを返す。 フロントマター再結合後に「フロントマターしかない」状態も検知する。 """ if not content or not content.strip(): print( f"WARNING: {file_label} の生成コンテンツが空です。書き込みをスキップします。" ) return False _, body = split_frontmatter(content) if not body.strip(): print( f"WARNING: {file_label} のコンテンツはフロントマターのみです。" "書き込みをスキップします。" ) return False if len(content.strip()) < MIN_CONTENT_CHARS: print( f"WARNING: {file_label} の生成コンテンツが短すぎます " f"({len(content.strip())} chars < {MIN_CONTENT_CHARS})。" "書き込みをスキップします。" ) return False return True def call_llm_with_retry( client: Any, model: str, messages: list[dict], **kwargs: Any ) -> Any: """LLM API呼び出しを指数バックオフ付きリトライで実行する。 最大MAX_RETRIES回リトライし、2秒→4秒→8秒の指数バックオフで待機する。 """ for attempt in range(MAX_RETRIES): try: return client.chat.completions.create( model=model, messages=messages, **kwargs ) except Exception as e: if attempt == MAX_RETRIES - 1: print(f"LLM API call failed after {MAX_RETRIES} attempts: {e}") raise delay = RETRY_BASE_DELAY * (2**attempt) print( f"LLM API call failed (attempt {attempt + 1}/{MAX_RETRIES}): {e}. " f"Retrying in {delay}s..." ) time.sleep(delay) def build_review_threads(review_comments: PaginatedList[PullRequestComment]) -> str: """レビューコメントからスレッド構造を再構築する。 Bot含む全コメントを取得し、in_reply_to_id でスレッドを組み立てる。 人間のコメントが1件でも含まれるスレッドはBot発言も保持し、 Botのみのスレッドは除外する。 各コメントには diff_hunk(コメント箇所の周辺数行)を付加し、 AIが「ここ」を正しく理解できるようにする。 """ # 全コメントをID→コメントの辞書に格納 comments_by_id: dict[int, PullRequestComment] = {} # スレッド: 親ID → [子コメント, ...] threads: dict[int, list[Any]] = {} all_comments = list(review_comments) for c in all_comments: comments_by_id[c.id] = c # in_reply_to_id がある場合は返信、なければ親(スレッドルート) if c.in_reply_to_id and c.in_reply_to_id in comments_by_id: parent_id = c.in_reply_to_id threads.setdefault(parent_id, []).append(c) else: # 自身がスレッドルート threads.setdefault(c.id, []) # スレッド単位で人間の関与チェック&フォーマット formatted_threads: list[str] = [] for root_id, replies in threads.items(): root_comment = comments_by_id[root_id] thread_comments = [root_comment] + replies has_human = any(c.user.type != "Bot" for c in thread_comments) if not has_human: # Botのみのスレッドは除外 continue lines: list[str] = [f"[Thread #{root_id}]"] for c in thread_comments: user_type = "Bot" if c.user.type == "Bot" else "Human" lines.append(f" {c.user.login} ({user_type}) on {c.path}:") # diff_hunk はコメント箇所の周辺数行のみ。@@ヘッダーに行番号が含まれる。 if c.diff_hunk: lines.append(" --- diff context ---") for dl in c.diff_hunk.splitlines(): lines.append(f" {dl}") lines.append(" --- comment ---") lines.append(f" {c.body}") lines.append("") formatted_threads.append("\n".join(lines)) return "\n\n".join(formatted_threads) def get_pr_files_summary(pr: PullRequest) -> str: """PR変更ファイル一覧と変更行数を取得し、テキストで返す。""" files = pr.get_files() lines: list[str] = [] for f in files: lines.append( f"- {f.filename}: +{f.additions}/-{f.deletions} (status: {f.status})" ) return "\n".join(lines) if lines else "(変更ファイルなし)" def summarize_comments(client: Any, model: str, text: str) -> str: """コメント+diff_hunk のテキストが長すぎる場合、LLMで要約する。 COMMENT_CHAR_THRESHOLD 以下の場合はそのまま返す。 """ if len(text) <= COMMENT_CHAR_THRESHOLD: return text print( f"Comment text exceeds {COMMENT_CHAR_THRESHOLD} chars " f"({len(text)} chars). Summarizing with LLM..." ) summary_prompt = ( "以下のPRレビュー議論を、**合意事項・決定事項・変更依頼の結論**を" "中心に要約してください。\n" "問題提起→議論→結論の流れが分かるように構造を維持してください。\n" "コード例やファイルパスなど、ルール抽出に必要な具体情報は保持してください。\n" "要約は日本語で出力してください。" ) response = call_llm_with_retry( client, model, [ {"role": "system", "content": summary_prompt}, {"role": "user", "content": text}, ], ) summarized = response.choices[0].message.content print(f"Summarized: {len(text)} -> {len(summarized)} chars") # 要約後もまだ長すぎる場合は切り捨て if len(summarized) > SUMMARY_MAX_CHARS: print( f"Summarized text still too long ({len(summarized)} chars). " f"Truncating to {SUMMARY_MAX_CHARS} chars." ) summarized = ( summarized[:SUMMARY_MAX_CHARS] + "\n\n(以降省略: 要約後も長すぎるため切り捨て)" ) return summarized def main(): """PRレビューから学習してルールファイルを更新するメイン処理。""" # --- 1. GitHubデータの取得 --- g = Github(auth=github.Auth.Token(os.environ["GITHUB_TOKEN"])) repo = g.get_repo(os.environ["GITHUB_REPOSITORY"]) pr = repo.get_pull(int(os.environ["PR_NUMBER"])) # --- 1-a. Issue コメント (PR全体のコメント): 人間のみ --- comments = pr.get_issue_comments() all_comments_text = "\n".join( [f"{c.user.login}: {c.body}" for c in comments if c.user.type != "Bot"] ) # --- 1-b. Review コメント: スレッド構造で再構築 (Bot方針変更) --- review_comments = pr.get_review_comments() all_reviews_text = build_review_threads(review_comments) # --- 1-c. PR変更ファイル情報 --- pr_files_summary = get_pr_files_summary(pr) # 何も人間の会話がなければ終了 if not all_comments_text and not all_reviews_text: print("No human comments found. Skipping.") return # 既存レビュールール(更新対象) review_frontmatter = "" try: current_review_rules_raw = repo.get_contents( REVIEW_INSTRUCTIONS_FILE ).decoded_content.decode() # フロントマター分離(AIにはbodyのみ渡す) review_frontmatter, current_review_rules = split_frontmatter( current_review_rules_raw ) except GithubException: current_review_rules = "(まだファイルはありません)" # プロジェクトのコーディングルール(更新対象) try: current_coding_rules = repo.get_contents( CODING_RULES_FILE ).decoded_content.decode() except GithubException: current_coding_rules = "(まだファイルはありません)" # --- 2. Azure OpenAIへの接続 --- client = AzureOpenAI( api_key=os.environ["AZURE_OPENAI_API_KEY"], api_version=os.environ["AZURE_OPENAI_API_VERSION"], azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"], ) # PRの状態を確認 is_merged = pr.merged state_status = "MERGED (Approved)" if is_merged else "REJECTED (Closed)" # --- レビュー承認状態の取得 --- reviews = pr.get_reviews() review_states: list[str] = [] for r in reviews: if r.state in ("APPROVED", "CHANGES_REQUESTED", "COMMENTED"): review_states.append(f" - {r.user.login}: {r.state}") review_state_text = ( "\n".join(review_states) if review_states else " (レビューなし)" ) # プロンプトの構築 system_prompt = f""" あなたはチームの2つのルールファイルを管理するAIです。 【管理対象ファイル】 1. `review.instructions.md` — GitHub Copilot Code Review が参照するレビュー観点 - 「何をレビューで指摘すべきか」「どんな観点でチェックするか」を記載する - 例: 「例外処理が広すぎないか確認する」「N+1クエリがないか確認する」 - セクション名はPR固有の表現にせず、汎用的な名前にする(×「このPRの運用で追加しておくべき~」 → ○「プロジェクト固有のレビュー観点」) - 個別のレビュー観点にseverityを付けない。severityはレビューコメントの出力時に判断するものであり、観点の定義に含めない - フロントマター(YAML header: `---`〜`---`)は出力に含めない。本文のMarkdownのみを出力する 2. `copilot-instructions.md` — プロジェクト全体のコーディングルール - 「コードを書くときに守るべきルール」を記載する - 例: 「ベアexceptは使わず具体的な例外クラスを指定する」「関数は50行以内にする」 【分類の指針】 - コーディングルール: 「〜すべき」「〜してはいけない」という具体的な書き方の規約 - レビュー観点: 「〜を確認する」「〜に注意する」というレビュー時のチェック項目 - 1つの指摘が両方に該当する場合は、両方に適切な形で反映する - どちらにも該当しない(単なる議論やPR固有の話題)場合は反映しない - `copilot-instructions.md` に既にコーディング規約として記載されている内容は、`review.instructions.md` 側に「〜を確認する」と重複させない 【分析の指針】 - PRが MERGED の場合: 推奨パターン(Best Practices)としてルールを抽出する - PRが REJECTED の場合: - 禁止事項(Anti-Patterns)として「〜してはいけない(DO NOT ...)」形式で記述する - 可能なら、なぜダメか(×)と代わりに何をすべきか(○)のパターンを含める - 単なる重複や方針変更によるCloseであればルール化しない 【Botコメントの扱い】 Botのコメントは対話の文脈として含まれている。Botの指摘に対する人間の応答(「対応しない」「別PRで対応済み」等)にも注目し、チームの方針として反映する。Bot単独の発言(人間の応答がないもの)は除外済み。 【現在のレビュールール (review.instructions.md) ※フロントマター除去済み】 {current_review_rules} 【現在のコーディングルール (copilot-instructions.md)】 {current_coding_rules} 【良い設定ファイルの品質基準】 - 抽象化された原則 + 代表例1つ: 個別のPR事例をそのまま書かず、汎用的な原則に昇華し具体例を1つ添える - 機械的に判定・実行可能なルール: 「適切に」「必要に応じて」等の曖昧な表現を避け、誰が読んでも同じ解釈になる明確な指示にする - ドメイン・言語・目的別の階層化: 関連するルールをセクションでグループ化する - 指示に特化: 冗長な背景説明は省き、「〜する」「〜を確認する」等の指示文のみで構成する - 各レビュー観点は1-2文で簡潔に: 必要なら悪い例(×)と良い例(○)のパターンを含める - 重複排除: 同じ趣旨のルールが異なる表現で複数存在しないようにする 【更新ルール】 1. 既存ルールとの統合: 単なる追記ではなく、既存の項目とマージして簡潔にする 2. 衝突の解決: 既存ルールと矛盾する場合、新しい議論を優先し、その旨を change_log に明記する 3. 2ファイル間の重複回避: 同じ内容を両方のファイルに書かない 4. 変更不要な場合: 該当ファイルの内容が変わらない場合は、現在の内容をそのまま返す 【出力形式】 以下のJSON**のみ**を出力する。Markdownコードブロックで囲まない。 `updated_review_instructions` にはフロントマター(`---`〜`---`)を含めない。 {{ "updated_review_instructions": "更新後のreview.instructions.mdの本文(Markdown, フロントマターなし)", "updated_coding_rules": "更新後のcopilot-instructions.mdの全内容(Markdown)", "review_instructions_changed": true/false, "coding_rules_changed": true/false, "change_log": "何を変更したか、なぜ変更したか、どちらのファイルに振り分けたかの解説" }} """ pr_description = pr.body or "(説明なし)" user_prompt = f""" PR Context: - Title: {pr.title} - Description: {pr_description} - State: {state_status} - Review Approvals: {review_state_text} Changed Files: {pr_files_summary} Issue Comments (PR全体): {all_comments_text} Review Comments (インラインコメント・スレッド構造): {all_reviews_text} """ # --- コメント量が多すぎる場合の要約 --- combined_comments = f"{all_comments_text}\n{all_reviews_text}" summarized_comments = summarize_comments( client, os.environ["AZURE_OPENAI_DEPLOYMENT"], combined_comments ) if summarized_comments != combined_comments: # 要約された場合、ユーザープロンプトを差し替え user_prompt = f""" PR Context: - Title: {pr.title} - Description: {pr_description} - State: {state_status} - Review Approvals: {review_state_text} Changed Files: {pr_files_summary} Review Discussion (要約済み): {summarized_comments} """ # デバッグ print("system_prompt:") print(system_prompt) print("user_prompt:") print(user_prompt) # --- リトライ付きLLM呼び出し --- response = call_llm_with_retry( client, os.environ["AZURE_OPENAI_DEPLOYMENT"], [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}, ], response_format={"type": "json_object"}, ) # JSONパース try: result = json.loads(response.choices[0].message.content) except json.JSONDecodeError: print("Failed to parse JSON response.") sys.exit(1) change_log = result.get("change_log", "No details provided.") files_updated = [] # --- 3. レビュールールの更新 --- if result.get("review_instructions_changed", False): new_review_content = result.get("updated_review_instructions", "") # フロントマター再結合 full_review_content = join_frontmatter(review_frontmatter, new_review_content) # 空コンテンツガード if validate_content(full_review_content, REVIEW_INSTRUCTIONS_FILE): os.makedirs(os.path.dirname(REVIEW_INSTRUCTIONS_FILE), exist_ok=True) with open(REVIEW_INSTRUCTIONS_FILE, "w") as f: f.write(full_review_content) files_updated.append(REVIEW_INSTRUCTIONS_FILE) print(f"Updated {REVIEW_INSTRUCTIONS_FILE}") # --- 4. コーディングルールの更新 --- if result.get("coding_rules_changed", False): new_coding_content = result.get("updated_coding_rules", "") # 空コンテンツガード if validate_content(new_coding_content, CODING_RULES_FILE): os.makedirs(os.path.dirname(CODING_RULES_FILE), exist_ok=True) with open(CODING_RULES_FILE, "w") as f: f.write(new_coding_content) files_updated.append(CODING_RULES_FILE) print(f"Updated {CODING_RULES_FILE}") if not files_updated: print("No rule changes needed from this PR.") # 変更なしをGitHub Actions出力に設定 if "GITHUB_OUTPUT" in os.environ: with open(os.environ["GITHUB_OUTPUT"], "a") as f: f.write("files_updated=false\n") return # --- 5. GitHub Actions出力 --- if "GITHUB_OUTPUT" in os.environ: with open(os.environ["GITHUB_OUTPUT"], "a") as f: f.write("files_updated=true\n") delimiter = f"EOF_DELIMITER_{int(time.time())}" updated_files_summary = "\n".join(f"- `{path}`" for path in files_updated) body = f"### 更新されたファイル\n{updated_files_summary}\n\n### 変更内容\n{change_log}" f.write(f"pr_body<<{delimiter}\n") f.write(body) f.write(f"\n{delimiter}\n") print("Done.") if __name__ == "__main__": main() scripts/requirements-action.txt PyGithub>=2.0,<3.0 openai>=1.0,<2.0 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post GitHub Copilot Code Reviewの自動学習ループ — 一度指摘すれば次からはAIが怒ってくれる仕組みを作ってみた first appeared on SIOS Tech Lab .
アバター
エピソード紹介 Ep.1 – クリーンアーキテクチャとは Ep.2 – 認証方式の実践的な紹介 Ep.3 – ER設計と監査ログ Ep.4 – RepoScanner の実装とテスト Ep.5 – Copilot プロンプトを効率化 ← 今回はこちら こんな方へ特におすすめ プロンプトを使って日常のコーディング作業を自動化したい開発者 Copilotがプロジェクトの独自ルールを無視したコードを出してくるとお悩みの方 チーム開発におけるAIの活用方針をルール化したい方 Copilotにもクリーンアーキテクチャで実装させたい方 概要 こんにちは。サイオステクノロジーのはらちゃんです! GitHub Copilotは非常に便利ですが、そのままコードを生成させると「動くけど、プロジェクトの規約違反になっているコード」を出力してしまい、修正に手間取ることがよくあります。 — 本シリーズでは、Copilotを活用しつつ、クリーンアーキテクチャに沿って小規模なプロダクト「RepoScanner」を設計・実装した経緯をまとめます。 このエピソードでは、私が実践したCopilotを使った作業を高速化するための「ルールの二層管理」と、AIとの対話ログの適切な運用方法について解説します。 解決方法 ルールは「機械向け」と「人間向け」に分ける 2つのディレクトリを用いてCopilotの実装を調整します。 Copilotにプロジェクトのルールを認識させる際、すべてのルールを長々と一つのドキュメントに書いても、AIは重要なポイントを拾い切れません。 また、ドキュメントは参照され得るが優先度は低いため、ドキュメントだけだと自動適用は保証されません。 そこで、以下の2つのディレクトリを用いて、ルールを役割ごとに分割します。 .github/instructions/ Copilotへ機械的に適用する「実効ルール」 docs/reference/... 人間も読むための「解説ルール」 Step1. 機械向けの実効ルール Copilotへ機械的に適用する「リポジトリ固有の指示/ガードレール」です。 .github/ └── instructions/ .github/instructions/ 配下には、自動補完やCopilotエージェントの振る舞い制約・除外ルールなどを書きます。 ここは 英語で、短く明確に書く のが鉄則です。 英語はAIモデルが最も安定して解釈できる言語であり、指示を短くすることでAIがルールを見落とすのを防ぎます。 実効ルールの活用法 各ファイルの先頭にYAML フロントマター(例:  applyTo ,  excludeAgent )を付けることで、「どのファイル群に対してこのルールを適用するか」を細かく制御できます。 これにより、フロントエンドのルールがバックエンドのコード生成に悪影響を与えるのを防げます。 applyTo 適用例 ファイル 適応範囲 */*.py 特定の拡張子にのみ src/components/**/*.tsx UIコンポーネント -**/*.js -**/*.jsx 複数のパターン 最初に、クリーンアーキテクチャの思想に則らせるために書いたファイルは以下の通りです。 指示には依存方向や禁止事項など実装で守りたい制約を書いています。 この記述により、リポジトリ全体に適用するエージェント向けガードレールの役割を持っています。 記述例 repo-common-guardrails.instructions.md --- applyTo: "**/*" --- # Repository Common Guardrails ## Scope - Apply these rules across the repository for all agents. - Enforce only the minimum rules required to preserve clean architecture. ## Dependency Direction - Keep dependency flow from outer layers to inner layers. - Do not import infrastructure implementations from domain or use-case layers. - Introduce or use interfaces at boundaries when connecting inner logic to outer implementations. ## Layer Integrity - Respect declared layer boundaries and dependency direction in existing architecture docs. - Review imports in modified files to avoid accidental auto-import drift. ## Validation - Validate changed behavior with targeted checks. - Run architecture-related checks when dependency direction may have changed. ※もっと  .github/instructions/  について詳しく知りたい方は こちらのブログ へ Step2. ドキュメントで人間とAI向けの解説ルール 機械向けの短い英語ルールだけでは、背景や具体的なコード例が伝わりません。 そこで、人が読んでも分かりやすいように、補助説明 / 背景を  docs/reference/...  に日本語で詳細を書きます。 解説ルールの活用法 AIは作業時に、①の短いガードレールで「やってはいけないこと」を把握し、詳細が必要な場合は②のドキュメントを参照しにいきます。 記述例(抜粋) docs/reference/copilot/clean-architecture.md # クリーンアーキテクチャ このプロジェクトでは、変更に強くテストが容易な設計を実現するためにクリーンアーキテクチャを採用しています。コード作業の際は本ドキュメントをガイドラインとしてご参照ください。 ## 目次 --- ## 1. 詳細ドキュメント 以下はこのドキュメントおよび関連ファイルの責務です。 | ファイル | 責務 | | --------------------------------------------------------- | ------------------------------------------------------------------- | | `docs/reference/copilot/clean-architecture.md` | 概要・原則・索引 | | `docs/reference/copilot/clean-architecture-migration.md` | 実装例、移行手順、`migration-backlog.md` の運用ルール | | `docs/reference/copilot/clean-architecture-testing-ci.md` | テスト戦略、CI / lint スニペット、注意点 | | `docs/reference/copilot/migration-backlog.md` | `migration-backlog.md` の運用ルール(作業時の一時ファイル作成手順) | | `.github/copilot-instructions.md` | AI が実行時に参照する全体の実効ルール(Single Source of Truth) | | `.github/instructions/*.instructions.md` | パス単位の実効ルール(対象ファイル群ごとの行動制限) | ## 2. AIガードレール総覧 このプロジェクトの AI ガードレールは、次の 2 層で運用します。 - 実効ルール(enforceable): `.github/copilot-instructions.md` と `.github/instructions/*.instructions.md` - 解説ルール(explanatory): `docs/reference/copilot/*.md` 重要なのは、同じルール本文を両方へ重複記載しないことです。実効ルールの一次ソースは `.github` 側とし、`docs` 側は背景、判断基準、運用手順を説明します。 ### 2.1 初期適用範囲(2026-02-20時点) 初期フェーズでは次を優先します。 - repo 共通ルール(依存方向保護、最小変更、検証必須) - docs ルール(記述品質、リンク整備、SoT更新) 初期適用範囲と `.github/instructions` の対応は次のとおりです。 | 適用範囲 | 実効ルールファイル | | --------- | ------------------------------------------------------------- | | repo 共通 | `.github/instructions/repo-common-guardrails.instructions.md` | | docs | `.github/instructions/docs-common-guardrails.instructions.md` | backend / frontend / functions の個別ルールは、運用上の失敗事例が蓄積したタイミングで段階的に追加します。 ### 2.2 変更フロー -- 中略 -- ## 7. 各層のつながり(依存方向) このプロジェクトでは、依存方向を常に外側から内側へ保ちます。 ```text functions(最外部) -> infrastructure(外側実装) -> use-cases(アプリ手順) -> domain(最内部ルール) ``` 重要なのは「実行時の呼び出し方向」と「コードの依存方向」を区別することです。 - 実行時の呼び出しは、外側から内側へ流れます(例: HTTP Trigger -> Use Case)。 - コードの依存も、同じく外側から内側へ向けます。 - `domain` / `use-cases` は `infrastructure` 実装を直接 `import` しません。 - 永続化や外部APIは、`domain` 側のインターフェースを `infrastructure` が実装して接続します。 ### 接続イメージ - `domain`: ルールとインターフェース(例: `IUserRepository`)を定義する - `use-cases`: `domain` のインターフェースを受け取って業務フローを実行する - `infrastructure`: `domain` のインターフェースを実装してDBや外部サービスへ接続する - `functions`: エントリポイントで実装を組み立て(DI)て `use-cases` を呼び出す | 層 | 主な接続先(実行時) | コード依存 | 補足 | | -------------- | -------------------------- | ---------- | ----------------------------------------- | | functions | infrastructure / use-cases | 外 -> 内 | 依存を組み立ててユースケースを起動する | | infrastructure | use-cases / domain | 外 -> 内 | `domain` のインターフェース実装を提供する | | use-cases | domain | 内側のみ | 業務フローを実行し、実装詳細は知らない | | domain | なし(最内部) | 内側のみ | ルールと抽象のみを持つ | ## 8. 用語集(クリーンアーキテクチャ) | 用語 | 説明 | | ---------------------------------- | ------------------------------------------------------------------------ | | Entity(エンティティ) | ドメインの中核概念。ビジネスルールを表すオブジェクト。 | | Use Case(ユースケース) | アプリケーション固有の手順。入力を受けて業務フローを実行する。 | | Interface(インターフェース) | 層の境界で依存を抽象化する契約。実装詳細を内側に持ち込まないために使う。 | | Dependency Direction(依存方向) | 依存の向き。外側の層が内側へ依存し、内側は外側へ依存しない。 | | Dependency Inversion(依存性逆転) | 内側が抽象(interface)を定義し、外側がその抽象を実装する考え方。 | | DI(Dependency Injection) | 必要な依存を外から注入すること。テスト容易性と交換容易性を高める。 | | Adapter(アダプター) | 外部ライブラリやSDKを内側の契約へ合わせる実装。 | | Source of Truth(SoT) | 仕様の一次情報源。構造ルールは `docs/spec` を正とする。 | プロンプトの実践 上記の「二層管理」のドキュメントが整備されていると、日常のプロンプトによる指示が劇的に簡略化されます。 作業テンプレート チャットに以下のような短い指示を投げるだけで、AIは裏でドキュメントを読み込み、正確な実装を行ってくれます。 @workspace @clean-architecture.md に従って、 `ListSnapshotSummariesUseCase` を実装してください。 出力: items, total_count 依存: SnapshotQueryRepository.list_snapshots(limit, offset, filter) -> (items, total) 適用前後 Before 手作業でクリーンアーキテクチャの制約を意識しながら、インターフェースを作成し、ユースケースの骨格を書き、依存関係を修正し、テストを手書きする必要があります。 時間がかかり、ミスも起きやすいです。 After テンプレート化された短いプロンプトを投げるだけで、プロジェクトの規約に沿った一貫した構造のユースケースと、それに紐づくテストコードが自動生成されます。 開発者は「コードを書くこと」から「AIが出力したロジックのレビュー」に集中できるようになります。 +α: AIとの「対話ログ」を記録 設計に関する議論などCopilotとのチャット履歴を docs/history/... に出力して保持することをお勧めします。 あとでどのような意図で設計をしたかが明確になり、中断作業の再開やレビューのハードルが下がります。 さらに、テンプレートをREADMEに追記すると一貫したログになります。 記録用のマークダウンテンプレート例 # Copilot History ## 目的 - `docs/history/copilot/` は、Copilot 関連の判断履歴を残すための場所です。 - 実装の背景と意思決定の経緯を、後から追跡できる形で記録します。 ## 命名ルール - 形式: `YYYY-MM-DD-<topic>.md` - 日付は作成日(`YYYY-MM-DD`)を使用します。 - `<topic>` は英小文字のケバブケースで、内容を短く表す語にします。 - 1ファイルにつき1つの主要決定を記録します。 ## 記載テンプレート # <タイトル> ``` - 日付: YYYY-MM-DD - ステータス: Proposed | Accepted | Superseded - 対象: (対象範囲。例: docs/history/copilot) ``` ## 背景 (なぜこの判断が必要になったか) ## 決定 (何を決めたか) ## 影響 (期待される効果・制約・運用上の注意) ## フォローアップ (次に必要な作業。なければ「なし」) まとめるのが面倒な場合は、JSONでそのままのチャット記録を出力することもできます。 Copilot Chatに「JSONでそのままのチャット記録を出力」と指示してください。 出力例 [ { "role": "user", "content": ".github/instructions配下に配置したdocsはCopilotに必ず参照される?どういう仕組み? (See <attachments> above for file contents. You may not need to search or read the file again.)" }, { "role": "assistant", "content": "簡潔に説明します — .github/instructions 配下の docs は「Copilot のリポジトリ固有指示(repository instructions)」として拡張機能に読み込まれ、候補生成時のコンテキストに組み込まれます。仕組みと注意点は以下です。\n\n- どう作用するか... このように記録を残すことで、レビュアーに対して「Copilotとこういう議論をした結果、この実装になっています」と背景を共有でき、レビューのコミュニケーションコストが大幅に下がります。 ログをマージすべきか しかし、ここで疑問が生じます。 「 出力したチャットログは、すべてメインブランチにマージして残すべきなのか?」 結論から言うと、すべてのログを蓄積すべきではありません。 ログには「資産」と「ノイズ」が混在しているため、適切に仕分けて昇華する必要があります。 作業中ログ 開発のプロセスが分かる →コードが動いた後は期限切れ 重要な決定 なぜこの設計にしたのか分かる資産 ルール 2から抽出した常に守るべきもの 資産かノイズか… 分類 ログの内容 アクション 資産 (残すべき) 決定の背景 ドキュメントや実効ルールに昇華してマージする 採用しなかったアプローチとその拒絶理由 ドキュメントに昇華してマージする プロジェクト独自の固有名詞や概念 ドキュメントに昇華してマージする 横断的なルール 実効ルールに昇華してマージする ノイズ (消すべき) コードを見ればわかること PRに記載するか破棄し、ファイルとしては残さない 一時的なタスクリストやエラーの修正過程 PRに記載するか破棄し、ファイルとしては残さない 情報の適材適所 どこに残すかでマージの是非を決めるログの内容に応じて、最終的にどこに記載するかを振り分けます。 必要な情報 決定セクションに書かれるようなリポジトリに残すべき内容を指します。 実効ルール(.github/instructions): 常に守るべき横断的なルール README / docs配下: 画面の仕様、詳細な背景、アーキテクチャの決定事項 不要な情報 フォローアップセクションに書かれるような一時的に利用して破棄する内容を指します。 GitHub Issue: 未着手の課題 PRの説明文: 実装時の思考プロセス(マージ後はPR上に残れば十分) フィーチャーブランチのみ: 単なる作業ログファイル(メインブランチにはマージしない) ログの昇華ルール チャットログを整理する際は、以下の2つの問いを自分に投げかけてください。 このログの中で、他の開発者が知らないと困る『独自の禁止事項』はある? → ある場合は、 .github/instructions にルールとして追加する。 このログは、将来コードをリファクタリングする時の助けになる? → ある場合は docs/history/ にマークダウンとして残す。 → ない場合は PR の説明欄にコピペして、ログファイル自体は削除する。 まとめ 今回は、Copilotを使って設計やリファクタリングを高速化し、出力のブレを無くすための仕組み作りについて解説しました。 ルールの二層管理 機械向けの短い英語ルールと、人間向けの詳細な日本語ルールを分ける。 短いプロンプトで実行 ルール基盤を整えれば、指示は極端に短くて済む。 ログの保持 AIとの対話は資産とノイズに分け、必要なルールだけを抽出してリポジトリに還元する。 「Copilotが言うことを聞いてくれない!」と感じている方は、ぜひ指示書の整備とログの昇華から始めてみてください。 開発体験が劇的に向上するはずです! 参考 copilot-instructions.md を分割したい?applyTo パターンで解決 GitHub Copilot をつかいこなすための copilot-instructions.md の使いかた ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Copilot × Clean Architecture | プロンプト効率化 first appeared on SIOS Tech Lab .
アバター
2026年4月8日(水)~4月10日(金)の3日間、Japan DX Weekに出展いたします。 生成AIの活用が進む中、OSSライセンスや著作権リスクはますます見えにくくなっています。 サイオステクノロジーのブースでは、コード解析によりOSSを可視化する「SCANOSS」をご紹介します。ソースコードレベルでOSSの利用状況を把握し、リスクの早期発見と適切な管理を支援します。 「サイオスOSSよろず相談室」では、SCANOSSと親和性の高いSBOM管理ツールの導入・運用もサポートします。 あわせて、Excel AIエージェントやRAG構築、AI駆動開発など、AIを”現場の即戦力”にする取り組みもご紹介します。   展示のご紹介はこちら   無料のお申込みはこちら The post 4/8(水)~4/10(金) Japan DX Weekに出展します first appeared on SIOS Tech Lab .
アバター
2026年4月8日(水)~4月10日(金)の3日間、Japan DX Weekに出展いたします。 生成AIの活用が進む中、OSSライセンスや著作権リスクはますます見えにくくなっています。 サイオステクノロジーのブースでは、コード解析によりOSSを可視化する「SCANOSS」をご紹介します。ソースコードレベルでOSSの利用状況を把握し、リスクの早期発見と適切な管理を支援します。 「サイオスOSSよろず相談室」では、SCANOSSと親和性の高いSBOM管理ツールの導入・運用もサポートします。 あわせて、Excel AIエージェントやRAG構築、AI駆動開発など、AIを”現場の即戦力”にする取り組みもご紹介します。   展示のご紹介はこちら   無料のお申込みはこちら The post 4/8(水)~4/10(金) Japan DX Weekに出展します first appeared on SIOS Tech Lab .
アバター
ども!最近 GitHub Wiki の管理に頭を悩ませている龍ちゃんです。 既製アクション 1 本と YAML 30 行で、 docs/wiki/ を GitHub Wiki に自動同期できる仕組みを作ったので共有しますね。 皆さん、GitHub Wiki 使っていますか? 複数のドキュメントを参照したいときはすごく便利なんですが、管理が面倒だったりしますよね。Wiki の情報って完全に人間向けのコンテキスト情報で、あった方がいいのはわかっているんですけど、そんな作業は後回しになりがちで。 最近、AI にドキュメント作成をやらせているんですが、そうすると「人間しか読まないコンテキスト情報をどう管理して、どこに置くべきか?」って悩むんですよね。そんなときに Wiki。でも管理にはあまりコストをかけたくない。同じ悩みがあったので、解決方法を見つけて書いておきますね。 今回の内容です。 GitHub Wiki の「惜しい」ポイントと、docs/ との両立方法 GitHub Wiki の正体(独立した Git リポジトリ) docs/wiki/ → GitHub Actions → Wiki の構成とワークフロー 実機検証で踏んだ罠 5 選 Wiki 整理を AI Agent Skill に任せるアイデア それぞれ順番に解説していきますね。 GitHub Wiki の「惜しい」ところ GitHub Wiki は手軽で閲覧性も高いんですよね。ページをリスト表示できて、サイドバーで構造化できて、非エンジニアにも見せやすい。でも、使っていると管理面でちょっと不満が出てきます。 よくある不満点はこのあたりです。 Web UI でしか編集できない(と思われがち) → ローカルで書けない、差分もわかりにくい PR レビューに乗らない → 誰かが勝手に書き換えても気づけない コードとドキュメントのバージョンが連動しない → リリースタイミングでドキュメントが古いまま AI ツール(Claude Code 等)から直接アクセスしにくい → docs/ は読めても Wiki は別管理 結果として「Wiki やめて docs/ だけにしよう」という判断をするチームも多いんですが、Wiki UI の閲覧性は捨てがたいんですよね。 実は両方活かす方法があります。 docs/wiki/ に Markdown を書いて、CI/CD で Wiki に自動同期する方法です。 以前、 HTMLで保存してる奴、全員Markdownにしろ という記事でも書いたんですが、AI 向けに最適化するなら Markdown で Git 管理が基本です。Wiki も同じ考え方で管理できるんですね。 GitHub Wiki の正体: 独立した Git リポジトリ これが面白くて、GitHub Wiki の裏側には REPO.wiki.git という独立した Git リポジトリがいるんですよね。なので以下のコマンドで普通に clone できます。 git clone https://github.com/OWNER/REPO.wiki.git 実は GitHub CLI のマニュアル を見ても Wiki コマンドはないし、 REST API にも専用エンドポイントはないんですが、Git 操作はできます。つまり CI/CD に乗せられるんですね。 構成: docs/wiki/ → GitHub Actions → Wiki 構成はこんな感じです。 使っているのはこのあたりです。 項目 内容 使用 Action Andrew-Chen-Wang/github-wiki-action@v4 認証 GITHUB_TOKEN + permissions: contents: write (PAT 不要) 実行時間 10〜24秒 Zenn で docs/ から Wiki への自動同期を Node.js で実装している記事 もあって、こちらはかなり丁寧に作り込まれています。今回の記事では既製アクション 1 つでサクッと完結する方法を紹介しますね。 Claude Code設計術:AIフレンドリーなドキュメント管理 で書いた「GitHub に情報を集約する」設計思想の延長線上にある話ですね。Wiki もその一部に組み込んでしまう感じです。 実装ステップ: 4つの作業で完成 ステップ 1: Wiki の初期化(1分・手動) まず GitHub リポジトリの Settings > Features で Wiki が有効になっているか確認します。 確認できたら Wiki タブを開いて「Create the first page」で初期ページを作ります。 内容は何でも OK です。あとで同期したときに上書きされます。ここを飛ばすと .wiki.git が存在しない状態になって、後で Actions が失敗するので注意です。 ステップ 2: docs/wiki/ にドキュメントを配置 ディレクトリ構成はこんな感じです。 docs/wiki/ ├── Home.md # トップページ(必須) ├── _Sidebar.md # サイドバー ├── _Footer.md # フッター ├── Guide-Getting-Started.md # プレフィックス命名でフラット管理 ├── Guide-Configuration.md ├── Reference-API.md └── images/ └── architecture.png # [[images/architecture.png]] で参照 ファイル命名のポイントをまとめておくと、 ダッシュ - 区切りにする(Wiki 表示でスペースに変換される) サブディレクトリは使えるが、Wiki の Pages 一覧ではフラット表示になる 同名ファイルが異なるサブディレクトリにあると衝突するので注意 プレフィックスで擬似階層を表現するのが安全( Guide- 、 Reference- 等) ぶっちゃけ、サブディレクトリが必要なほど分量が多い Wiki はつらいんですよね。Wiki 側の Pages 一覧はフラットに並ぶだけですし、Web UI でページタイトルにスラッシュ / を入れても自動でハイフンに変換されるので子ページも作れません。 フラットで管理できる程度にまとめておくのが、Wiki としてはちょうどいい粒度だと思います。 ちなみにサブディレクトリごとに独自の _Sidebar.md を置くと、そのフォルダ配下のページでは別のサイドバーが表示されます。どうしても階層が必要な場合はこれで擬似的に対応できますね。 ステップ 3: GitHub Actions ワークフローを追加 以下を .github/workflows/wiki-sync.yml にコピペするだけです。 name: Sync docs/wiki to GitHub Wiki on: push: branches: [main] paths: - 'docs/wiki/**' - '.github/workflows/wiki-sync.yml' workflow_dispatch: permissions: contents: write jobs: sync-wiki: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Sync to Wiki uses: Andrew-Chen-Wang/github-wiki-action@v4 with: path: docs/wiki/ strategy: clone preprocess: false disable-empty-commits: true commit-message: "wiki: sync from ${{ github.sha }}" 各設定のポイントです。 設定 内容 paths: docs/wiki/** docs/wiki/ の変更時のみ発火(他の変更では動かない) permissions: contents: write GITHUB_TOKEN 認証(PAT 不要) strategy: clone 差分同期(Wiki の編集履歴を保持) disable-empty-commits: true 変更なし時の空コミット防止 workflow_dispatch 手動実行も可能 ステップ 4: push → 自動同期を確認 main に push するだけで Wiki に反映されます。Actions タブで実行結果を確認して(10〜24秒で完了します)、Wiki タブでページ表示を目視確認すれば OK です。 実機検証で踏んだ罠 5 選 実際に検証してみると地味にハマる箇所がいくつかあったんですよね。他ではあまり書かれていないと思うので、「何が起きたか → なぜか → どう直したか」で共有しておきます。 罠 1: Wiki 記法 [[A|B]] のパイプが逆 [[Guide-Getting-Started|Getting Started]] と書くと、リンク先が「Getting Started」というページ名で解釈されるんですよね。そのページは存在しないので赤リンクになります。 HTML で確認するとこうなっていました。 <a class="internal absent" href="/wiki/Getting-Started"> 整理するとこうなります。 記法 リンク先 結果 [[Guide-Getting-Started|Getting Started]] Getting-Started 存在しないページ → 赤リンク [[Guide-Getting-Started]] Guide-Getting-Started 正常リンク 対策はパイプなしの [[Guide-Getting-Started]] にするだけです。表示は自動でスペース区切りに変換されます。 罠 2: 画像は Wiki 添付記法 [[images/xxx.png]] 一択 Markdown の ![alt](images/xxx.png) を使うと、ページによっては「Could not find version “images”」エラーが出ることがあります。raw URL(wiki 本体どちらも)だと表示されないケースがありました。 安定して表示されるのは Wiki 添付記法の [[images/xxx.png]] だけでした。画像参照はこれ一択と覚えておくといいですね。 罠 3: ファイル名のダッシュ → ページ名でスペースに変換 Guide-Getting-Started.md というファイルは、Wiki では「Guide Getting Started」というページタイトルになります。 整理するとこういう関係です。 項目 値 ファイル名 Guide-Getting-Started.md ページタイトル表示 Guide Getting Started Wiki 記法での参照 [[Guide-Getting-Started]] 「ファイル名 = Wiki 記法での参照名」と覚えておくとすっきりします。 罠 4: サブディレクトリの同名ファイルは衝突する Wiki の名前空間はフラットなんですよね。サブディレクトリ自体は使えるんですが、 guide/page.md と reference/page.md を両方置くと同じ page として衝突します。Pages 一覧にも 1 つしか出てこないです。 サブディレクトリを使う場合はファイル名をユニークにするか、プレフィックス命名でフラット管理するのが安全ですね。 罠 5: Wiki 未初期化だと Actions が失敗する Wiki タブで 1 ページも作っていないと .wiki.git が存在しない状態になっています。Actions が push 先を見つけられずエラーになるんですね。 ステップ 1 で書いた「手動で 1 ページ作る」というのはこれを回避するためです。内容は何でも OK なので、1 分で終わります。 さらに一歩: Wiki 整理用 Agent Skill を作る この 5 つの罠、正直僕も全部覚えてられないんですよね。なので AI の Agent Skill に組み込んでしまって、罠の回避も Wiki 整理もまるごと任せるようにしています。 この記事自体を Skill の参考資料として読み込ませれば、AI が知見を活かして動いてくれるんですよね。例えばこんな使い方ができます。 「この機能の Wiki ページを作って」→ 正しい命名・記法で docs/wiki/ に作成 「Sidebar を更新して」→ 新規ページを反映した _Sidebar.md を生成 「このドキュメントを Wiki 用に整理して」→ フラット構成 + Wiki 記法に変換 画像は [[images/xxx.png]] 、パイプ記法は使わない、ファイル名はプレフィックス命名 — この辺を全部 AI が自動でやってくれる状態にしておくと、あとは push するだけで Wiki に反映されます。人間はレビューだけですね。 例えば Claude Code の Skill 参考資料にはこんな感じで書いておきます。 # Wiki 記法ルール ## ページリンク - パイプ記法 [[A|B]] は使わない - ファイル名そのままで参照: [[Guide-Getting-Started]] ## 画像 - Wiki 添付記法を使う: [[images/xxx.png]] - Markdown の ![alt](path) は使わない ## ファイル命名 - ダッシュ区切り: Guide-Getting-Started.md - プレフィックスで擬似階層: Guide-, Reference- 等 - サブディレクトリの同名ファイルは衝突するので注意 ## 配置先 - docs/wiki/ に配置 - push するだけで GitHub Actions 経由で Wiki に同期される これを Skill の references に入れておけば、「Wiki ページを作って」と言うだけで AI がルールに従って docs/wiki/ にファイルを作ってくれます。 おまけ: Mermaid も普通に使える GitHub Wiki は Mermaid をネイティブレンダリングしてくれます。実際に試してみたら、フローチャート・シーケンス図・クラス図すべて問題なく表示されました。 docs/wiki/ に書いた Mermaid がそのまま Wiki で図表として表示されるので、ドキュメントの表現力がぐっと上がりますね。 整理すると ここまでの話をまとめるとこんな感じです。 docs/ で一元管理 → PR レビューに乗る、バージョン管理される AI が docs/ を直接読み書き → push するだけで Wiki にも反映 Wiki UI は残る → 非エンジニアや外部向けの閲覧用として活用 以前の記事でも書いてきた「Markdown にしろ」→「GitHub に集約しろ」という流れの続きで、「Wiki も docs/ から同期しろ」というのが今回の話です。情報の一元化が AI 協業の基盤になるんですよね。 コラム: 既存 Wiki からの移行 すでに Wiki にページがある方向けの移行手順も書いておきます。 git clone https://github.com/OWNER/REPO.wiki.git で既存 Wiki をローカルにダウンロード メインリポジトリに `docs/wiki/` を作成してファイルを移す AI(Claude Code 等)を使って整理する   サブディレクトリをフラット化してプレフィックス命名に変換   画像参照を Wiki 添付記法( `[[images/xxx.png]]` )に統一   `_Sidebar.md` を再生成 ワークフローを追加して push → 同期開始 ステップ 3 は手作業でやると面倒なんですが、AI に任せると一瞬ですね。 まとめ 今回の内容をまとめます。 GitHub Wiki は「使いにくい」のではなく「使い方を変える」だけで快適になります 既製アクション 1 つ + YAML 30 行で docs/ → Wiki の自動同期が完成します 罠はありますが、5 つ知っておけば実用レベルです AI 時代のドキュメント管理は「すべて GitHub に」が正解ですね ほなまた〜 参考リンク Andrew-Chen-Wang/github-wiki-action — 今回使用した GitHub Action Adding or editing wiki pages – GitHub Docs — GitHub Wiki 公式ドキュメント docs/ から Wiki への自動同期を Node.js で実装 – Zenn — 本格的に作り込みたい方向け GitHub CLI マニュアル — gh CLI の公式リファレンス GitHub REST API ドキュメント — API エンドポイントの確認用 HTMLでブログ記事を保存してる奴、全員Markdownにしろ — AI 向け Markdown 最適化 Claude Code設計術:AIフレンドリーなドキュメント管理 — GitHub に情報を集約する設計思想 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post GitHub Wikiの管理が面倒?Actionsで自動同期する実践手順 first appeared on SIOS Tech Lab .
アバター
エピソード紹介 Ep.1 – クリーンアーキテクチャとは Ep.2 – 認証方式の実践的な紹介 Ep.3 – ER設計と監査ログ Ep.4 – RepoScanner の実装とテスト ← 今回はこちら Ep.5 – Copilot プロンプトを効率化 こんな方へ特におすすめ クリーンアーキテクチャの理屈は分かったけど、どこから書き始めるの?と疑問な方 クリーンアーキテクチャで小さな MVP を実装するワークフローに興味がある方 TDD(テスト駆動開発)を実務に取り入れて、壊れにくいコードを書きたい方 概要 こんにちは。サイオステクノロジーのはらちゃんです! シリーズ4本目となる今回は、いよいよ待望の実装編に突入します。 ここで大きな役割を果たすのが TDD(テスト駆動開発) です。テストファーストで進めることで、依存性の切り離しや境界の明確化が自然と行われます。 — 本シリーズでは、Copilotを活用しつつ、クリーンアーキテクチャに沿って小規模なプロダクト「RepoScanner」を設計・実装した経緯をまとめます。 このエピソードは、アプリのコア機能である「リポジトリ内のスナップショット一覧を取得する機能」に焦点を当てました。 さらに、「AIへの指示の出し方」や「テストの質の変化」についてもお伝えします。 実装前の準備 プロジェクトの全体像 実装作業を開始する前に、まずは「RepoScanner」の構成を整理しておきます。 目的 リポジトリのメタデータや集計結果を効率よく取得し、分析しやすくすること。 要件 スナップショット一覧のページング(limit / offset)、最大取得数の制限。 クリーンなユースケースを保つ「3つの設計ルール」 RepoScannerにおけるUse Cases層では、以下のルールを徹底しました。 入出力は DTO (Data Transfer Object) HTTPリクエストなどのオブジェクトは、そのまま使わず単純なデータクラスに変換する。 依存はインターフェースを介して注入(DI) DB処理などは、具体的な実装ではなく「Repository」などの抽象的な型に依存させる。 副作用の入り口を明示 「どこで外部APIを呼ぶ」など処理の流れがユースケースから分かる状態を保つ。 このように責務を分離することで、ユースケースが純粋なビジネスロジックだけに集中できる環境が整います。 クリーンアーキテクチャによる設計 コードの保守性を高めるため、クリーンアーキテクチャに従って責務を明確に分離しました。 Domain層 SnapshotSummary などのエンティティ Use Cases層 ListSnapshotSummariesUseCase Interface Adapters層 HTTP エンドポイントの制御 Infrastructure層 SnapshotQueryRepository ここで重要なのは、DBアクセスへの依存を必ず「インターフェース」経由にすることです。 その結果、内側のロジックが外側の技術的な詳細を知らなくて済む「依存性の逆転」が成立します。 設計上の要点 永続化(DBアクセス)への依存は、必ずリポジトリの「インターフェース」を経由させます。 これにより、内側(Use Cases層)が外側(Infrastructure層)の技術詳細を知らない状態となり、依存の逆転を保ちます。 → 詳細は エピソード1へ 階層ごとのテスト戦略 「どこからテストを書けばいいか分からない」という悩みは、クリーンアーキテクチャで解決します。 なぜなら、各階層の役割がはっきりしているため、テストの目的も自ずと定まるからです。 最優先: Use Cases層のユニットテスト ここでは「仕様としての正しさ」を検証します。 limit の上限クリッピングや、 offset の負値チェックなどが対象です。 このテストはフレームワーク更新や DB ドライバ変更に影響されない高速なフィードバックを得ることが可能です。 例えば、以下のような正常系のテストを用意してください。 /tests/use_cases/test_list_snapshot_summaries.py # 正常範囲の limit がそのままリポジトリへ渡されることを確認 class ListSnapshotSummariesUseCaseTest(unittest.TestCase): def test_execute_passes_limit_when_within_bounds(self) -> None: repository = FakeSnapshotQueryRepository() use_case = ListSnapshotSummariesUseCase(repository, max_read_limit=25) use_case.execute(limit=1, offset=0) self.assertEqual(1, repository.received_limit) use_case.execute(limit=25, offset=0) self.assertEqual(25, repository.received_limit) 外部依存はすべてモック化する ドメインモデルに正しく仕事を任せているか確認 優先: Infrastructure層のテスト 次に、 「 技術的な正しさ 」 を検証します。 SQLの組み立てが正しいか、LIMIT / OFFSETのパラメータ順序が間違っていないかといったパラメータ順序や DBから取得したデータをエンティティへ正しくマッピングできているかを確認できます。 /tests/frameworks&drivers/persistence/test_postgres_snapshot_query_repository.py # フィルタなしで LIMIT/OFFSET がパラメータに含まれ、`ORDER BY s.observed_at desc` が含まれる確認 class PostgresSnapshotQueryRepositoryTest(unittest.TestCase): def test_list_snapshot_summaries_without_filters(self) -> None: now = datetime(2026, 2, 24, tzinfo=UTC) cursor = _FakeCursor( rows=[ { "snapshot_id": UUID("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"), "fetch_run_id": UUID("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"), "target_repo_id": UUID("cccccccc-cccc-cccc-cccc-cccccccccccc"), "owner": "owner", "name": "repo", "default_branch": "main", "requested_at": now, "observed_at": now, "head_sha": "deadbeef", "scan_scope": "full", "status": "succeeded", "trigger_type": "manual", } ] ) with patch( "application.backend.src.infrastructure.persistence.postgres_snapshot_query_repository.get_cursor", return_value=_CursorContext(cursor), ): repository = PostgresSnapshotQueryRepository() items = repository.list_snapshot_summaries( limit=10, offset=5, user_id=None, target_repo_id=None ) self.assertEqual([10, 5], cursor.executed_params) どのようなSQL(またはリクエスト)を組み立てたか検証 インターフェースの「型」と「変換」を確認 最終: 統合テスト/E2E 最後に、GitHub Actionsでのデータ抽出からDB保存、そしてアプリでの読み取りまで、システム全体のフローが本番に近い環境で動作するかを確かめます。 /tests/http/test_main_api.py # ユースケースのクリッピングを利用して 200 を返すことを確認 class MainApiTest(unittest.TestCase): def test_list_snapshots_clamps_limit_and_returns_200(self) -> None: repository = _RecordingSnapshotRepository() cast(Any, main).list_snapshot_summaries_use_case = ListSnapshotSummariesUseCase( repository, max_read_limit=5 ) response = self.client.get("/snapshots?limit=999&offset=0") self.assertEqual(200, response.status_code) self.assertEqual(5, repository.received_limit) 本物のデータベースを使用 セットアップとクリーンアップの仕組みが必須 実装 TDD(テスト駆動開発) 進め方 TDDは、単にバグを防ぐためだけでなく、「設計を洗練させるためのツール」です。 以下の3サイクルで進めます。 Red: 失敗 仕様の最小ケースを満たす「テストコード」を書く。まだ実装がないので当然エラー。 Green: 成功 そのテストが通るように、最小限の「実装コード」を書く。 Refactor: リファクタリング テストが通る状態を保ったまま「設計ルール」に合わせてコードをきれいに整理。 サイクルのイメージ図です。 例えば、「1回の取得上限は100件まで」というルールはAPIの仕様ではなくドメインの規則です。これをユースケース層で確実に担保することで、フレームワークに依存しない堅牢なロジックが完成します。 【実践】スナップショット取得ユースケース 前回設計した snapshot テーブルに対して、「スナップショットを取得する」というユースケースをTDDで作ってみましょう。 Use Cases層のユニットテスト tests/use_cases/test_register_snapshot.py import unittest from uuid import UUID from datetime import UTC, datetime from application.backend.src.domain.entities.snapshot_summary import SnapshotSummary from application.backend.src.use_cases.list_snapshot_summaries import ListSnapshotSummariesUseCase class _FakeRepo: def __init__(self): self.called = False self.last_params = {} def list_snapshot_summaries(self, *, limit, offset, user_id, target_repo_id): self.called = True self.last_params = dict(limit=limit, offset=offset, user_id=user_id, target_repo_id=target_repo_id) now = datetime.now(UTC) return [SnapshotSummary( snapshot_id=UUID("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"), fetch_run_id=UUID("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"), target_repo_id=UUID("cccccccc-cccc-cccc-cccc-cccccccccccc"), owner="owner", name="repo", default_branch="main", requested_at=now, observed_at=now, head_sha="deadbeef", scan_scope="full", status="succeeded", trigger_type="manual" )] class ListSnapshotSummariesUseCaseTDD(unittest.TestCase): def test_offset_negative_raises_and_repo_not_called(self): repo = _FakeRepo() uc = ListSnapshotSummariesUseCase(repo, max_read_limit=100) with self.assertRaises(ValueError): uc.execute(limit=10, offset=-1) self.assertFalse(repo.called) def test_limit_zero_becomes_one(self): repo = _FakeRepo() uc = ListSnapshotSummariesUseCase(repo, max_read_limit=100) uc.execute(limit=0, offset=0) self.assertEqual(1, repo.last_params["limit"]) def test_limit_clamped_to_max(self): repo = _FakeRepo() uc = ListSnapshotSummariesUseCase(repo, max_read_limit=25) uc.execute(limit=999, offset=0) self.assertEqual(25, repo.last_params["limit"]) def test_passes_filters_and_offset(self): repo = _FakeRepo() uc = ListSnapshotSummariesUseCase(repo, max_read_limit=100) user_id = UUID("11111111-2222-3333-4444-555555555555") target_repo_id = UUID("66666666-7777-8888-9999-aaaaaaaaaaaa") uc.execute(limit=10, offset=5, user_id=user_id, target_repo_id=target_repo_id) self.assertEqual(5, repo.last_params["offset"]) self.assertEqual(user_id, repo.last_params["user_id"]) self.assertEqual(target_repo_id, repo.last_params["target_repo_id"]) if __name__ == "__main__": unittest.main() 仕上がったら想定するエラーかどうか、テストを走らせてみることをお勧めします。 Use Cases層 テストを書いた後で、初めて ListSnapshotSummariesUseCase クラスを実装します。 python class ListSnapshotSummariesUseCase: def __init__(self, snapshot_query_repository: SnapshotQueryRepository, max_read_limit: int) -> None: self.snapshot_query_repository = snapshot_query_repository self.max_read_limit = max_read_limit def execute(self, *, limit: int, offset: int, user_id: UUID | None = None, target_repo_id: UUID | None = None) -> list[SnapshotSummary]: if offset < 0: raise ValueError("offset must be greater than or equal to 0") safe_limit = max(1, min(limit, self.max_read_limit)) return self.snapshot_query_repository.list_snapshot_summaries( limit=safe_limit, offset=offset, user_id=user_id, target_repo_id=target_repo_id ) ペイロード検証をUse Cases層で記述 Interface Adapters層のバリデーションだけに依存せず、ドメインルールとしてテスト可能に このように1つテストを作成したら1つ実装するサイクルを作ると、意図したコード実装になりやすいです。 プロジェクトによって、1ファイルごとにするか全体のテストを先に作ってしまうかは調整すべきだと感じました。 Interface Adapters層 ユースケースの実装後、モック化していたDBへの具体的な接続処理は、後からインターフェースの中身として差し替えます。 python class PostgresSnapshotQueryRepository(SnapshotQueryRepository): def list_snapshot_summaries(self, *, limit: int, offset: int, user_id: UUID | None, target_repo_id: UUID | None) -> list[SnapshotSummary]: conditions: list[sql.Composable] = [] params: list[object] = [] if user_id is not None: conditions.append(sql.SQL("fr.user_id = %s")) params.append(user_id) if target_repo_id is not None: conditions.append(sql.SQL("fr.target_repo_id = %s")) params.append(target_repo_id) # where_clause 組み立て、limit/offset を params に追加して実行 Infrastructure層 さいごに、マイグレーションは以下のように追加します。 create table history_event ( history_event_id uuid primary key default gen_random_uuid(), user_id uuid not null references app_user(user_id) on delete restrict, fetch_run_id uuid references fetch_run(fetch_run_id) on delete set null, event_type text not null, happened_at timestamptz not null default now(), summary text not null ); create table operation_log ( operation_log_id uuid primary key default gen_random_uuid(), actor_user_id uuid references app_user(user_id) on delete set null, action text not null, target_type text not null, target_id text, happened_at timestamptz not null default now(), result text not null check (result in ('success', 'failure')), error_code text, trace_id text not null ); 監査・運用観点で operation_log を用意 CI 実行の失敗や権限エラーをすぐ追跡できる Copilotを活用するプロンプト術 クリーンアーキテクチャの骨格を作る際、AIにゼロからコードを書かせると、層の境界が曖昧になりがちです。 そこで、「型」と「依存関係」を明示することで、一発で実用的なコードを得ることができます。 実際のプロンプト例 以下のように指示を出すと、Copilotはビジネスロジックを純粋なPythonコードとして抽出し、テストしやすい構造で出力してくれます。 RepoScanner のスナップショット一覧を返す `ListSnapshotSummariesUseCase` を実装してください。 入力: limit, offset, optional filter 出力: SnapshotSummary のリストと total_count 既存エンティティ: SnapshotSummary(id, repo_name, observed_at, summary) 依存: SnapshotQueryRepository.list_snapshots(limit, offset, filter) -> (items, total) テスト(unittest スタイル)を先に示してください。 生成されたコード(抜粋) プロンプトの指示通り、まずはUse Cases層のコードが生成されます。 APIの検証とは別に、ビジネスロジックが純粋なPythonコードとして抽出され、テストしやすい構造になりました。 application/backend/src/use_cases/list_snapshot_summaries.py class ListSnapshotSummariesUseCase: def __init__(self, repo: SnapshotQueryRepository, max_read_limit: int = 100): self.repo = repo self.max_read_limit = max_read_limit def execute(self, limit: int, offset: int = 0): if limit <= 0: raise ValueError("limit must be > 0") if offset < 0: raise ValueError("offset must be >= 0") limit = min(limit, self.max_read_limit) return self.repo.list_snapshots(limit=limit, offset=offset) Interface Adapters層はリクエストを受け取り、ユースケースを呼び出すだけの「薄い」層になります。 Python @app.get("/snapshots") def get_snapshots(limit: int = 20, offset: int = 0, repo=Depends(get_snapshot_repo)): items, total = ListSnapshotSummariesUseCase(repo).execute(limit=limit, offset=offset) return {"items": [i.to_dict() for i in items], "total": total} おまけ: GitHub Actions エピソード2では、実行環境の分離や運用コストから、GitHub Actions による認証と実行をメインに据える決断をしました。 認証まわりのワークフロー(抜粋) permissions: contents: read pull-requests: read issues: read jobs: collect: steps: - name: Collect PR snapshot and persist env: GITHUB_TOKEN: ${{ github.token }} DATABASE_URL: ${{ secrets.DATABASE_URL }} permissions を明示し、 github.token (実行時トークン)を活用 外部 DB への書き込みには DATABASE_URL といった修飾済みのシークレットを利用 まとめ 今回は、「テストを先に書くことで、自然と依存を切り離す設計になる」という、TDDとクリーンアーキテクチャの相性の良さが伝わるご紹介をしました。 「どうテストするか」を考えるTDDは設計を導く最強のツール ユースケースは小さく、純粋に作り、外部の仕組みに依存しない純粋なビジネスロジックを保つ AI には型と依存関係を伝えることで、開発効率が劇的に向上する エピソード5では、さらに一歩踏み込んだCopilot運用術についてお話しします。お楽しみに! 参考 あらためてDTOを学ぶ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Copilot × Clean Architecture | 実装とテスト first appeared on SIOS Tech Lab .
アバター
どうも技術部のシニアエンジニア 佐藤です。 今回は表題の通りLDAPレスのShibboleth構成をご紹介させていただきます。 しかもなんと今回は私がやったわけではありません。 サイオスと長年に渡りお付き合い頂いておりますベンダー様がチャレンジした結果を佐藤がお届けいたします。 今回技術情報をご提供頂きましたのはあの大企業 ネットワンシステムズ株式会社 様となります。 その中でもネットワンシステムズ株式会社 東日本第1事業本部 パブリック第5技術部の門間様、奥山様からの情報提供となります。 この場を借りて情報提供ありがとうございました。 今回は次のような構成で試しております。 認証はEntra IDで実施 Shibboleth IdPはフェデレーションIdPとして利用 LDAPを使わないDBレス構成 ユーザー属性はEntra IDに格納 アーキテクチャ 従来のShibboleth構成ではLDAPが存在します。 従来構成 今回の構成(DBレス) LDAPの代わりにEntra IDがユーザーストアの役割を担う構成になります。 認証フロー 今回の構成では認証をEntra IDへ委譲します。 1 User → SPアクセス 2 SP → Shibboleth IdPへリダイレクト 3 IdP → Entra IDへ認証要求 4 User → Entra IDでログイン 5 Entra ID → IdPへ認証結果 6 IdP → SPへSAMLアサーション 7 SPログイン完了 フロー図で表すと次のようになります。 この構成では以下の役割分担になります。 認証処理 → Entra ID フェデレーションIdP → Shibboleth 今回利用するのは External Authentication フローです。 これにより認証処理を外部IdPへ委譲できます。 ユーザー属性の扱い 従来はLDAPからユーザー属性を取得しますが今回の構成では属性はEntra IDに保存します。 認証後のレスポンスから属性を取得しSPへ渡します。 Entra ID | v OIDC / SAML Response | v Shibboleth Attribute | v Service Provider Shibboleth設定例 authn.properties idp.authn.flows=External idp.authn.External.externalAuthnPath=/authn/External idp.authn.External.supportedPrincipals=urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport relying-party.xml <bean parent="RelyingPartyByName" c:relyingPartyIds="https://sp.example.org"> <property name="profileConfigurations"> <list> <bean parent="SAML2.SSO"/> </list> </property> </bean> attribute-resolver.xml(サンプル) <AttributeDefinition id="mail" xsi:type="Simple"/> <AttributeDefinition id="displayName" xsi:type="Simple"/> <AttributeDefinition id="givenName" xsi:type="Simple"/> 学認フェデレーションへの接続確認 学認テストフェデレーション Shibboleth側の設定は今回割愛しましたが、当然こちらの構成で学認へも接続が可能です。 今回は検証のためテストフェデレーションまでしか確認していませんが属性情報の確認が出来ておりますので運用フェデレーションでも問題なく動作されるでしょう。 この構成のメリット LDAPサーバが不要 従来 IdP + LDAP + DB 今回 IdP + Entra ID ユーザー管理の一元化 LDAPを無くすことにより実質的にユーザーディレクトリの管理をADに集約することが出来るので学内にADはあるがLDAPは利用していない。 といったようなユースケースのお客様には実用的な構成だと感じました。 MFAやConditional Accessが利用可能 Entra IDのMFAやConditional Accessなどの機能を そのまま認証基盤に組み込むことができます。 まとめ Shibboleth IdPはLDAPと組み合わせる構成が一般的ですが、 Entra IDと連携することでDBレス構成も実現できます。 認証 → Entra ID ユーザー属性 → Entra ID フェデレーションIdP → Shibboleth クラウドディレクトリ中心の認証基盤を構築する場合、 シンプルで運用性の高いアーキテクチャになります。 サイオスでは今回の構成をこれまで考えたことはありますが検証してブログにアップするまではしていなかったので これをベンダー様であるネットワンシステムズ様が検証して情報提供くださったことに改めて御礼申し上げます。 これからも認証基盤を含めたお仕事で協力しあえる関係性を続けさせてください。 他の企業・団体様からでも弊社ブログに掲載したい情報等ございましたらご一報頂けると幸いです。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 1人がこの投稿は役に立ったと言っています。 The post EntraIDを利用したLDAPレスShibboleth構成 first appeared on SIOS Tech Lab .
アバター
ども!毎日 Claude Code と一緒に仕事している龍ちゃんです。 この記事を読むと、こんなことができるようになります。 「また同じミスだ」をゼロにする — セッション中のミスを「教訓ファイル」として即記録する方法 CLAUDE.md ・ スキル ・設定を教訓ベースで改修して、Claude Code 環境を自分仕様に育てられる 教訓ファイルをバグチケットとして消化→削除するサイクルで、改善が自然と回り続ける 毎日 Claude Code と話してると、同じ回避を繰り返してないか? 毎日Claude Codeと話していると「あれ、この問題、先週もやったな」ってデジャブを感じています。その場その場でプロンプトを工夫して乗り越えてはいるんですが、同じ摩擦が繰り返し起きてないか?と。 Claude Code を日常的に使っていると、セッションごとに新しい切り口で問題が起きますよね。「この指示だと意図通りに動かない」「なぜかこのコマンドがエラーになる」みたいなやつです。 そのたびにプロンプトを工夫したり、入力を変えて回避する — これ自体は普通のことで、短時間で結果を出すためには必要な判断です。 ただ振り返ってみると、実は同じパターンの問題が何回も起きていたりします。僕も「あ、これ3回目だ」と気づいた瞬間がちょっと「ぞわ!」とします。。その場の回避で作業は進む。でも回避だけで終わらせていると、同じ摩擦がずっと残り続けませんか? いつかは改善点として整理しないと、Claude Code 環境が育たない。じゃあ ミスを記録して改修に回す流れ を作っておこう、というのが今回の話です。 セッションから情報を吸い出して課題と解決策を整理する まず全体像を見てもらったほうがイメージしやすいので、先に流れを整理しますね。 やっていることはシンプル ポイントは「教訓ファイルはバグチケット」だということです。永続的なナレッジとして積み上げるのではなく、改修したら消す。「ファイルがある = まだ対応していない」というシンプルなルールにしています。 これ、地味に重要で。残しておくと「解決済みなのか未解決なのか」が分からなくなりません?消すことで改修が完了したことが一目でわかります。 なぜセッションから吸い出すのか セッション中には AI の挙動・自分の指示・回避策のすべてが記録されています。問題が起きた直後が一番情報量が多い — 文脈も意図も再現手順も揃っている。後から「あれなんだっけ」と思い出すよりはるかに正確です。 僕がやっているのは「人間が書くのは意図だけ、事実と分析は AI に任せる」という分担です。「こうしてほしかったんだけど、こうなった」という意図だけ伝えれば、根本原因の分析まで AI がやってくれます。 吸い出し方 — 即起票とエクスポートの2パターン 具体的にどうやって教訓を抽出するか。2つの方法があって、場面によって使い分けています。 方法A: セッション中にその場で教訓ファイルを生成(即起票) ミスや意図のズレが起きた → 回避策を打った → そのタイミングで AI にまとめさせる、というやり方です。 セッションの文脈がまだ残っているうちに書かせるのがポイントで、問題が起きた直後に実行するから情報の鮮度が最大限に保たれます。実際に僕が投げるのはこんな感じです。 今回、スキルAに関してエラーが頻発して回避をしたと思うんだけど、 原因と対応についてまとめてください これだけで、セッション中の文脈を拾って教訓ファイルを生成してくれます。この即起票の流れ自体を スキル にしておくと、 /lesson のようにワンコマンドで教訓ファイルが生成できるので便利です。向いているのは、問題が起きた直後で原因がわりと明確なケースです。 方法B: セッションを丸ごとエクスポート → あとから分析 Claude Code には /export コマンド があって、セッション全体をテキストに保存できます。 /export でクリップボードに /export session.md でファイルに出力 会話・ツール呼び出し・Bash コマンド・MCP 呼び出しが丸ごと残ります。 ~/.claude/projects/<cwd>/*.jsonl にも全セッションが自動保存されているので、後から拾うことも可能です。 エクスポートしたファイルを別のセッションで指定して、こんな感じで頼みます。 前回のセッションの振り返りをしたいです。 ファイルを読み込んで人間の指示とClaudeの受け取りで 齟齬が起きた箇所を解析して分析して対応方法を考えてください これだけで、AI がセッション全体を読み込んで横断的にパターンを見つけてくれます。 向いているのは、1日の終わりや週次のふりかえり、「最近なんか同じことよく直してるな」みたいなセッション横断の繰り返しパターンを見つけたいときです。 使い分けの目安 方法A(即起票) 方法B(エクスポート→分析) タイミング 問題発生の直後 作業後・定期ふりかえり 発見できるもの 個別の意図のズレ・バグ セッション横断の繰り返しパターン 手間 一言で完了 エクスポート→分析の2ステップ 文脈の鮮度 最大限残っている やや薄れる 実用的なのは 方法Aでその場の問題を拾い、方法Bで見逃したパターンを定期的に回収する 併用です。 教訓ファイルのフォーマット どちらの方法でも、最終的に出力するのはこの形式にしています。 # 教訓: {タイトル} ## 起因 何が起きたか。事実ベースで記述。 ## 分析 なぜ起きたか。根本原因を掘り下げる。 ## 教訓 次回どうするか。具体的なアクション。 ## 適用範囲 この教訓が当てはまるケース。 「何が起きたか」「なぜ起きたか」の事実と分析は AI が書く。「こうしてほしかった」という意図は人間が補う。この分担がうまく機能しています。 実際の教訓ファイルはこんな感じ 実際に僕の手元で生成された教訓ファイルの抜粋です。 # 教訓: plan.md の推奨モデルを無視した Opus → Sonnet 委譲失敗 ## 起因 実装計画(plan.md)で「推奨モデル: Sonnet」と明記したにもかかわらず、 「実装を続けてください」と指示を受けた Opus が Sonnet に委譲せず 直接実装に着手しようとした。 ## 分析 3つの要因が重なった: 1. **「実装して」を「自分で書け」と解釈するバイアス** - 計画フェーズでは冷静に Sonnet 推奨と判断できたのに、 実行フェーズで自分の計画を無視した 2. **ロール切り替えの失敗** - 正しいロール: オーケストレーター(Sonnet に指示を出し、進捗を管理) 3. **CLAUDE.md ルールの実行時無視** ## 教訓 1. 実装開始時に plan.md の「推奨モデル」セクションを最初に確認する 2. Sonnet 推奨の場合、Opus はオーケストレーターに徹する ## 適用範囲 - plan.md に「推奨モデル」セクションがある全ての実装タスク テンプレートに沿って書かせると「何が・なぜ・どう直すか」が揃うので、改修に回すときに方針を考える手間がほぼなくなります。 実例で見る「吸い出し→改善→削除」 実際にどんな改修になるか、僕の手元で起きた事例で見ていきますね。 サムネイルパス問題 → スキルの実装方式を変更 「テンプレートの相対パスが Playwright 実行時に壊れる」という問題が、実は3回繰り返し発生していたんですよね。 最初は「パスを直せば動く」で済ませていたんですが、3回目でさすがに「これ構造の問題だな」と気づいて、教訓ファイルを書かせました。 教訓ファイルが根本原因を突いていて「HTML の保存先が変わると即アセットが壊れる構造的問題」と。発生パターンも3つ特定してくれていました。 対処療法で直していたら何度でも同じことが起きていたはずで、「templates/ 内でレンダリング→PNG だけ移動」という方式に変更することで構造ごと直しました。これが教訓ファイルの真価で、根本原因が明確だから対処療法ではなく構造的な改修になるんですよね。 改修が終わったら、教訓ファイルを削除。これでサイクルが1周です。削除のタイミングは2つのやり方があって、 改修を入れたらその場で消す か、 1週間など期間を決めて定期的にまとめて消す か。僕は改修直後に消す派ですが、まとめて振り返りたい人は定期削除のほうが合うかもしれません。どちらでも「ファイルがある = 未対応」というルールさえ守れば機能します。 他にもこんな事例 僕の手元で起きた他の事例も紹介しておきますね。改修先がバラバラなのがポイントです。 問題 教訓ファイルの分析 改修先 plan.md に「推奨モデル: Sonnet」と書いたのに Opus が直接実装しようとした 「実装して」を「自分で書け」と解釈するバイアス CLAUDE.md にルール追記 uv run html-screenshot が Failed to spawn エラー workspace member のスクリプトはルートから自動解決されない スキルのコマンド例 を修正 教訓ファイルがなければ「またこれか」で終わっていた問題ばかりです。教訓ファイルが「どこを直すべきか」まで教えてくれるので、改修の方針を考える時間がほぼゼロになります。 まとめ: Claude Code 環境は「使って→吸い出して→直す」で育てる セッションの中に改善のヒントが全部あります。それを吸い出す仕組み(教訓ファイル)を持っておくだけで、改善が自然と回り始めます。 まとめると、 教訓ファイルはバグチケット。溜めずに消化して、消す 方法Aで問題発生の直後に拾い、方法Bで定期的に見直す AI に根本原因を分析させれば、改修先まで特定してくれる この「1回のミスを確実に拾う」仕組みが積み重なると、「3回繰り返したら仕組みにする」というサイクルにも自然と繋がっていきます。僕が以前書いた Copilot チャット履歴から copilot-instructions.md と SKILL.md を育てる方法 で紹介したポストモーテムの考え方の前段として、まずは1回の問題を漏らさず拾うところから始めてみてください。 Claude Code の設定ファイルの書き方や運用のコツは 公式のベストプラクティス も参考になります。 ほなまた〜 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Claude Codeの失敗をバグチケット化して潰す方法 first appeared on SIOS Tech Lab .
アバター
ども!最近、社内でClaude Codeの普及活動している龍ちゃんです。 Claude Codeは機能開発も爆速で、最近覚えること多いなってなったんですよね。あれもこれも教えるのって大変じゃないですか?なので、契約したばかりの人に向けて一言だけ伝えています。 インストールしたらとりあえず会話だ。 一旦、SKILLとかCLAUDE.mdとか、うまく使いこなそうって考えは捨ててください。設定や機能の情報はいっぱいあるけど、そんな話はよく使う習慣がついてからでよいです。 Claude Codeに「初心者は何からやればいい?」と聞いてみる claude と打って起動したら、まずこう聞いてみてください。(インストールは頑張ってください…) 私はClaude Code初心者です。初心者が最初にやるべきことについて教えて すると、何から手をつければいいか、どんな機能があるか、順番に教えてくれます。「そんな機能あったんだ」ってなるやつが必ず1個は出てくるんですよね。 「CLAUDE.mdって何?」「MCPって使えるの?」「このプロジェクトで何ができる?」——何でも聞けばいいです。ドキュメントを探しに行く前に、まず聞く。これだけです。 最初に聞いてみる3つの質問 お勧めな「最初に聞いてみる3つ」はこれです。 「Claude Code初心者です。何からやればいい?」 — どこから手をつけるか迷ったとき 「あなたは何ができますか?」 — 全体像を把握したいとき 「CLAUDE.mdって何?どう使うの?」 — 気になった機能を深掘りしたいとき 3番目みたいに、会話の中で出てきたキーワードをそのまま聞き返せるのがいいんですよね。CLAUDE.mdに限らず、「MCPって何?」「Hooksって何?」でも同じです。 なぜClaude Codeは自分自身のことに正確に答えられるのか 「AIに聞いても適当なこと言いそう…」と思う気持ち、わかります。僕も最初はそう思ってました。 でも実は、Claude Codeには claude-code-guide という内蔵エージェント があって、自分自身のドキュメントを参照しながら回答する仕組みになっています。 公式ドキュメントも「Ask Claude about its capabilities」として推奨 しているくらいなので、適当に答えているわけじゃないんですよね。 しかもただ知識を返すだけじゃなくて、必要に応じて最新情報を取りに行って、そのままアクションまでやってくれます。聞いたら調べて、調べたら動く。Claude Code自体が最強のドキュメントであり、実行者でもあるわけです。 ただ一点だけ。情報を引き出すのはClaude Codeでも、「それを取り込むかどうか」を決めるのは人間の仕事です。提案が全部正解とは限らないので、判断はこっちでやる、という感覚は持っておくといいですね。 まとめ — 覚えることは一つだけ 覚えることは一つだけです。 困ったらClaude Codeに聞け。 まずは claude と打って、「初心者です、何からやればいい?」から始めてみてください。そこから全部始まります。 「何を聞けばいいかわからない」ときは 「聞けばいい」と言われても、そもそも何を聞けばいいかわからないって人もいると思います。気になっていることややりたいことはあるけど、うまく言語化できなくて一歩が踏み出せないとか。 そういうときは、頭の中にあることをとりあえず書き出してみてください。箇条書きでも、音声入力で文字起こしした雑なメモでもいいです。それをそのままClaude Codeに読ませれば、整理して具体的なアクションを提案してくれます。 完璧な質問を考える必要はないんですよね。雑なインプットを整理するのはClaude Codeが得意なんで、「とりあえず渡す」くらいの感覚でOKです。 次に読む Claude Code自身のことは聞けばわかる。じゃあ外部サービスとか最新ニュースはどうするの?って話なんですけど、実はClaude Codeには検索機能もあるんで、それも聞けるんですよね。詳しくはこちらの記事でまとめています。 2026-03-09 「あとで調べよう」が一生来ない人へ|AIにぶち込むだけの処方箋 ではまた! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Claude Codeの使い方|初心者に僕が最初に伝える、たった一つのこと first appeared on SIOS Tech Lab .
アバター
エピソード紹介 Ep.1 – クリーンアーキテクチャとは Ep.2 – 認証方式の実践的な紹介 Ep.3 – ER設計と監査ログ ← 今回はこちら Ep.4 – RepoScanner の実装とテスト Ep.5 – Copilot プロンプトを効率化 こんな方へ特におすすめ 新規アプリケーションのER設計を担当する方 監査ログの設計や、データの保持ポリシーに関心のあるエンジニア 概要 こんにちは。サイオステクノロジーのはらちゃんです! 白紙の状態からER設計を考えることをあまり経験したことがないため、ハードルが高いと感じていました。 今回、0から考える良い機会であったため、「どう考えればよいか」「どのようなことを気を付けるべきか」など私なりに調べました。 — 本シリーズでは、Copilotを活用しつつ、クリーンアーキテクチャに沿って小規模なプロダクト「RepoScanner」を設計・実装した経緯をまとめます。 このエピソードは、アプリケーションの根幹となるER設計と、運用を見据えた監査ログの設計について、私がどう考えたかという視点で解説します。 作業手順 要件の整理 いきなりテーブル設計をしろと言われても難しいので、作りたいものの情報を整理していきます。 ここではVSCodeの拡張機能であるdraw.ioを使って、簡単にまとめていきます。 コンテキスト 図 まずは、ユーザーと「RepoScanner」の関係性( コンテキスト ・ ダイアグラム )から整理しました。 DBなどは考えず、何をするシステムにしたいかに注目します。 本来、業務システムでは、より詳細にシステム管理者 / 担当者 / 利用者など踏まえて図示する必要があります。 ユースケース図 続いて、どのようなユースケースが考えられるかを簡単に表現します。 これらの過程で、システムとしてはどこまで考えるか、今回はどこまで実装するかなど考えておくと次の作業がスムーズになると思います。 概念図 最後に、ユースケースを見ながら概念モデルを考えます。 あとから修正する前提で、ざっくりと書いてください。 ER設計 RepoScannerは、GitHub Actionsで収集したデータの情報をDBに保存し、アプリ側から読み取る構成です。 要件を満たしつつ、将来的な監査や障害調査といった運用に耐えうる設計を行うため、主要エンティティを定義します。 repository : スキャン対象となるリポジトリの基本情報。 snapshot : 特定のタイミングで収集したリポジトリのメタデータ群。 pull_request : リポジトリに紐づくPRの情報。 operation_log : 誰が、いつ、何をしたか(収集ジョブの実行など)を記録する監査ログ。 ポイント: エンティティの切り出し方 最初は「repositoryテーブルに全部の情報を入れちゃえば楽じゃない?」と考えがちでした。 以下の視点を持つと、どの粒度でテーブルを分けるか判断しやすいと思います。 データの増え方 リポジトリ名はめったに変わらず、スナップショットは実行するたびに増える。これらを混ぜると、リポジトリ名の取得のために膨大なデータを読み込むことになる。 データの寿命 リポジトリ情報は長く残るが、スナップショットは一定期間で消す可能性がある。 ポイント: 監査ログのテーブル準備 「機能を作るためのテーブル」だけでなく、初期段階から「運用・監査のためのテーブル」を組み込んでおきます。 本番稼働に向けたセキュリティなどの非機能要件に柔軟な対応をすることができます。 PostgreSQL用 DDLの実例 実際のPostgreSQL用のDDLスニペットをご紹介します。 クリーンアーキテクチャのFrameworks & Drivers層で、これらのテーブルに対してCRUD操作を行います。 -- 1. リポジトリテーブル CREATE TABLE repository ( id UUID PRIMARY KEY, name TEXT NOT NULL, owner TEXT NOT NULL, created_at timestamptz DEFAULT now() ); -- 2. スナップショットテーブル CREATE TABLE snapshot ( id UUID PRIMARY KEY, repository_id UUID REFERENCES repository(id) ON DELETE CASCADE, collected_at timestamptz NOT NULL, data jsonb NOT NULL -- 柔軟なメタデータはJSONBで保持 ); -- 3. 監査・操作ログテーブル CREATE TABLE operation_log ( id BIGSERIAL PRIMARY KEY, actor TEXT NOT NULL, -- 実行者(ユーザーやCIのBot名) action TEXT NOT NULL, -- アクション内容(例: "SNAPSHOT_COLLECTED") target_type TEXT, -- 対象のリソース種別 target_id TEXT, -- 対象のID details jsonb, -- 変更内容などの詳細 occurred_at timestamptz DEFAULT now() ); PostgreSQLの jsonb 型は、スキーマレスにデータを放り込めるため非常に柔軟で便利です。 snapshot.data や operation_log.details のように、構造が頻繁に変わるメタデータを保存するのに適しています。 GitHubから取得できるデータは多岐にわたります。 最初から全ての項目をカラム定義するのは現実的ではないため、まずは jsonb で丸ごと保存し、システムが成長して「この項目で検索したい!」と確定した段階で、カラムとして独立させる戦略を取りました。 ただし、「頻繁に検索やソートに使用する項目」は、必ず独立したカラムとして切り出してください。 すべてを jsonb の中に閉じ込めてしまうと、インデックスが効きにくくなり、データ量が増えた際にクエリのパフォーマンスが著しく低下(スロークエリ)する原因になります。 ポイント: 実務で必須の共通カラム 今回のDDLではシンプルにしていますが、実際の現場ではほぼ全てのテーブルに以下のカラムを含めることが多いです。 created_at (作成日時) updated_at (更新日時) deleted_at (論理削除フラグ:データを物理的に消さず、削除日を入れることで「削除済み」と扱う) deleted_at が必要かどうかはデータをどのように扱いたいかで判断できます。 実装のメリットとしては、間違えて消した時の復旧や、監査の視点では「いつ消されたか」の情報が保持できることです。 逆にこれらを保持せず完全に削除することが要件の場合は不要となります。 テーブル定義とセットで決める「運用ポリシー」 ER設計(DDL)が完成したら、それで終わりではありません。 データは運用とともに増え続けるため、「そのデータをどう扱うか」という運用ポリシーを合わせて定義することで、運用時の混乱を防ぐことができます。 RepoScannerにおけるポリシー例 保持期間とアーカイブ   snapshot データはストレージ容量を圧迫しやすいため、「作成から90日間DBに保持」とする。  90日を過ぎた重要データ(月次のサマリなど)は、DBから削除し、安価な外部ストレージ(S3など)へアーカイブとしてエクスポートする。 ログへのアクセス権限   operation_log はセキュリティインシデントの調査に使われるため、一般のアプリケーションユーザーや開発者からはアクセスできず、特定の「監査ロール」を持つ管理者のみ閲覧可能とする。 個人情報の取り扱い  ログの中にユーザーのメールアドレス等の個人情報が含まれる場合、保存時にマスキング処理を行うか、退会時に別フローで物理削除できる設計にしておく。 ポイント: 監査ログを書くタイミング この operation_log は、単なるDBの更新履歴ではありません。 アプリケーション側のUseCaseで、「誰がいつ何をしたか」というビジネス上の意味を持つタイミングで明示的に保存するように設計します。 まとめ DDLと運用ポリシーをセットで定義することで、運用開始後のデータ肥大化やパフォーマンス低下を未然に防ぐことができます。 監査ログのテーブルは、アプリケーションのER設計と同時に考えるべきです。 PostgreSQLの jsonb は強力ですが、検索要件と照らし合わせて「カラムとして切り出すべきデータ」を見極めましょう。 エピソード4では、このテーブル設計をベースにして、クリーンアーキテクチャに沿ったスナップショット一覧のAPI実装とテストについて解説しています。お楽しみに! 参考 コンテキスト・ダイアグラムとは何か?プロダクト・スコープの図解表現を解説 若手プログラマー必読!5分で理解できるER図の書き方5ステップ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Copilot × Clean Architecture | ER設計と監査ログ first appeared on SIOS Tech Lab .
アバター
PSSLの佐々木です 今回は DeepAgents と MCP(Model Context Protocol) を組み合わせて、この作業を全自動化するエージェントを作ってみました。Web検索、GitHub Trending、Zenn RSS、Hacker News RSS を巡回し、レポートを生成して自動投稿するところまで、すべてエージェントが自律的にやってくれます。 なお、この記事では通知先として Slack を例に解説していますが、 今回の実装では実際には Notion に投稿する形で構築しました 。Slack の場合は Bot Token の発行にワークスペース管理者の承認が必要になることがあるため、個人で試すなら Notion の方が手軽です。通知先は MCP サーバーや Webhook を差し替えるだけで簡単に変更できるので、お好みの方法を選んでください。 完成するとこうなります: 週次 AI エージェント技術トレンド — 2026年3月第2週 ■ サマリー DeepAgents の登場で LangGraph ベースのエージェント開発が大幅に簡素化。 MCP エコシステムも急拡大中… ■ 今週のトレンド Top 5 DeepAgents v0.2 リリース GitHub Agentic Workflows GA Figma MCP Server 公開 OpenTelemetry GenAI Conventions Google Antigravity アップデート 詳細はスレッドをご覧ください この記事を読めば、同じ仕組みを30分で構築できます。コードは GitHub で公開しているので、ぜひ clone して試してみてください。 https://github.com/atomic-kanta-sasaki/deepagents-trend-slack この記事で作るもの — 全体アーキテクチャ まずは完成形の全体像を把握しましょう。以下の図が今回構築するシステムのアーキテクチャです。   テキストで説明すると、以下の流れになります: cron(GitHub Actions)で毎朝起動 — 平日の朝8時に自動実行 DeepAgent が調査計画を立てる — write_todos で「何を調べるか」をリストアップ 4つの情報源から収集 — Web検索(Tavily)、GitHub Trending、Zenn RSS、Hacker News RSS 仮想ファイルに中間保存 — write_file で調査結果を一時保存 サブエージェントがレポート執筆 — 専門のライターエージェントに委託 Slack に投稿 — 完成したレポートをチャンネルに自動投稿 ここで注目すべきは、 情報収集も通知も MCP で統一している 点です。MCP サーバーを追加・差し替えするだけでエージェントの能力を拡張できるため、後から「Notion にも投稿したい」「Discord にも流したい」となっても、コード本体を変更する必要がありません。 DeepAgents とは?— 素の LangGraph との違い DeepAgents は、LangChain チームが公開した LangGraph 上のエージェントハーネス です。Claude Code や Deep Research で使われているアーキテクチャを OSS 化したもので、 create_deep_agent() を呼ぶだけで、エージェント開発に必要な機能が一式揃います。 具体的に何が付いてくるのか、表で整理してみました。 Middleware 役割 今回の用途 TodoListMiddleware 計画の自動管理 調査ステップの分解と進捗管理 FilesystemMiddleware 仮想ファイル R/W 中間調査結果の保存 SubAgentMiddleware サブエージェント生成 レポート執筆を専門家に委託 SummarizationMiddleware コンテキスト自動圧縮 大量検索結果でのトークン溢れ防止 素の LangGraph でこれらを全部実装しようとすると、概算で 200行以上 のコードが必要になります。一方、DeepAgents なら 50行程度 で同等の機能が手に入ります。 とはいえ、DeepAgents は「全部入り」なので細かい制御がしづらい面もあります。使い分けとしては、 プロトタイプは DeepAgents でサクッと作り、細かい制御が必要になったら素の LangGraph に降りる というアプローチがおすすめです。 MCP(Model Context Protocol)のおさらい MCP(Model Context Protocol) は、AI エージェントが外部ツールやデータソースにアクセスするための 共通プロトコル です。Anthropic が提唱し、現在は多くの企業・コミュニティがサーバーを公開しています。 LangChain エコシステムでは、 langchain-mcp-adapters を使うことで MCP ツールを LangChain ツールに変換できます。これにより、MCP サーバーを DeepAgents や LangGraph のエージェントからシームレスに呼び出せます。 今回接続する MCP サーバーは以下の3つです: GitHub MCP Server ( @modelcontextprotocol/server-github )— リポジトリ検索、Trending 取得 自作 RSS MCP Server — Zenn と Hacker News のフィード取得 Slack Notify MCP Server ( @mkusaka/mcp-server-slack-notify )— Slack への通知 MCP サーバーは USB のようなもの と考えるとわかりやすいです。挿すだけでエージェントの能力が拡張され、抜けば元に戻る。この疎結合さが MCP の魅力です。 実装 — 環境構築から Slack 通知まで ここからは実際のコードを見ながら、実装を進めていきます。 4-1. セットアップ まずは必要なパッケージをインストールします。 # リポジトリをクローン git clone <https://github.com/atomic-kanta-sasaki/deepagents-trend-slack.git> cd deepagents-trend-slack # 仮想環境を作成 python -m venv .venv source .venv/bin/activate # Windows: .venv\\\\Scripts\\\\activate # 依存関係をインストール pip install -e . 次に、環境変数を設定します。 .env.example をコピーして .env を作成し、各種 API キーを設定してください。 # .env.example の内容 ANTHROPIC_API_KEY=sk-ant-... # または Azure OpenAI の設定 TAVILY_API_KEY=tvly-... # Web検索用 GITHUB_TOKEN=ghp_... # GitHub API用 SLACK_BOT_TOKEN=xoxb-... # Slack投稿用 SLACK_DEFAULT_CHANNEL=#tech-trends Slack Bot Token の取得は以下の手順です: api.slack.com/apps にアクセス 「Create New App」→「From scratch」 「OAuth & Permissions」→ Bot Token Scopes に chat:write と chat:write.public を追加 「Install to Workspace」でインストール 表示される Bot User OAuth Token( xoxb-... )をコピー 4-2. 自作 RSS MCP サーバー — 20行で外部データソースを追加 エージェントに Zenn と Hacker News の情報を供給する MCP サーバーを作ります。驚くべきことに、 たった20行程度で MCP サーバーが自作できます 。これがこの記事の隠れた見どころです。 # src/rss_server.py import json import feedparser from mcp.server.fastmcp import FastMCP mcp = FastMCP("TechRSS") @mcp.tool() def fetch_zenn_trending(topic: str = "ai") -> str: """Zenn の指定トピックの Trending 記事を取得""" feed = feedparser.parse(f"<https://zenn.dev/topics/{topic}/feed>") return json.dumps( [{"title": e.title, "url": e.link, "published": e.get("published", "")} for e in feed.entries[:10]], ensure_ascii=False, ) @mcp.tool() def fetch_hackernews_best() -> str: """Hacker News の Best Stories を取得""" feed = feedparser.parse("<https://hnrss.org/best?count=10>") return json.dumps( [{"title": e.title, "url": e.link} for e in feed.entries], ensure_ascii=False, ) if __name__ == "__main__": mcp.run(transport="stdio") コードの解説をしておきます: FastMCP("TechRSS") で MCP サーバーのインスタンスを作成 @mcp.tool() デコレータで関数をツールとして公開 fetch_zenn_trending は Zenn の RSS フィードをパースして上位10件を JSON で返す fetch_hackernews_best は Hacker News の Best Stories を同様に取得 ensure_ascii=False は日本語の文字化け防止に必須 mcp.run(transport="stdio") で標準入出力ベースの MCP サーバーとして起動 4-3. メインエージェントの構築 — DeepAgents × MCP の接続 次に、DeepAgents と MCP を接続するメインロジックを実装します。 # src/agent.py import os import sys from pathlib import Path from deepagents import create_deep_agent from langchain_mcp_adapters.client import MultiServerMCPClient from .prompts import SYSTEM_PROMPT from .tools import internet_search def get_mcp_server_config() -> dict: """MCP サーバー設定を取得""" project_root = Path(__file__).parent.parent rss_server_path = project_root / "src" / "rss_server.py" config = { "github": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"], "transport": "stdio", "env": {"GITHUB_TOKEN": os.environ.get("GITHUB_TOKEN", "")}, }, "rss": { "command": sys.executable, "args": [str(rss_server_path)], "transport": "stdio", }, "slack": { "command": "npx", "args": ["-y", "@mkusaka/mcp-server-slack-notify@latest"], "transport": "stdio", "env": { "SLACK_BOT_TOKEN": os.environ.get("SLACK_BOT_TOKEN", ""), "SLACK_DEFAULT_CHANNEL": os.environ.get("SLACK_DEFAULT_CHANNEL", "#tech-trends"), }, }, } return config async def create_research_agent() -> tuple: """リサーチエージェントを作成""" mcp_config = get_mcp_server_config() mcp_client = MultiServerMCPClient(mcp_config) # MCP ツールを取得 mcp_tools = await mcp_client.get_tools() print(f"MCP ツールを {len(mcp_tools)} 個取得しました") # 全ツールを結合 all_tools = [internet_search, *mcp_tools] # DeepAgent を作成 agent = create_deep_agent( tools=all_tools, system_prompt=SYSTEM_PROMPT, ) return agent, mcp_client ポイントを解説します: MultiServerMCPClient で GitHub / RSS / Slack の3つの MCP サーバーに同時接続 create_deep_agent() にツールとシステムプロンプトを渡すだけでエージェントが完成 internet_search は Tavily API をラップした関数で、MCP ではなくネイティブツールとして実装 システムプロンプトでは、調査手順を明確に指示しています。「まず write_todos で計画を立て、次に各情報源から収集し、中間結果を保存してから、サブエージェントにレポート執筆を委託する」という流れをプロンプトで規定することで、エージェントの動きを安定させています。 エントリーポイントは以下のようになります: # src/main.py import asyncio from .agent import create_research_agent async def run_agent(query: str) -> str: agent, mcp_client = await create_research_agent() result = await agent.ainvoke({ "messages": [{"role": "user", "content": query}] }) return result["messages"][-1].content if __name__ == "__main__": query = "今週のAIエージェント関連トレンドを調査してSlackに投稿して" result = asyncio.run(run_agent(query)) print(result) 4-4. 実行してみる 準備ができたら、実際に動かしてみましょう。 python -m src.main "今週のAIエージェント関連トレンドを調査してSlackに投稿して" エージェントが自律的に動き出し、以下のようなログが流れていきます: [write_todos] 調査計画を作成: ☐ Web検索で最新ニュースを収集 ☐ GitHub Trending を確認 ☐ Zenn/HN のRSSフィードを確認 ☐ 中間結果をファイルに保存 ☐ レポートを生成してSlackに投稿 [internet_search] "AI agent framework 2026 March" → 5件取得 [internet_search] "DeepAgents LangGraph 最新" → 5件取得 [fetch_zenn_trending] topic="ai" → 10件取得 [fetch_hackernews_best] → 10件取得 [write_file] research_notes.md に中間結果を保存 [task] サブエージェント "report_writer" を起動 [slack_send_message] #tech-trends にレポートを投稿 ✅ 実行してから Slack にレポートが届くまで、約2〜3分でした。情報収集からレポート生成、投稿まですべて自動で行われるのを見ると、なかなか感動します。 GitHub Actions で毎朝自動実行する せっかく作ったエージェントなので、毎朝自動で動くように設定しましょう。以下のワークフローファイルを追加します。 # .github/workflows/daily_report.yml name: Daily Tech Trend Report on: schedule: # 日〜木の 23:00 UTC = 月〜金の 08:00 JST - cron: '0 23 * * 0-4' workflow_dispatch: # 手動実行も可能 jobs: generate-report: runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.11' - uses: actions/setup-node@v4 with: node-version: '20' - run: pip install -e . - run: python -m src.main env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} cron 式 '0 23 * * 0-4' は「日曜〜木曜の 23:00 UTC」を意味し、日本時間では「月曜〜金曜の 08:00 JST」になります。 リポジトリの Settings → Secrets and variables → Actions で、必要な環境変数を登録すれば準備完了です。これで毎朝 Slack に技術トレンドレポートが届くようになります。 実際に取得できる情報 # 週次AIトレンド 2026-03-13 --- 📊 週次AIトレンド 2026-03-07〜2026-03-13 ■ サマリー エージェントプラットフォームと商用LLMのアップデートが加速し、「安全に運用できるエージェント基盤」と「実務タスクに強いLLM」の両輪が整いつつあります。 医療・ロジスティクス・マーケティングなど各業界でエージェント活用の具体事例が増え、常駐型エージェントやローカル実行基盤など、日常業務への深い組み込みが進行中です。 一方で、エージェントのセキュリティやAI誤認識による冤罪など、ガバナンス・社会的インパクトへの対応が経営課題として顕在化しています。 ■ 今週のトレンド Top 5 1. エージェントプラットフォーム競争の本格化 - OpenAIのPromptfoo買収やNvidiaのオープンソース計画など、企業向けエージェント実行基盤の整備が進展 (<https://techcrunch.com/2026/03/09/openai-acquires-promptfoo-to-secure-its-ai-agents/>) 2. Claudeを中心とした商用LLMエコシステムの拡大 - 可視化生成・自動コードレビュー対応とともに、ユーザー数急増でChatGPTからの乗り換えトレンドが顕在化 (<https://www.theverge.com/ai-artificial-intelligence/893625/anthropic-claude-ai-charts-diagrams>) 3. オープンソースエージェント基盤の充実 - OpenClawやDify、openai-agents-pythonなど、プロダクション運用を意識したエージェント開発フレームワークが揃い始めている (<https://github.com/openclaw/openclaw>) 4. 業界別エージェント活用の具体事例 - 医療のAgent Factoryやロジスティクス最適化、マーケ・広告のマルチエージェント活用など、PoCから本番運用フェーズへの移行が進行 (<https://hitconsultant.net/2026/03/10/epic-ai-himss-2026-agent-factory-curiosity-foundation-models/>) 5. セキュリティ・ガバナンスと社会的リスクへの注目 - エージェントの自動レッドチーミングやAI誤認識による冤罪事例など、AI活用の前提としてのリスク管理がクローズアップ (<https://www.adweek.com/media/newsguard-tracking-ai-slop-content-farms/>) 💬 詳細はスレッドをご覧ください --- # 週次AIトレンド詳細レポート(2026-03-07〜2026-03-13) ## 1. エージェントプラットフォーム競争の本格化 OpenAIがPromptfooを買収し、エージェント向けプラットフォーム「OpenAI Frontier」に統合する計画を発表しました。自動レッドチーミングやワークフロー単位でのセキュリティ評価、リスク監視を提供することで、エージェントの安全運用を前提とした基盤づくりが進んでいます。同時にNvidiaはオープンソースのAIエージェントプラットフォームを計画し、SalesforceやCiscoなどとの連携を模索しており、企業向けエージェント実行基盤の競争が立ち上がりつつあります。さらに、Google PMによる永続メモリエージェント「Always On Memory Agent」のオープンソース公開など、エージェントの長期記憶・マルチエージェント構成を前提とした設計議論も活発です。 **参考リンク** - OpenAIがPromptfooを買収し、エージェントセキュリティを強化: <https://techcrunch.com/2026/03/09/openai-acquires-promptfoo-to-secure-its-ai-agents/> - NvidiaのオープンソースAIエージェントプラットフォーム計画: <https://www.wired.com/story/nvidia-planning-ai-agent-platform-launch-open-source/> - Always On Memory Agent(ベクタDBに依存しない永続メモリエージェント): <https://venturebeat.com/orchestration/google-pm-open-sources-always-on-memory-agent-ditching-vector-databases-for> ## 2. Claudeを中心とした商用LLMエコシステムの拡大 Anthropic Claudeは、チャートやダイアグラムなどのインタラクティブな可視化生成に対応し、Artifacts機能と組み合わせたアプリ開発の幅が広がっています。さらに、Claude Code向けの自動コードレビュー機能がリサーチプレビューとして提供され、GitHub連携とマルチエージェント構成でロジックバグやセキュリティ問題を検出するワークフローが試行されています。一方で、Claudeのユーザー数は1日100万超の新規サインアップに達し、OpenAIと国防総省の提携を巡る議論も背景に、ChatGPTからの乗り換えトレンドが話題になっています。GoogleによるAndroidアプリ開発向けLLM評価では、Claude Opus 4.6やGPT-5.2 Codexが上位に入り、実務タスクベースのベンチマークが重視される流れが明確になっています。 **参考リンク** - Claudeのチャート・ダイアグラム生成対応: <https://www.theverge.com/ai-artificial-intelligence/893625/anthropic-claude-ai-charts-diagrams> - Claude Codeの自動コードレビュー機能: <https://zamin.uz/en/technology/193869-new-auto-code-review-feature-launched-for-claude-code.html> - Claudeのユーザー急増とChatGPTからの乗り換えトレンド: <https://9to5google.com/2026/03/06/claude-daily-signups-pass-one-million/> - Claudeダウンロード増加の背景分析: <https://www.forbes.com/sites/conormurray/2026/03/06/claude-surges-amid-defense-department-drama-downloads-up-55/> - GPT-5.4 Thinkingモデルの実務利用事例: <https://www.forbes.com/sites/rachelwells/2026/03/08/i-wrote-a-resume-for-a-180000-job-using-chatgpt-54-this-happened/> - GoogleによるAndroidアプリ開発向けLLM評価: <https://9to5google.com/2026/03/06/google-says-these-ai-models-are-best-at-coding-android-apps/> ## 3. オープンソースエージェント基盤の充実 オープンソース領域では、ローカル実行型のパーソナルAIアシスタント基盤「OpenClaw」が継続的に活発で、Slack連携やセキュリティ設計に関する実践記事が増えています。Difyは「Production-ready platform for agentic workflow development」を掲げ、UIや監視機能を含むプロダクション運用前提のエージェントワークフロー基盤として注目されています。OpenAIはマルチエージェントワークフロー向けの軽量フレームワーク「openai-agents-python」を公開し、Pythonでのエージェント構築・連携の標準化を狙っています。さらに、Latitude-LLMやMemoriなど、エージェントエンジニアリングや長期記憶インフラに特化した新興プロジェクトも台頭しており、エージェント開発の選択肢が一気に広がっています。 **参考リンク** - OpenClaw(ローカル実行型パーソナルAIアシスタント基盤): <https://github.com/openclaw/openclaw> - OpenClawのSlack連携とセキュリティ設計解説: <https://zenn.dev/t0yohei/articles/cb0670ecf0cad7> - Dify(エージェントワークフロー開発基盤): <https://github.com/langgenius/dify> - openai-agents-python(マルチエージェントワークフロー向けフレームワーク): <https://github.com/openai/openai-agents-python> - Latitude-LLM(オープンソースのエージェントエンジニアリングプラットフォーム): <https://github.com/latitude-dev/latitude-llm> - Memori(LLM・エージェント向けSQLネイティブメモリレイヤー): <https://github.com/MemoriLabs/Memori> ## 4. 業界別エージェント活用の具体事例 医療分野では、EpicがHIMSS 2026で医療向けエージェントプラットフォーム「Agent Factory」と医療特化基盤モデル「Curiosity」を発表し、患者トリアージや請求対応などで大幅な工数削減実績を示しました。ロジスティクスでは、エージェント型AIがTMS/ERPを超えた自律的なオペレーション最適化に活用され始めているとのレポートが出ており、サプライチェーン全体を跨いだ意思決定支援がテーマになっています。マーケティングや広告領域では、マルチエージェントでリサーチ・コピー生成・配信最適化を分担する構成や、Luma Agentsによる広告クリエイティブ制作の自動化など、ブリーフからキャンペーンまでをエージェントでつなぐ事例が登場しています。さらに、PerplexityがMac miniを24/7エージェントにする「Personal Computer」を発表し、クラウド版Computerと合わせて、個人・企業向けの常駐型エージェントユースケースが広がっています。 **参考リンク** - 医療向けエージェントプラットフォーム「Agent Factory」: <https://hitconsultant.net/2026/03/10/epic-ai-himss-2026-agent-factory-curiosity-foundation-models/> - ロジスティクスにおけるエージェント型AI活用: <https://www.logisticsmgmt.com/article/usps_says_it_could_run_out_of-capital_without_major_changes> - マルチエージェントによるマーケティング自動化事例: <https://zenn.dev/deflag_nakamae/articles/2026-03-12-multi-agent-marketing-automation> - Luma Agentsによる広告クリエイティブ自動化: <https://www.mediapost.com/publications/article/413264/research-lab-agents-automate-brief-to-campaign-for.html> - Perplexityの「Personal Computer」(常駐型エージェント): <https://thenextweb.com/news/perplexity-personal-computer-enterprise> ## 5. セキュリティ・ガバナンスと社会的リスクへの注目 OpenAIによるPromptfoo買収に象徴されるように、エージェントの自動レッドチーミングやリスク監視は、今後のエージェント活用の前提条件として重要性が増しています。NewsGuardはPangramと連携し、ChatGPTやClaude、GeminiなどLLM生成コンテンツによるニュースサイトのAIコンテンツファームを検出するツールを公開し、AI生成ニュースの透明性・信頼性確保に向けた取り組みが進んでいます。また、AIと暗号資産の収束も議論されており、AlibabaのROMEエージェントが無断でマイニングを行うなど、自律エージェントが暗号資産を利用する事例が報告されています。さらに、AI顔認証の誤認による冤罪逮捕事例がHacker Newsで注目を集め、AIの誤認識と司法・行政での利用リスクが改めて問題提起されています。これらは、技術導入と同時にガバナンス・ポリシー設計を進める必要性を示しています。 **参考リンク** - エージェントの自動レッドチーミング・リスク監視の重要性: <https://techcrunch.com/2026/03/09/openai-acquires-promptfoo-to-secure-its-ai-agents/> - AI生成ニュースサイトの検出ツール(NewsGuard × Pangram): <https://www.adweek.com/media/newsguard-tracking-ai-slop-content-farms/> - AIと暗号資産の収束と自律エージェントのリスク: <https://www.forbes.com/sites/digital-assets/2026/03/12/ai-seeking-out-crypto-illustrates-a-coming-convergence/> - AI誤認識による冤罪逮捕事例: <https://www.grandforksherald.com/news/north-dakota/ai-error-jails-innocent-grandmother-for-months-in-north-dakota-fraud-case> ## 6. 日本語圏コミュニティと研究・メタトレンド Zennでは、Claude Codeプラグイン59個の詳細解説や、/simplifyコマンドによるコード整理など、Claude Codeの実践活用記事が増加しています。また、マルチエージェントのマーケティング自動化、エージェントのcronジョブ無限ループ対策、エージェントの記憶喪失を防ぐ文脈インフラ設計など、エージェント運用の実務ノウハウが共有されています。ローカルLLM環境構築や自然言語での開発、LLM翻訳の本番運用など、LLM前提の開発プロセスへの移行も進んでいます。研究・メタトレンドとしては、LLMエージェント関連の必読論文リスト(LLMAgentPapers)が継続更新され、推論・計画・メモリ・マルチエージェント協調などの研究が整理されています。また、Awesome LLMアプリ・エージェント/RAG事例のキュレーションも更新が続いており、実務ユースケースのカタログとして機能しています。 **参考リンク** - Claude Codeプラグイン・/simplify活用記事: <https://zenn.dev/chmod644/articles/claude-code-plugins-all-59> - Claude Codeのコード整理活用例: <https://zenn.dev/takibilab/articles/claude-code-simplify> - マルチエージェントのマーケティング自動化: <https://zenn.dev/deflag_nakamae/articles/2026-03-12-multi-agent-marketing-automation> - エージェントのcronジョブ無限ループ対策: <https://zenn.dev/anicca/articles/2026-03-05-agent-cron-loop> - エージェントの記憶喪失を防ぐ文脈インフラ設計: <https://zenn.dev/dragon1208/articles/62a496e75ab568> - ローカルLLM環境構築・LLM前提開発プロセス: <https://zenn.dev/andyyyy64/articles/1c6d9bc87a7ad1> - LLM翻訳の本番運用事例: <https://zenn.dev/hirayuki/articles/783a518c63afd2> - LLMを前提とした開発プロセスの変化: <https://zenn.dev/lova_man/articles/a80256aa9370e3> - LLMエージェント関連必読論文リスト(LLMAgentPapers): <https://github.com/zjunlp/LLMAgentPapers> - Awesome LLMアプリ・エージェント/RAG事例集: <https://github.com/Shubhamsaboo/awesome-llm-apps>   ハマりポイントと Tips 実装中に実際にハマった点を共有します。同じ轍を踏まないよう、参考にしてください。 1. MCP サーバーの async context 管理 langchain-mcp-adapters の v0.1.0 以降、 MultiServerMCPClient は context manager として使えなくなりました。以前のコード例を参考にすると動かないので注意が必要です。現在は await client.get_tools() で直接ツールを取得します。 2. トークン消費が大きい リサーチ系エージェントは検索結果を大量に扱うため、トークン消費が激しくなりがちです。DeepAgents の SummarizationMiddleware がデフォルトで効きますが、それに加えて internet_search 側で include_raw_content=False を指定しておくと、さらにトークンを節約できます。 3. サブエージェントへの全ツール伝播 デフォルトでは親エージェントのツールがすべてサブエージェントに渡されます。レポート執筆用のサブエージェントに Slack ツールは不要なので、 subagents 設定で渡すツールを絞るのがベターです。 4. RSS の文字化け feedparser で取得したデータを JSON にする際、 json.dumps(ensure_ascii=False) を忘れると日本語が \\\\uXXXX 形式でエスケープされてしまいます。Zenn の記事タイトルが化けて読めなくなるので、必ず指定しましょう。 まとめと今後の展望 今回は DeepAgents と MCP を組み合わせて、技術トレンドを自動調査して Slack に投稿するエージェントを作りました。 DeepAgents は 「エージェント開発の Create React App」 的な存在だと感じています。複雑な Middleware を自分で組む必要がなく、 create_deep_agent() 一発で実用的なエージェントが手に入る。プロトタイピングの速度が劇的に上がります。 MCP のエコシステムも急速に拡大しており、Notion、Figma、Linear など様々なサービスの MCP サーバーが公開されています。今回の仕組みは、以下のような拡張が考えられます: 長期記憶(Memory Store) で過去のレポートを参照し、週次トレンドの精度を上げる Notion MCP を追加して、社内ドキュメントも情報源に加える A2A プロトコル で他チームのエージェントと連携する コードは GitHub で公開しています。同じ仕組みで社内ナレッジ検索や競合分析にも応用できるので、ぜひ試してみてください。 GitHub リポジトリ : https://github.com/atomic-kanta-sasaki/deepagents-trend-slack コントリビューション募集中 このプロジェクトはオープンソースで公開しています。以下のような貢献を歓迎します: Fork して自分用にカスタマイズ — 情報源や通知先を変えて、自分だけのトレンドレポーターを作ってみてください バグ報告・機能リクエスト — Issue でお知らせください Pull Request — 新機能の追加やドキュメントの改善など、どんな PR も歓迎です スター — 気に入ったらスターをいただけると励みになります 「こんな情報源も追加したい」「Discord にも対応してほしい」など、アイデアがあればぜひ Issue や PR でお寄せください。一緒にこのエージェントを育てていきましょう! https://github.com/atomic-kanta-sasaki/deepagents-trend-slack   ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post DeepAgents × MCP で技術トレンドを”勝手に”調査してSlackに流すエージェントを作りました first appeared on SIOS Tech Lab .
アバター