はじめに エンタープライズLinux大手のSUSEにより、AIソリューション「SUSE AI Factory」が「SUSECON 2026」にて発表されました。 生成AIの企業利用が進む一方で、多くの企業がセキュリティの懸念や構築・運用の複雑さといった課題に直面しています。SUSEが提供する「SUSE AI」および「SUSE AI Factory」は、こうした企業の課題を解決するためのソリューションとして大きな注目を集めています。 本記事では、SUSE AIとSUSE AI Factoryの概要やそれぞれの位置づけについて紹介します。 SUSE AIの概要 SUSE AIは、生成AIアプリケーションを任意の環境で展開・実行するためのエンタープライズ向けAIプラットフォームです。 SUSE Rancher Primeを導入できるKubernetes環境であればどこでも展開でき、安全なAI実行基盤を構築することができます。 SUSE AIは主に以下の特徴があります。 自由なAIコンポーネント選択 多様なオープンソース(OSS)のAIコンポーネントを利用できるため、ニーズや要件に合わせてそれらを組み合わせ、推論APIなどを構築・提供することができます。 堅牢なセキュリティ 提供されるAIコンポーネントは安全性が保証されたもののみが使用されます。 SUSE AIはエアギャップ環境に展開することができるため、データの漏洩リスクを大幅に軽減することができます。 SUSE Securityというコンポーネントを利用したゼロトラストセキュリティが組み込まれています。 リアルタイムな監視 SUSE Observabilityというコンポーネントと連携した可観測性ダッシュボードにより、リアルタイムでの監視を行うことができます。 CPU使用率やメモリ使用率などの基本的なメトリクスに加え、GPUリソースの使用状況やAIアプリケーションのパフォーマンス、LLMのトークン使用量などもリアルタイムに監視することができます。 SUSE AI Factoryの概要 SUSE AI FactoryはOSSのAIコンポーネントをKubernetesクラスター上に効率よくデプロイ・管理するためのコンポーネントです。 SUSE AI Factoryは主に以下の特徴があります。 UIから簡単にデプロイ SUSE AI Factoryは、KubernetesオペレーターとRancher UI拡張機能によって構成されており、RancherのUI上からAIコンポーネントをデプロイすることができます。 「Blueprint(ブループリント)」によるAIスタックの容易な構築 AIコンポーネントの組み合わせを「Blueprint」と呼ばれるテンプレートとして作成・利用できます。 代表的なユースケース向けに、SUSEが事前検証したBlueprintもあらかじめ用意されているため、ゼロから構築する手間を省けます。 NVIDIA製品と統合された「SUSE AI Factory with NVIDIA」 OSSのコンポーネントだけでなく、NVIDIA AI Enterprise製品群を組み込んだ「SUSE AI Factory with NVIDIA」も提供されています。 SUSE AIとSUSE AI Factoryの関係 SUSEの公式ドキュメント では、SUSE AIとSUSE AI Factoryの関係性が以下のような方程式で表されています。 Infrastructure Platform (like SUSE Rancher Prime) + Application Platform (SUSE AI Factory) = SUSE AI この方程式が示す通り、「SUSE AI」とはプラットフォーム全体の枠組みであり、その中で「SUSE AI Factory」はAIアプリケーションのデプロイやライフサイクル管理を担うコンポーネントという位置づけになります。 SUSE AIのアーキテクチャ図は以下のようになっており、SUSE Rancher Primeを中心とするインフラ基盤の上でSUSE AI Factoryが稼働し、全体をセキュリティや監視機能が統合的に保護する構成こそが「SUSE AI」の全体像になります。 SUSE AIのアーキテクチャ図 おわりに 本記事ではSUSE AIおよびSUSE AI Factoryの概要について紹介しました。 SUSE AIは、ニーズに応じて柔軟に組めるコンポーネント選択の自由さや、堅牢なセキュリティ、リアルタイムで状況を可視化する監視機能など、企業が安全にAIを活用するための要素が揃っています。さらにSUSE AI Factoryの登場により、これらを容易にデプロイ・管理できる実用的な環境も整いました。 企業のAI導入がさらに加速する中で、今後ますます注目されるソリューションになっていくはずです。 当ブログでは、今後もSUSE AIに関する内容を発信していく予定です。ぜひチェックしてみてください。 参考文献 https://www.suse.com/ja-jp/solutions/ai/ https://www.suse.com/ja-jp/products/ai/ https://documentation.suse.com/ja-jp/suse-ai-factory/latest/ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post SUSE AIとSUSE AI Factoryとは? first appeared on SIOS Tech Lab .
ども!朝7時にClaudeへ「おはよ」と送るのが日課になっている龍ちゃんです。布団の中で、スマホで、3文字だけ。 7時に「おはよ」って送るよ。なんでなん? 先に言っておくと、挨拶したところで出力が優しくなるとか、そういう話は聞いたことがないです。むしろ日本人は「おはよう」と送っちゃうから、ほかの国より無駄に電気代を食ってるぜ、みたいな話をどこかで見た気がします。効率の話ではないんですよね。 Claudeには5時間ごとの使用枠があります。この枠、 その枠での最初のメッセージから5時間を数えます 。いつ始めるかで、その日1日のリセットタイムが決まるわけです。 なので朝起きたら、とりあえず「おはよ」と送る。だいたい7時ですね。7時から仕事を始めるわけではないです。送ったあとは歯を磨いたり着替えたり、ちょっとリフレッシュしたりで、 1時間くらい何も送りません 。本番は8時ごろから。枠だけ先に開けて、自分はのんびり身支度しています。 午前・午後・夜に、1本ずつ引かれる 7時に開けると12時に切れて、次が12時から17時。この並びがいい感じなんですよね。午前に激しめの作業をやって、一息つくタイミングでちょうど切れる。休憩して、12時からの枠でまた午後スタート。うちの業務時間が9時から17時半なので、2本目がだいたい業務にそのまま乗ります。3本目でClaude Codeの検証とかをやると、寝るころには一旦きれいに切れます。 10時から始めてもいいんですよ。ただ2枠使うころには人間の時間が終わっている、というだけで。枠を自分の活動時間に合わせるなら、前を早くするか後ろを遅くするかのどっちかです。僕は前を選びました。10時に開ける人は、午前と午後の捉え方をずらせばその人の生活に合うと思います。 もう一つ得があって、朝にやる作業と午後にやる作業で枠が分かれるので、トークンの効率がいいです。メリットはそこだけですね。 前に書いた記事 では、5時間ごとの制限をClaudeの弱点として挙げました。開ける時刻を自分で決められるなら、そこまで弱点でもなかったです。 這ってでも、3文字は送る 眠くても「おはよ」だけは送る。運動するよりは楽ですよね。だって3文字送るだけなので。 別に挨拶じゃなくてもいいと思います。やることが何かあると、みなさん朝から携帯を触るじゃないですか。触る人はとりあえずClaudeに何か送っておけば、そこから枠が引かれます。脳が起きてきたら「よし仕事するぞ」と改めて話しかける。 龍ちゃん 技術的な優位性は何もない話でした。ただ、AIの制限にかこつけて生活リズムが直るんじゃない?というのが、個人的にけっこう気に入っているところです。枠を朝に置くには起きるしかないので。Claudeに生活を寄せたら、生活のほうがまともになりました。 正直、良い図ができて喋りたすぎてこのブログを書きました。 コラム:それ自動化すればよくないですか できます。 /schedule でクラウドにルーティンを立てれば、毎朝決まった時刻に勝手に撃ってくれます。手順そのものは別の話なので置いておきます。 でも、やっていません。朝起きたタイミングに枠を合わせるのが目的なので、自分が寝ているのに7時に発火するのは本末転倒だと思うんですよね。 あと念のため。1日3セッションを平均的に回していくと、意外と週の枠のほうを使い切ります。枠を3本開けられるからタダ、という話ではないです。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【地味に便利】ClaudeCodeの業務時間に合わせて最適化する小技! first appeared on SIOS Tech Lab .
ども!ここしばらく、Slidevで作ったスライドをPowerPointで出す方法ばかり検証している龍ちゃんです。 僕は資料を Slidev で書いています。Markdownで書けるので中身がテキストのまま残るし、AIに書かせるのも直させるのも思いのままです。ただ社内に出すとなると、だいたいPowerPointが要求されるんですよね。PowerPointだったら誰でも編集できますもんね。 今回は、コードとして起こしていたスライドをPowerPoint形式にするための検証で、4パターンまで結論が出たのでまとめて紹介しようと思います。 この記事を読み終わると、自分がどの方法を選択するべきかが2つの質問で決まります。そして変換に何を使うかで、その先どこまで行けるかが決まることまで分かるはずです。 AIに資料を作らせるとして、どこまで任せるか 先に断っておくと、会社に出す資料がPowerPointなのは、ここでは前提として扱います。以前「 MarpとSlidevの使い分け 」の最後に「他部署の人が中身を直接いじる用途なら、素直にPowerPointが正解」と書いたんですが、今回はその扉をくぐる話です。 PowerPointじゃなくて、 Claude Design でいいじゃん!とか言わないでください… さて、AIに資料を作らせるといっても任せ方には幅があります。 丸ごと任せる :手元のソースをそのまま放り込んで、構成から中身まで全部まとめてもらう 変換だけ任せる :自分で書いた中身を渡して、PowerPointの形にするところだけやってもらう どちらもAIを使っている点では同じ。違うのは、AIをどの工程に置くかだけなんですよね。 丸ごとAIに任せると、何が出てくるのか 過程から見ていきます。 丸ごと任せると、完成品が降ってきます。速いし、出てくるものもきれいです。ただ「なんか違う」となったときに手を入れる場所が途中にない。これは以前「 AIに全任せしないデザインシステムの育て方 」で書いた話で、そのときは「スライドは自分の話しやすさとセットで形になる」という理由を挙げました。今回はもうひとつ別の理由があります。 出てくるものの中身です。 実際に試しました。Slidevのデッキからデザインを落としたMarkdownを用意して、Claude Designに渡して33枚。ここは正直に書くんですが、WEB上で編集までできるうえに、打ち替えられるPowerPointファイルが本当に出ました。文字は395個のテキストとして生きていて、画像は1枚も貼られていない。 見た目はこのとおりで、正直きれいです。 速さも見た目も良かったんですよ。ただPowerPointで開いてみると、403個の図形が全部バラバラに置かれていました。まとまりがないので、このパーツを使いたい、ここだけ動かしたい、となったときの調整が全部人間の手作業になります。PowerPointの強みってそこじゃないですか。掴んで、動かして、組み替える。それができない形で出てきている。 念のため、これは欠陥だと言いたいわけではないです。速いし、デザインもなんとなくきれいに整理されている。用途が違うだけです。ちなみにデザインでいうと提案ベースで出てくるのでClaude Designの圧勝だと思います。 その「用途が違う」というのは、こういうことです。バラバラの図形として出てくると、直すときに一番楽なのはClaude Designの上で直すことになります。つまり正本があちら側へ移る。1回きりの資料なら何も困りませんが、来年また使うとか、会社のテンプレートに載せ替えるとか、他の人が組み替えるとか、難しくなりますよね。 分かれ目はここです。 掴んで動かせる形で欲しいか、そうじゃないか。 社内で共有するのであれば、PowerPointの側に正本を置いておきたいです。そのうえでAIの力は最大限享受したい。なので、Slidevでスライドを作り込んで、最終成果物だけPowerPointに変換するという思想で検証を進めています。なぜSlidevだとAIの力を最大限享受できるのかは後半に書いたので、気になったら読んでみてください。 AIと一緒に、実際どうやってPowerPointにするのか 変換のやり方は4つあります。自分のがどれかは、2つ答えれば決まります。 ひとつめ。会社のPowerPointテンプレートを使いますか。使わない、使いたいけど手元にない、ある、の3つで分かれます。 ふたつめ。作り込んだ図まで持っていきますか。デザインはPowerPoint側で作り込むと割り切って、テキストとスライド割だけ運ぶのか。それとも作り込んだ図まで運びたいのか。この質問は自分のデッキを思い浮かべれば答えられるはずです。 会社のテンプレートを使わないなら 中身を後段の変換器に渡して、PowerPointの形にしてもらいます。会社のロゴも配色も乗りませんが、テキストとスライド割は運べる。 一番簡単なのは、Slidevで書いた中身をそのままClaude Designに貼ることですね。ただ単純に文字編集ができるPPTXの取得が可能になります。 Claude Design側でテンプレートを作って、デザインを均一化する道もあります。こちらは深掘り検証をしてブログにまとめていくので、結論が変わるかもしれません。現時点では、編集はできるけどデザインの修正はClaude Design上でやるのが早い、という中途半端なPPTXが出てくる、という評価です。 会社のテンプレートを使いたいけれど、手元にないなら テンプレートの方を、自分で作ります。 Slidevのテーマには、色もフォントも帯の太さも、手元で表示を確かめながら決めた値がもう書いてあるんですよね。その値を実測して、PowerPointのテンプレートをコードから起こす。デザインを決めるのはHTMLとCSSの世界でやって、決まったものだけをPowerPointへ持ち込む形です。 2026-08-13 PowerPointテンプレが無いなら、Slidevのデザインから自作する 会社のテンプレートがあって、テキストとスライド割だけでいいなら テンプレートが持っているレイアウトを選んで、枠にテキストを入れていく。それだけです。 書式も配置もテンプレート側が持っているので、こちらでは何も指定しません。だから出てくるのは再現ではなく、会社のテンプレートそのものの見た目。代わりに、持っていけるのはテキストとスライド割だけになります。こちらの手法では、デザインに関してはPowerPoint側で担保することになります。 2026-08-13 Slidevの中身を、会社のPowerPointテンプレのレイアウトへ流し込む 会社のテンプレートがあって、作り込んだ図まで持っていくなら 1枚ごとの中身をAIにOOXMLで書かせて、会社のテンプレートと1枚のスライドの上で合体させます。 ロゴやヘッダーのような繰り返し出てくる部分は、テンプレート側に任せたまま。AIが書くのは、その枚にしか出てこない図だけです。出てきた図はカードごとに掴んで動かせます。 2026-08-13 Slidevのスライドを、手で組み直さずに編集できるPPTXにする 4つに上下はありません 念のため書いておくと、この4つに優劣はありません。テキストとスライド割だけを運ぶ道は、図まで運ぶ道の劣化版ではなくて、 デザインはPowerPointに任せると割り切る判断そのものが結論 になっています。 4つともAIを使っていて、違うのはAIをどの工程に置くかだけです。 共通の限界もひとつあります。 HTMLとCSSで作ったものが、そのままの姿で運ばれるわけではありません。 Vue のコンポーネントも凝ったCSSも、変換の過程で落ちます。図まで持っていく道でも、運んでいるのは見た目そのものではなく、AIがPowerPointの図形として書き直したものです。 それと、会社のPowerPoint資料の側しか持っていないなら、この分岐には乗りません。その場合は「 会社のPowerPointの資料をSlidevへ移す話 」へどうぞ。 2026-08-13 PowerPointをSlidevのコードに落とす|抜くもの、画像に任せるもの 変換に何を使うか Slidevの中身をPowerPointにするとき、変換に何を使うかという話です。 前提を先に置いておくと、 Slidevのpptx書き出し が編集できないのは、Slidevの欠陥ではありません。SlidevはHTMLとCSSをブラウザで描画してスライドにしているので、pptxに書き出すときは描画した結果を画像として貼るしかない。原理的にそうなります。だから出力を後から編集可能にしようとするのではなく、中身の方を別の変換器に渡します。 選択肢は3つあります。簡単な順に並べますね。 Claude Designに貼る。 これが一番簡単で、見た目も一番いい。Slidevで書いた中身をそのまま渡せば、ワンステップで出てきます。ただし出てくる図形はテンプレートの枠に乗らないし、フォントも1文字ずつ直接指定されている。 PPTX側で後からテーマを差し替えても見た目は変わりません。 ここが行き止まりですね。 pandoc を使う。 コマンド1本で終わります。出てくるファイルは図形が全部テンプレートの枠に乗っていて、フォントの指定は1つも書き込まれていない。書式をテーマから継承する素直な形なので、後からテーマを差し替えれば見た目が変わります。渡すのはSlidevの記法を含まない素のMarkdownですね。 python-pptx を使う。 手間は一番かかります。ここだけは自分でコードを書くことになるので、貼るだけ・コマンド1本とは手触りが違います。ただしテンプレを自作する道も、レイアウトへ流し込む道も、合体させる道も、全部これの上に乗っている。 先へ行くというのは、変換にコードを挟む側へ回るということです。 先へ進む気があるなら、これですね。 補足が2つあります。 Claude Designに会社のブランドを載せる道もあります。やることは、会社の .pptx から色やフォントの値を取り出して、それをHTMLとCSSの雛形に起こしてClaude Design側に登録する、という手順です。値が取り出せること自体は「 会社のPowerPointの資料をSlidevへ移す話 」で確かめました。あちらはSlidevへ移す話なので、ここで使うのは取り出しの部分だけです。登録まで持っていくと クラスメソッドさんの事例 の形になります。ただし16種類の雛形をHTMLで起こす手間はテンプレートを自作する道とほぼ同じで、 貼るだけという手軽さは無くなります。 なお成果物はHTMLなので、PowerPointで出したときにどうなるかは僕も試していません。次の検証課題なので、まとめたらブログを追記します! pandocの方には罠があって、会社のテンプレートを渡してもレイアウトは使ってくれません。 --reference-doc でテンプレートを指定できるんですが、pandocが探しに行くのは Title Slide や Section Header といった英語のレイアウト名。決め打ちです。日本語版のPowerPointで作ったテンプレートは「タイトル スライド」「セクション見出し」のような名前になっているので、1つも一致しない。手元の2つで確かめたら、7枚のレイアウトが全部日本語名で、全部pandocの既定にフォールバックしました。ただし色とフォントは持ってこられます。テーマの部分だけは効くので、ブランドカラーとメイリオの指定は引き継がれました。ロゴも帯も配置も来ませんけどね。 テンプレートを渡せば会社の見た目になる、とはいかない。 pandocが決め打ちで持っている形に、こちら側を合わせにいく作業になります。 ここまでで足りる人も多いと思います。5分や10分のLTとか、社内の共有メモとか。人間が道筋をパッと立てて、どういう展開にするかだけ決めて渡せば、あとはすごくやりやすいですからね。 そういう資料なら、Claude Designに貼るかpandocを通すかで足ります。 それでもSlidevで書き続けるのはなぜか ここから先がある人向けに、僕がなぜSlidevの側に立っているかも書いておきます。 僕にとってSlidevは、資料の正本を置く場所です。色とサイズを縛るCSS変数、よく使うレイアウトと部品、それを使う作法を書いた CLAUDE.md 。この一式を積み上げてきたのが「 SlidevのデザインシステムをCSS変数とCLAUDE.mdで作る 」で、スライドをレビューさせるエージェントも「 3体に分けて 」持っています。資料を作るときはこの仕組みの上に乗るので、けっこう爆速です。 AIには書かせています。ただし丸ごとは任せません。手元で見え方を確かめながら進めたいし、話の展開が文章のまま読めるから、流れが死んでいるのをスライドにする前に見つけられるんですよね。1枚ずつ作り上げながら詰めていく。この進め方そのものは「 AIとスライドを作る進め方 」に書きました。 でも、実際にスライドへ起こしてから見えるものもあるんですよね。論理的には要らないけど、人間が理解するには要る補足とか、たとえ話とか。ああいうものを捕まえられるのは、スライドの形にしたときです。だから流れの設計とスライドの生成は、行ったり来たりできる距離に置いておきたいんですよ。 はっきりさせておきたいのは、 生成そのものは、どちらもAIがやっている ということです。僕がSlidevで書くときもClaude Codeに書かせているので、速さで差がつくわけじゃない。差がつくのは、生成の前と後なんですよね。 後の方は、さっき書いた正本の話です。前というのは、話の流れを組み立てるところ。ひとつ前の章で5分や10分の資料なら貼るだけで足りると書きましたが、45分とか1時間のセミナーになると、話の流れが最初から決まっていることなんて絶対にないんですよね。何を先に置いて何を後に回すか、どこで一度まとめるか・小休止を作るか。そこを行ったり来たりしながら決めていく作業が要ります。僕の仕事にはこの長さの資料が結構あって、だからこの前段が消えません。 まとめ 以前「 Claude Code × Slidev 」で、Slidevのpptx出力について「画像が貼り付けられただけのファイルで編集はできない。配布用の最終形と割り切る」と書きました。配布して見てもらうだけなら、その割り切りは今も正しいと思っています。ただ、そこから先に道があった。中身を別の変換器に渡せば、編集できるPowerPointが作れるんですよね。 そして「 MarpとSlidevの使い分け 」の最後に書いた「他部署の人が中身を直接いじるならPowerPointが正解」という一言も、今回でようやく回収できた気がします。Slidevで書き続けたまま、出口だけPowerPointにする。それが今の答えです。 それと、ここまで読んで「自分は1回きりの資料しか作らないな」と思った人。それでいいと思います。Claude Designに貼れば、たぶん一番きれいなものが一番早く出ます。掴んで動かせる形が要るのは、その資料に先があるときだけです。 なお、変換器の実装をパターンごとの自作から固定化できないか、という論点も検証の中にはあります。ただこちらはまだ設計メモの段階なので、一行だけ触れておきますね。 ここに書いたのは、あくまで僕がこの道で出した結論です。SlidevからPowerPointへ渡すやり方は他にもあるはずで、これが唯一の答えだとは思っていません。 自分の資料がどれくらいの尺で、どこまで先があるのか。そこさえ決まれば、道は勝手に決まります。 ではまた! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post AIは編集しやすいPowerPointスライドを作れるか? first appeared on SIOS Tech Lab .
ども!会社のPowerPointを解析して、Slidev側に持ってくる検証をしている龍ちゃんです。 会社の資料って、1枚1枚に職人芸が効いてるんですよね。ここはこの位置に、ここはこの大きさで、という判断が積み重なって、見やすい形になってる。あの詰め方は正直すごいと思ってます。 ただ、取り回しはよくない。開くと ppt/media/ に画像が148枚も入ってたりするし、中身をのぞいても座標の数字がずらっと並んでいるだけで、AIに手伝ってもらうにはちょっと解析しにくいんですよね。 僕の場合はというと、会社で発表するとき資料は会社のテンプレで作るので、ふだんSlidevで書いてる分を毎回作り直してました。同じ内容を二回作ってるのが地味に面倒で。 だったら逆をやればいいんじゃないかと思ったんです。会社のPowerPointを解析して、Slidev側に引っ張ってくる。会社のテンプレがSlidevのテーマになってしまえば、作り直す側が消えるはずだなと。 結論から言うと、 会社のPowerPointはSlidevに移せます。雑に頼んでもけっこう移りますが、決まってこけるところがあります 。読み終わる頃には、手元の .pptx を自分のClaude Codeに解析させて、Slidevに載せられる状態になっているはずです。指示の考え方まで含めて書いていきます。 雑に投げてもけっこう動く。ただ、決まって同じところでこける スライドを画像にして「これをSlidevで再現して」と渡すだけでも、かなり形になります。まずは投げてみればいい、というのが最初の感触でした。 なお記事に貼っている画像は、公開できる方のテンプレで同じ手順を試したものです。数字のほうは会社の実資料で測っているので、そこは別物として読んでもらえると助かります。 指示をほとんど渡さなくても、この調子でそれなりに動きます。 真ん中の「指示なし」を見てもらうと分かるんですが、これでも十分読めるスライドになっています。 で、この調子で何枚か試していくと、決まって同じところでこけました。AIに何かを再現させたことがある人なら、たぶん見覚えがあると思います。 日本語と英語の間に、なぜか半角スペースが入っている 見た目は合っているのに、直そうとしたら手が入らない 文字が画像になっていて、打ち直せない 材料を足したのに、かえって悪くなった この4つ、理由はひとつで説明つきます。 AIが見た目に合わせにいっている んですよね。 いちばん分かりやすいのが、日本語と英語の間の空白です。 元のPowerPointを見ると、たしかに空いて見えます。ただ、これ文字じゃないんですよ。PowerPointが描くときに自動で空けているだけで、XMLから抜いた原文にスペースは入っていません。あとからAIが自分で書いた「迷ったところ」を読んだら、PowerPointの和欧文オートスペースに寄せる意図でやった、と書いてありました。事故じゃなくて、気を利かせた結果なんですよね。 見た目は合っているのに直せないやつ、これは中を見ると全部が座標でベタ置きされています。文字が画像になっているのも同じで、どちらも見た目を合わせる最短経路なんですよ。元と同じ位置に置きたいなら座標がいちばん確実だし、元と同じ字面にしたいなら絵にして貼るのがいちばん確実です。 材料を足したら悪くなった、というのも同じ動機でした。図形の座標や入れ子まで渡すと、渡した分だけ「その通りに並べる」側へ寄っていくんです。渡した量の問題ではなくて、渡したものの種類の問題ですね。 並べ直したら、移すものは3つに分かれた じゃあ何をどう渡せばいいのか。詰めていくと、そもそも移すものが3つに分かれることが見えてきました。 コードから抜くもの 画像に任せるもの 再現しないもの 1枚のスライドに重ねると、こういう分かれ方です。 ひとつずつ見ていきます。なお、抜く作業自体はここも全部Claude Codeに投げます。python-pptxとOOXMLの直読みで、一発で抜けます。 コードから抜く:文字と寸法と、繰り返し出てくるもの 文字と、寸法と、全ページで共通のパーツです。 共通パーツはロゴ・帯・罫線・フッター。これはレイアウト側に置いてあるので、レイアウトだけ見れば済みます。指示は「レイアウトからの参照回数で共有アセットを特定して」。ファイル名でも見た目でもなく、回数で当てるのがポイントです。会社の実資料はロゴが24のレイアウトから参照されていて、公開用テンプレでも8のレイアウトから参照されていました。数えれば一発で出てきます。 帯や罫線やロゴまわりの 色 もここです。抜いて、Slidev側のテーマに焼いておきます。 ちなみに、色を数えていて面白かったこと。本文スライド43枚で直接指定されている色が44種類あって、しかも目で見て区別がつかない組み合わせが5つ出てきました。たとえば黒。 #000000 が17回で、その隣に、ほぼ黒だけど黒じゃない #050100 が25回。本物の黒より多いんですよ。 これ、わかるわーってなりましたね。43枚を手で作れば黒が2種類になるのは当たり前で、責める話じゃないです。むしろ人間やなーと。揃っている値は機械が作ったもので、散っている値は人が判断した跡なんですよね。 寸法も同じ束で、図形の座標はEMUという単位で入っているので、スライドの判型で割れば%になります。Slidev側はCSSなので、%にしてしまえばそのまま置けます。 文字は1枚ずつ抜きます。ここで注意がひとつあって、XMLの並び順は読み順ではありません。重なりの前後関係でしかないので、素直に上から抜くと見出しと本文が入れ違って出てきます。だから指示には「図形の上端の座標で並べ直してから出して」まで入れておく必要があります。 画像に任せる:本文の中の見た目 本文の中の見た目です。配置とか、余白とか、その辺。 ここ、「抜かなくていい」というより、 画像側が担当してくれるから抜かない という話なんですよね。Slidevをずっと触ってきた実感として、画像を見せて見た目を再現させる部分に関しては、かなり強いと思ってます。実際、ブランドの色まで抜いて一緒に渡してみたこともあるんですが、出てくるものは変わりませんでした。抜く工程がまるごと要らなかった、ということです。 面白いのは、抜くか任せるかの線が 素材の種類では引かれていない ことです。同じ「色」でも、レイアウトに書いてあるブランドの色は抜くし、本文の中の色は任せる。どこに書いてあるか、何のための値か、で分かれます。 再現しない:ページごとの装飾 イラスト、飾りのアイコン、切り貼りされた画像。ページごとに乗っている装飾です。 ここは忠実に再現しにいかないほうがいいです。PowerPointのグループ機能をそのまま持ってくればいいかと思って開けてみたんですが、中身はアイコンを構成するパスの集まりでした。意味のまとまりではなく、絵の部品なんですよね。これをCSSに翻訳しても、手間の割に何も残りません。 判断は共有パーツと同じで、回数で決めます。何ページにも繰り返し出てくるなら、それはレイアウト側の持ち物なので、抜いてテーマに焼く。そのページにしか出てこないなら、それはそのページの飾りなので、再現しない。必要なら画像のまま置いて、先に進みます。 忠実に再現しないと決めるところが、たぶん一番大事です。 見た目だけ合わせたいなら画像を貼るか座標でベタ書きすれば済みますし、実際そのほうが元と一致します。でもそれだと、あとから直せないし、他のスライドのお手本にもなりません。 抜く、任せる、再現しない。この3つでだいたい片付きます。 ただ、どれをやっても出てこないものがひとつだけ残ります。その1枚の中で、どれとどれがひとまとまりなのか、どこを強く見せたいのか。スライドの設計です。ここはファイルのどこにも書いていないので、自分の言葉で渡すしかありません。 逆に言うと、さっき「見た目に合わせにいく」で説明したこけのほうは、 こちらが任せる範囲を決めていなかった だけなんです。決めていないから、任せた側が倒れた。 渡すのは2点。あとは要求文を4つ というわけで、渡すものは決まりました。レンダ画像と、XMLから抜いた文字の2点セットです。 画像だけだと文字が必ず崩れます。さっきの和欧間のスペースですね。毎回まったく同じところが崩れたので、これは偶然じゃなくて性質です。文字をテキストで渡してあげれば直ります。画像を渡すのが効くこと自体は以前 AIに「なんか違う」が伝わらない?画像を渡して指示ループから抜け出す で書いたんですが、スライドの書き起こしでは画像だけだと足りません。 逆に、ここから材料を増やしても構造は出てきませんでした。動いたのは頼み方のほうです。 こちらが満たしてほしいことを4つ、そのまま文章にして渡してみました。 文字は必ず文字として置くこと。画像やSVGの中に焼き込まない 見た目のまとまりは、そのまま入れ子で表すこと。座標で位置を合わせて並べない テーマがすでに描いているもの、ロゴや罫線やフッターは描き足さないこと 本文領域の中で、いちばん小さい文字が13pxを下回らないこと 判定に使いたい軸を、そのまま要求文に翻訳した形です。全文は付録に置いておくので、手元でそのまま使ってもらえます。 構造がまったく違う2枚で試して、狙ったことは全部通りました。まあ割と通るよねって感じです。 ただ、効き方がちょっと意外でした。構造そのものは、頼まなくても出てくることがあるんです。何も言わなくても表を table と tr で組んでくれたりします。じゃあ指示は何をしたのかというと、逃げ道を塞いでいました。座標で位置合わせをする回、文字をSVGや背景画像に焼き込む回。何も言わないとこの2つが混じるんですが、4つを渡した回はどちらもゼロになりました。 それでもこけたときは、こう考えると仕分けられます。 そのスライドを、人間がファイルの中身だけ見て復元できるかどうか。 できないなら、AIにもできません。できるはずなのに出てこないなら、任せる範囲を決めていないだけなので、頼み方で直ります。 ついでに、こけたわけじゃなくて割り切ったものも書いておきます。配置は原本とずれますが、これはずれていいと思ってます。構造として組み直す以上ずれるので、移した先で調整するのが前提です。変換ツールも作っていませんし、いちばん複雑な1枚はまだ試せていません。 じゃあ、何から始めればいいのか 最初の一手は決まっていて、全部を移す計画は立てなくていいです。1枚だけ、レンダ画像と抜いた文字を渡して投げてみてください。最初の「まず投げてみればいい」に戻ってくる話です。 レンダ画像は、PowerPointから画像で書き出すのがいちばん早いです。文字は <a:t> から抜いて、図形の上端の座標で並べ直してから渡します。どちらも作り方を付録に書いておきました。 こけたら、さっきの仕分け方を使ってください。人間がファイルの中身だけ見て復元できるものがこけていたら、任せる範囲を決めていないだけなので、要求文を足します。付録の全文をそのまま持っていってもらえます。復元できないものなら、それはファイルに答えがないので、諦めてAIに書かせてください。特に設計のところは、自分の言葉で渡してあげるのが結局いちばん早いです。 そこまで移してしまうと、あとがけっこう楽になります。エンジニアが慣れている形式で管理できるし、コードになっているぶんAIも読める。重いPPTXを持ち歩かなくてよくなって、職人芸だった資料が、あとから読めるお手本になります。スライドをコードで書くこと全般の利点でもあるので、 Marp と Slidev の使い分け にも近い話を書きました。 SlidevからPowerPointへ、という向きも検証してあります。Slidevで作った資産をそのままPowerPointへ持っていく話は Slidevのスライドを、手で組み直さずに編集できるPPTXにする 、中身だけ会社のテンプレへ流し込む話は Slidevの中身を、会社のPowerPointテンプレのレイアウトへ流し込む に書きました。 会社のテンプレが手元になくて、自分のデザインから起こしたい人は PowerPointテンプレが無いなら、Slidevのデザインから自作する 。道の全体像は SlidevからPowerPointへ渡す道を整理した記事 にまとめてあります。 まぁ割と通るよね、AIって優秀です。 次はもう一段上をやりたいと思ってます。1枚の中じゃなくて、デッキ全体のほう。どの順で見せているのか、この枚は読ませたいのか、インパクトを与えたいのか、流れで持ち帰らせたいのか。デザイン込みのストーリーラインまで解析できたら面白いなと思っていて、ここはまだ手をつけていません。 付録:手元で再現するための情報 要求文の全文 4つの要求をそのまま文章にしたものです。手元の .pptx で試すときは、これをそのままAgentに渡してください。 ## このスライドをSlidevで書き起こしてください 渡した画像のスライドを、Slidevのコード(`slides.md` + 必要なら scoped CSS / Vue コンポーネント)として書き起こしてください。成果物はコードです。画像を貼って済ませないでください。 以下の4つを満たしてください。 ### 1. 文字は必ず文字として置く - 画面に見えている文字は、すべて DOM のテキストノードとして存在させてください。 - 画像・`<svg><text>`・CSS の背景画像に文字を焼き込まないでください。後から打ち直せなくなります。 - 渡したテキストがある場合は、そこから一字一句そのまま使ってください。画像から読み取った文字で上書きしないでください。 ### 2. 見た目のまとまりを、そのまま入れ子で表す - スライドの上でひとまとまりに見えるもの(カード、行、ステップ、表の1行、アイコンと見出しと本文の組など)は、1つの親要素の中に入れ子にしてください。 - 判断の基準は「これを掴んで動かしたら、中身がまとめて付いてくるか」です。付いてこない書き方になっていたら、まとまりが表現できていません。 - 座標で位置を合わせて並べる書き方を避けてください。`position: absolute` と `top` / `left` で1つずつ置いていくと見た目は合いますが、そのスライド専用のコードになって使い回せません。 - 代わりに、要素の入れ子と、flex / grid / 通常の文書フローで組んでください。 ### 3. テーマが描くものを描き足さない/本文の領域から出ない - ロゴ、ヘッダの罫線、フッターのコピーライト、ページ番号はテーマがすでに描いています。同じものを本文の中に描き足さないでください。 - ページのタイトルは frontmatter の `heading:` に渡してください。本文の中に見出しとして書かないでください。 - 描いてよいのは `default` レイアウトの slot(本文領域)の中だけです。この領域は `overflow: hidden` なので、はみ出した分は切れます。 - 文字が溢れたり、途中で切れたりしない状態にしてください。 ### 4. 読める大きさで置く - 本文領域の中で、いちばん小さい文字が 13px を下回らないようにしてください。 - 縮めて詰め込むより、入る量に収めることを優先してください。 ## 組み立てに使ってよい部品 以下の語彙で組んでください。ここに無いものを使ってはいけない、という意味ではありませんが、まず以下で表せないかを考えてください。 | 部品 | 使う場面 | |---|---| | 見出し(`h2` / `h3`)+ 本文(`p`) | 小見出しと説明の組 | | 箇条書き(`ul` / `ol` / `li`) | 並列する項目、入れ子の項目 | | 表(`table` / `tr` / `td`) | 行と列で読ませるもの。行は必ず `tr` にする | | カード(`div` などの囲み) | 枠線や背景色で囲まれたまとまり | | 横並び(`flex`) | アイコンと文章、2つのブロックの左右並び | | 格子(`grid`) | 3つ以上が規則的に並ぶもの | - 同じ形が繰り返し出てくるなら、Vue コンポーネントに切り出して使い回してください。 - CSS は scoped で書いてください。 ## 出力の形 1. `slides.md`(frontmatter に `layout: default` と `heading:` を含む) 2. 必要なら Vue コンポーネントと scoped CSS 3. 迷ったところ・妥協したところを最後に箇条書きで書いてください(何が難しかったかを知りたいので、うまくいったふりをしないでください) 素材の作り方 渡す2点セットのうち、レンダ画像はPowerPointから画像で書き出すのがいちばん早いです。手元にPowerPointがある人はそれで足ります。僕はdevcontainerの中でやっていたので、LibreOfficeでPDFにしてから画像にしました。ただしこの環境にはメイリオが無くて、LibreOfficeがNoto Sans CJKで代替してレンダします。字形は変わるので、フォントまで見たいならPowerPoint側から書き出したほうが確実です。 文字は <a:t> から抜いたあと、図形の上端の座標で並べ直してから渡します。XMLの並び順のままだと入れ違うので、ここは省けません。寸法はEMUという単位で入っているので、スライドの判型で割って%にします。Slidev側はCSSなので、%にしてしまえばそのまま置けます。 ロゴ・帯・罫線の実測値 ロゴはレイアウト側からの参照回数で特定します。会社の実資料は同じファイルが24のレイアウトから、公開用テンプレは8のレイアウトから参照されていました。透明パディングの実測は、会社の実資料が上下28%・左右11.5%、公開用テンプレが上下10%・左右0.1%です。アルファのbboxでタイトクロップしてから配置します。 こちらは公開用テンプレだけの実測値です。会社の実資料は参照回数と透明パディングしか測っていないので、対比にはなっていません。 項目 実測値 帯の高さ 13.0% 罫線 0.7% ロゴの上端 4.1% 踏んだ罠 <style> の中のCSSコメントに <br> と書くとSlidevのビルドが落ちます frontmatterの title は予約語なのでレイアウトに渡りません。別のキー( heading: など)にします <img> の width と right は既定テーマのCSSに上書きされます。divで包んで、div側で幅を決めます 測り方 slides.md の文字列を読むだけだと、Vueコンポーネント越しに生成されたものが測れません。ビルドしてレンダーしたあとのDOMから取る必要があります。 ではまた! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post PowerPointをSlidevのコードに落とす|抜くもの、画像に任せるもの first appeared on SIOS Tech Lab .
ども!Slidevで作った資料を、Agentに書かせた中身と会社のPowerPointテンプレへ合体させる検証を、ずっと続けている龍ちゃんです。 会社のPowerPointで資料を出そうとすると、たいてい同じところで詰まります。AIに作らせても、こちらが「これなら編集できる」と思って想像していたものより、ひとつ下のものが出てくるんですよね。結局、PowerPointを開いて1枚ずつ組み直すことになります。 資料自体はSlidevで書き続けたいんです。手元で見え方を確かめながら進められて、話の展開も文章のまま読めるので。書くのはSlidevのまま、出口だけPowerPointにできればいい、というのが今回の前提なんですよね。 その変換ができるようになりました。中身はAgentに書かせて、会社のテンプレはテンプレのまま使って、1枚のスライドの上で合体させます。 会社テンプレのままの見た目で、中身がテキストとして編集できて、図はカードごとに掴んで動かせるpptxが出ます。しかもそれを、デッキ丸ごと並列で書かせられます。 Agentに書かせるのは1枚ごとの中身だけで、ロゴやヘッダ、繰り返す枠は会社テンプレ側がそのまま持っています。 SlidevからPPTXへ渡す道はこれ一本ではありません。この記事は、中身はAgentに書かせ、会社のPowerPointテンプレはそのまま使って1枚のスライド上で合体させる道を切り出したものです。全体像は SlidevからPowerPointへ渡す道を整理した記事 にあるので、自分に合う道から選びたい人はそちらへ。この記事だけで完結するので、そのまま読み進めても大丈夫です。 会社のPowerPointで出そうとすると、結局手で組み直す羽目になる ゴールは、会社フォーマットのまま、中身が編集できる形で出すことです。PowerPointで出すということは、渡した相手が自由に配置を組み替えられるということで、それがPowerPointの価値です。だから画像を貼っただけのpptxは答えになりません。拡張子はPPTXでも、中身は組み替えられないからなんですよね。以前「 Claude CodeでSlidevのスライドを作る話 」で「配布用の最終形と割り切る」と書いたのは、配布して見てもらうだけの資料なら今も正しいと思っています。ただ、他の人が後から組み替える前提だと、その割り切りは通りません。以前「 Marp と Slidev の使い分け 」の最後に「他部署の人が中身を直接いじる用途なら、素直にPowerPointが正解」と書いたのがまさにこれで、今回はその前提の中の話です。 自由に組める中身を出そうとすると、結局OOXML(PowerPointのファイルの中身に入っているXMLです)を書くことになります。ところが、1から書くとなると、会社のロゴも配色も自分で全部書くことになるので、デッキ全体で統一を保つのが大変です。逆に会社テンプレに流し込めばブランドはそのまま乗りますが、今度は作り込んだ図の入れ先がありません。 つまり、PowerPointの価値を守ろうとするとブランドの統一が自分の肩に乗ってきて、会社ブランドを載せようとすると作り込んだ図が入らない。この二律背反にはまって、いつも同じところに着地するんですよね。PowerPointを開いて、1枚ずつ手で組み直す。デッキ1本ぶん、まるごとこれが起きます。 だからこそ、ここで発想を変えます。手で組み直すしかないと思っていたその中身、OOXMLで書ける形なんですよね。ということはAgentにも書かせられます。ただし、全部書かせるのは違います。 PowerPointの強みは、実は2つあります。ひとつは自由に配置して組み替えられることです。もうひとつは、テーマ・マスター・レイアウトで、繰り返す部分を揃える仕組みがもともと用意されていることです。この2つを、別々の担い手に割り当てます。ひとつはAgentに書かせます。もうひとつはテーマに委譲します。ロゴ・ヘッダ帯・フッター・ページ番号は、委譲すれば置くだけで出てくるので、Agentに振るのは、その1枚にしか出てこない図だけで済むんですよね。 委譲すれば、会社ブランドはテンプレ側がそのまま担保してくれますし、Agentに書かせる量そのものも減ります。担い手の割り当てを、合体後のスライドそのもので見てもらうのが早いです。 もうひとつ、時間の軸でも線が引けます。一度書けば使い回せるものと、デッキごとに書き直すものです。乗せる仕掛けは一度だけ書けばよくて、1枚ごとの中身は毎回書きます。 そして、この切り分けがそのまま「デッキ丸ごと並列で依頼できる」理由になります。Agentの持ち分が1枚の中身だけなら、1枚ぶんの仕事はもともと小さいんです。しかもSlidevで作ったデッキは、レイアウトとコンポーネントを使い回して組んであるので、中身もパターンの束になっています。そのうえで、1枚ごとが独立しています。だから1スライド=1ファイル=1エージェントで切って、デッキ丸ごとを同時に走らせられます。手描きで作ったPowerPointでも同じように切って投げることはできますが、1枚ごとの作りがばらばらだと1枚ぶんの仕事が小さくならないので、効きは落ちます。パターンが揃っているのは、Slidevで作ってあるからなんですよね。 AIにPPTXを書かせるなら、Anthropicが公式で出している pptxスキル もあります。あのスキルの機械チェックは「PowerPointで開けるか」までを見る作りで、はみ出しや図のまとまり、フォントの指定は目視のレビューに回す前提なんですよね(日本語も想定の外で、安全に使えるフォントの一覧が全部欧文です)。この記事で足したのはその先で、合否を機械で出す層と、日本語と会社のテンプレの上で成立させるところになります。 2つの指示で、Agentに「描かせないもの」と「掴める形」を守らせる 線は引けました。あとは、その線をAgentにどう守らせるかです。やっているのは2つの指示で、どちらも「描く範囲を狭める」向きの話になります。 会社テンプレの領分は、再現させるのではなく描かせない 会社テンプレが既に描いているものは、Agentに描かせません。 ロゴ・ヘッダ帯・フッター・ページ番号のことです。ここをAgentに再現させると、テンプレ側の装飾と二重になって必ず崩れます。再現の精度を上げる話ではなく、描く範囲を切る話なんですよね。 指示として書いたのは、意図で言うと3つです。 描く領域と描かない領域を先に渡す。固定ヘッダは描かない、本文の描画領域はここからここまで、というのを座標で指定します。座標で渡すのは、枠を言葉で説明するとAgentごとに解釈がずれるからです。 見出しは自分で描かせない。テンプレのタイトル枠へこちらで流すので、本文の中に見出しを描き足させません。 色は決めたトークンだけを使わせる。生のHEX値を勝手に増やさせない、ということです。 どれも「描かせない・狭める」向きの指示で、そこがこの節の名前とそのまま一致しています。だから読者の側でも、自分の会社テンプレが何を描いているかを先に決めれば、そのまま当てはめられるはずです。 この描かせない指示がいちばん効くのは、並列で走らせたときです。何体ものAgentがそれぞれ勝手にヘッダを描いて微妙に揃わない、みたいな事故が、そもそも起きなくなるんですよね。崩れる余地のあるものを最初から描かせていないので、ブランドが崩れる心配もありません。 とはいえ、指示は守られたり守られなかったりします。なので、Agentが装飾を描こうとしても通らないように、描く口のほうを塞いであります(仕掛けの中身は付録に置きます)。 繰り返す要素なのに、会社テンプレ側に型が無い、という場合もあります。表紙や章扉のように全面を紺で覆う型が、手元の会社テンプレにはありませんでした。こういうときは、Agentに描かせる方へ寄せずに、会社テンプレのレイアウトとして型を1つ足します(触るのはファイルの4か所で、そこは付録の罠2に置きます)。テンプレを作り直すのではなく、テンプレが持っていないものだけ足す、という向きです。デッキが変わったときに差し替えるのも、図の中の配色(デッキ側)と、焼き足すレイアウトの地の色(テンプレ側)の2つだけで済みます。 掴める単位は、名指しで宣言させないと書かれない もうひとつの指示です。PowerPointのスライドは、図形とテキストが1つずつ独立して置かれているだけの入れ物です。カードの角丸の背景も、その上に乗っている見出しも本文も、重ねて並べてあるだけで、互いに何の関係も持っていません。この状態で渡すと、受け取った人が「このカードだけ右に寄せたい」と思ったときに、背景だけが動いて文字が置き去りになります。組み替えられる形で渡す、というのは、見た目のまとまりが実体としても1つになっている、ということなんですよね。 そのまとまりは、Agentが「ここからここまでが1つのまとまり」と宣言して書くことで作られます。そしてこの宣言は、指示しないと書かれません。「Agentはグルーピングが苦手」という話ではなくて、名指しで要件に入れれば入るし、入れなければ書かれない、というだけの話です。 ただし、指示しても粒度までは揃いません。囲んだ中に図形が1つしか無いと、束ねる意味がないので束ねない仕様で、宣言はあるのにグループが1つもできない枚が出ます。カード1枚を掴んで動かせる粒度で出したいなら、「何を1つのまとまりとして囲むか」まで書いておくほうがいいと思います。 実際に手元のデッキで試してみた まず1枚だけ、見てもらうのが早いです。会社の実物テンプレは外に出せないので、以降の画像は同じ構成で起こしたサンプルのテンプレで撮っています。 1枚できたなら全部いけるはずだと思って、実際のセミナーデッキ2本で、フルデッキ分を全部やりました。まずは33枚のデッキの結果を、全枚まとめて置いておきます。 このデッキはパレットも構成も、さっきの1枚とはまったく別のものです。33枚のうち32枚をAgent32体に並列で新規著作させたところ、約8分で書き上がりました。残り1枚は、Agentに渡す手本として先に人が作ったものです。33枚すべて、合体そのものには成功しています。1枚ごとの中身が独立している、という線があったからこそ、32体に切って同時に投げられました。これは検証中に一度計測した実測値で、日常的に運用で回しているわけではありません。 念のため、まったく別のデッキでも同じことを試しています。最初の1枚を出した26枚のデッキで、既存のビルダ(1枚の中身を描くコードです)をそのまま流用して同じ1本に通しただけですが、こちらも問題なく合体できました。1本だけで、たまたま噛み合ったわけではなさそうです。 33枚のデッキのほうには、表紙や章扉のような暗い背景のスライドだけ下地の紺を別に用意して包む、という場合分けが1つだけ残っていました。これも、会社テンプレ側に紺の型を焼き足したことで、その場合分けごとテンプレ側へ移せています。合体の結果は1枚も変わっていません。 テキストは全部移る。図はカードごとに掴める ここが一番でかいところです。最初の1枚を重ねてレンダーしたとき、Slidev側ではnavyのナビ帯だったところが、そのまま会社テンプレのヘッダとロゴに置き換わっていました。図のほうはそっくり移っている。この画面が出てきたときは、正直ちょっと感動しました。何がそんなに嬉しかったのかというと、この2つです。 テキストは全部移ります。 図の中の文字は、1つも画像になっていません。33枚のデッキだと、生のテキストのrunが451本。PowerPointで選んで、そのまま打ち直せます。 図はグループのまま移ります。 しかも1スライド丸ごとで1つではなく、カード・行・ステップといった見た目のまとまりごとです。同じデッキで105グループ。掴んでドラッグすれば動きますし、まとめて縮めるのもできます。 渡した相手が自由に組み替えられる形で出す、というのが最初の要件でした。この2つが通っているので、渡した先で「ここだけ動かしたい」と言われても、その1枚を作り直す話にはならないんですよね。 どういう確認をしたのか 仕掛けを入れたからといって、毎回そのとおりに出るわけではありません。ただ、崩れるところはだいたい決まっていて、4つです。 背景の装飾が二重に残る (Slidev側の装飾と会社テンプレの装飾が、両方出てしまう) 図が本文枠からはみ出す、または縮みすぎて読めない大きさになる ロゴや罫線、フッターとぶつかる 文字が画像になっている (Agentには「画像にせずテキストで描け」と指示していますが、守られたかは見ないと分かりません) 崩れる場所が決まっているなら、確認の順番も決められます。しかも落ちたところをAgentに渡すガイドの側に書き戻しておけば、次から同じところでは落ちなくなるんですよね。この辺はAI様様ですね! 確認は3段で組んでいて、最後の1段が自分でPowerPointを開くところです。 1段目は、Agent自身に見させます。書いたものをそのまま画像に出して、元のスライドの画像と見比べて、はみ出しや重なり、欠落があれば自分で直させる。最大3回までのループです(画像に出すのはLibreOfficeにやらせています。コンテナにPowerPointが無いので)。AIに書かせるだけでなく、AIにレビューさせる段ですね。 2段目が機械です。さっきの4つを、そのまま監査項目にしてあります。Agentがよく失敗するところを観点として先に固定して、毎回同じ目で見させるので、役割としてはレビューに近いです(何をどう見ているかは付録に置きます)。機械に振ったのは、開いて眺めても分からないことだからです。背景が剥がしきれずに二重で残っていても、開いた絵はほとんど同じに見えます(実際、実機で「表示は問題ない」のに剥がし漏れだった版がありました)。図の中の文字が画像になっていても、絵として見るぶんには区別がつきません。縮めた結果として文字が読めない大きさになっていないかも、寸法を測らないと出てきません。 そして最後の段が、PowerPointで開いて自分が見るところです。前の2段はどちらも近似です。絵に出しているのはLibreOfficeで、機械が測っているのは寸法とXMLですから、フォントの出方も含めて最終の判定は実機でやります。実際には、Windowsのモバイル版ではないPowerPointで、3ファイルずつを2ラウンド、計6回開きました。1ラウンド目は会社テンプレ実物の版(26枚/33枚と、レイアウト見本7枚+合体サンプル3枚のデモ)で、修復ダイアログは出ず、文字も編集できてグルーピングも効いていました。ただ、このラウンドは中身のフォントがArialに落ちていました。2ラウンド目が、この記事の画像を撮っているのと同じサンプルテンプレの版(フォントを焼き込んだ26枚/33枚と、レイアウトを焼き足したテンプレ本体)で、こちらは修復なし・グルーピング可で、フォントもメイリオで出ています。 最後の段を自分でやっているのは、前の2段が見ているのが機構の健全性であって、中身の忠実度ではないからです。Agentが元と違う内容を描いてしまっても、機械のレビューは素通りします。丸投げが成立するのは「人が見なくて済むから」ではなくて、XMLの検分をしなくて済むぶん、絵の中身が合っているかどうかに集中できるから、という話なんですよね。 合体したあと、PowerPoint側で直すところは残る 一点留意事項として、完全に再現するわけではありません。「いや!できないんかい」って思いましたよね。でも安心してください。そんな深刻ではなく軽微な修正です。 合体させたあと、PowerPoint上でやる調整がいくつか出てきます。網羅した「残るのはこれだけ」という話ではなく、実際に手元で確かめたファイルの範囲で、こういう調整をすることになった、という紹介です。 実際に崩れているものを、先に見てもらいます。図は33枚デッキ側の1枚です(26枚の方はデッキを版で固定して回しているため、いまの書き出し画像と対応が取れなくなっていました)。 右側は、カードの文字が枠から数行分あふれていて、アイコンも小さな色付きの丸に置き換わっています。これから書く調整のうち2つが、この1枚に一緒に写っています。 数でも出しておきます。33枚のデッキで、機械のレビューを全部通ったのは22枚でした。落ちた枚も、合体そのものには成功していて、引っかかっているのは、これから挙げる調整のほうです。なお、この記事に載せている画像はSlidevと同じ基準サイズのサンプルテンプレのものです。会社の実物テンプレは幅が狭いので、そちらだともう少し厳しく出ます。 アイコンは元と同じ絵にならない 元のデッキのアイコン(Font AwesomeやMaterial Iconsです)を、Agentは①②③や↻、≡のようなUnicodeの記号に置き換えたり、小さな色付きの図形に置き換えたりして描いています。ガイドでは「元のアイコンは描かない。省略するか、小さな色付きの図形で代替してよい」とだけ書いていて、代替の仕方までは決めていなかったので、枚によってどちらになるかはまちまちです。フォントを明示してあるので表示自体はされますが、元と同じ絵にはなりません。ここは調整で持っていける範囲だと思っていますが、元のアイコンにこだわるなら、PPTX側で差し替える作業が要ります。 紺で覆った表紙・章扉ではロゴが沈む 全面を紺で覆った表紙・章扉だけに出るものです。ロゴがラスタ画像のため、白抜きへの差し替えが必要で、そのままだとワードマークが沈んで見えることがあります。テキストの色を反転させるだけでは直らず、これはテンプレのアセット側の話です。ページの地の色によっては気にならない程度で、あまり大きな問題ではありません。 テンプレの幅が狭いと、密な1枚があふれる 手元の会社テンプレの実物は幅が狭く、Slidevの基準サイズよりひとまわり縮んで会社テンプレの本文枠に収まる形になっています。密なスライドだと、本文が枠の中で折り返しきれず、わずかにあふれることがあります。実感としては「完全にあふれたものはなかった」「折り返して多少あふれているなというのはある。許容範囲」「簡単に修正できそうなもの」という程度です。自分のテンプレがSlidevと同じ基準サイズなら、そもそも縮める必要がないので、ここは起きません。中央寄せのような細かい寄せの調整も、あふれと一緒にこの段で直しています。どちらもPowerPoint上で選んで直せる範囲です。 それでも、直すのは調整の範囲 機械のゲートは、実機で見るよりも厳しく出ます。可読性を見るゲートはNoto系のフォントの寸法で判定していて数枚を落としましたが、実機のPowerPointで実物テンプレの合体ファイルを開いた範囲では、ここまで顕在化せず、完全にあふれたものは1枚もありませんでした。見逃しているわけではなく、前段のふるいとして安全側に外れている、ということです。 直すのはPowerPoint上の調整であって、作り直しではありません。完全に同じものが出てくるわけではない、というのは正直に認めます。ただ、そこから先が調整で持っていける水準まで来ているのは、実際に触ってみて感じたところです。 自分はこの丸投げに乗る側か 軸は2つです。ひとつは、そもそも会社フォーマットで出す必要があるかどうかです。必要がないなら、PDFで配るなり、Slidevのまま見てもらうなりで足ります。もうひとつは、中身が繰り返す枠中心か、1枚ごとに作り込む図が主役かです。繰り返す枠が中心なら、会社テンプレへの流し込みで足ります。1枚モノが多いほど、今回のやり方が効いてくるんですよね。 振り分けたうえでの効能は、渡した先でテキストが打ち直せてカードごと動かせること、どの型のスライドも同じ1本を通ること、デッキが変わっても仕掛けを書き直さずに済むこと、機械が先にふるってくれるので通しで見るところに集中できること、です。 会社テンプレの詳しい機序が知りたい人は Slidevの中身を会社のPowerPointテンプレに入れる話 へ。会社テンプレをそもそも持っていない人は Slidevのデザインを実測してPowerPointのテンプレをコードで作る話 へ。逆に、会社のPowerPointの資料を持っていてSlidev側へ持っていきたい人には 会社のPowerPointの資料をSlidevへ移す話 という選択肢もあります。要件によってどの道を取るか迷う人は SlidevからPowerPointへ渡す道を整理した記事 へどうぞ。 ただ確実にPPTXへ変換するのに時短になっています。しかも圧倒的に!! まぁでもPPTXでほしいって言われることが少ないのですけども。依頼が来たら、いつでも出せる状態にはしてあります。 付録:手元で再現するための情報 ここから下は、同じことを自分の環境でやるための材料です。実際に踏んだ罠もそのまま置いておきます。 環境 Python 3.12 と python-pptx 1.0.2、lxml 6.1.1、会社テンプレ実物です。仮想環境を作らずに試すなら uv が楽です。 uv run --with 'python-pptx==1.0.2' --with 'lxml==6.1.1' python merge.py 会社テンプレの実物が手元にない場合は、タイトルと本文のプレースホルダを持った本文レイアウトが1つあるpptxであれば、同じ手順を試せます。見た目までは同じになりませんが、仕掛けが動くところまでは確認できます。 Agentに渡したガイド 配るものはコードではありません。というより、 コードを配る意味がない んですよね。中身を書くのはAgentなので、手元にAIがあるなら、渡すのは書かせ方だけで足ります。以下は並列で書かせた33枚のデッキ側に渡したもので、1エージェント=1スライドで指示していたのはこの7つです(26枚の方の21ビルダは別の検証から流用したので、このガイドの産物ではありません)。 描かない領域と描く領域 : 固定ヘッダ(ネイビー帯+大見出し)は描かない。会社テンプレのガワに委譲されていて、見出しはこちらがTITLEプレースホルダへ流します。本文の描画領域は座標で指定します。 with component(slide, "name"): でグルーピングを宣言 : カード・行・列といった視覚のまとまりをこの構文で囲みます。合体するときにここがグループとして掴めるようになります。 テキストは必ずテキストで描く : 画像化しません。ライブテキストとして残すのが目的です。 色はトークンだけを使う : 決められた色のトークンだけを使い、生のHEX値を増やしません。 アイコンは描かない : 元のアイコンは省略するか、小さな色付きの図形で代替してよい、とだけ指示しています。 自己検証ループ : レンダーしてGround Truthの画像と見比べ、はみ出し・重なり・欠落があれば直して再実行します。最大3回まで。 完成例を1枚渡す : 動作確認済みのビルダを手本として1枚渡します。これが「手本1枚」の正体です。 このうち2つ目のグルーピングは、 名指しで指示しないと入りません 。ガイドに入れて書かせた33枚は、33枚とも宣言が入っていました。一方、宣言を要件に入れていなかった既存21ビルダの26枚は、宣言があったのが3枚だけで、残りは位置関係からの推測に回っています。指示しても粒度までは揃わず、33枚のうち3枚は、宣言はあるのにグループが1つもできませんでした。囲んだ中に図形が1つしか無いと、束ねる意味がないので束ねない仕様だからです。「何を1つのまとまりとして囲むか」まで書いておくほうがいいと思います。 02の付録「AIに変換器を書かせるときに渡したもの」と対になる話です。渡すのはコードではなく、 領域の切り方と、守らせる約束と、自分で回せる検証 です。 中身を器へ収める1本のコード(アダプタ)がやっていること Agentが返してくるのは、その1枚の中身の図だけです。置き場所も寸法も、会社テンプレとの噛み合わせも決めているのは、間に挟んだ1本のコードで、これをアダプタと呼んでいます。1枚ごとにやっているのは、この6つです。 会社テンプレのレイアウトでスライドを起こす 。ロゴ・ヘッダ帯・フッター・ページ番号は、ここで勝手に出てきます。 見出しをタイトルの枠へ流す 。ロゴと重ならない幅に制限してから入れます。 Slidev側の装飾は描かせない 。会社テンプレの装飾と二重になるからです。装飾を描く箇所は決めたフック越しにしか通らないようにしてあるので、そのフックを空にしてから走らせます。 図の実寸を測って、本文枠へ縦横同じ倍率で写す 。倍率は座標とサイズだけでなく、フォントサイズ・線の太さ・影にも同じ係数で焼き込みます。 フォントを1つずつ焼き込む 。会社テンプレ側に指定が無く、放っておくとArialに落ちるからです。 まとまりをグループとして束ねる 。カードや行の境界は、Agentが書いたコード側で宣言してもらったものを使います。掴んで動かせるのは、ここのおかげです。 あとは使わない本文プレースホルダを外して終わりです。型ごとに場合分けを書く必要もなく、表紙だから章扉だからと処理を切り替えることもありません。パレットも構成も違う2本のデッキを通しましたが、アダプタは1行も書き直していません。配色とレイアウトの色を差し替えただけです。 契約の実体 装飾を描く箇所を契約フック越しに通す、というのを最小の形で書くとこうなります。 # 契約側(figure_kit) def chrome(slide, draw): # ガワ描画の契約フック。既定は素通し draw(slide) def background(slide, fill_xml): # ビルダはこのヘルパを呼ぶ chrome(slide, lambda s: shape(s, H.decor_rect("bg", 0, 0, 1920, 1080, fill_xml))) class component: # with で囲んだ範囲を1グループとして記録する ... # ビルダ側 def build(slide, ctx): background(slide, H.solid_fill(NAVY900)) # chrome() を直接呼ばない(後述の罠3) with component(slide, "card0"): draw_card(slide, ctx) # アダプタ側:モジュールの名前を差し替えるとガワだけ消える figure_kit.chrome = lambda slide, draw: None H.decor_rect() は図形のXMLを文字列で返すだけで、それをスライドに足すのが shape() です。ビルダが background() のようなヘルパ経由で呼ぶのがポイントで、ビルダの中で chrome() を直接呼ぶと、アダプタ側の差し替えが効きません(罠3)。 宣言が無いものを位置関係から当て推量で拾う、ということをしないのは chrome() の方だけです。 chrome() はフック越しに呼ばれなければ何も描かないので、装飾を位置から推測することがありません。一方の component() は、宣言が無ければ図形の位置関係から判断する容器包含の空間ヒューリスティクスにフォールバックします(大きい矩形を容器、その中に6割以上収まる矩形を子として束ねます)。26枚の内訳は、宣言があったのが3枚、位置関係からの推測に回ったのが23枚(うち2枚はどちらも効かず単体のまま)でした。ガイドで宣言させた33枚の方は、33枚とも宣言が正として使われています。 グループそのものはOOXMLの <p:grpSp> です。python-pptxにグループ化のAPIが無いので、アダプタが <p:grpSp> を1つ組み立てて、対象のシェイプを spTree から外してその下へ移し替えています。 off と chOff 、 ext と chExt を同じ値にした 恒等変換 なので、群を作っても座標もフォントも動きません(スケールは各シェイプに焼き込み済みなので、群に倍率を持たせる必要がない=罠1の裏返しです)。束ねるのは機械ゲートを算出したあとです。ゲートは個々のシェイプの矩形で測るので、群にする前に測る必要があります。 機械ゲートM1〜M4 M1 装飾剥がし : 会社テンプレの装飾(ロゴ・帯・フッター・ページ番号)以外の背景要素が残っていないか M2 座標接合 : Agentが書いた図が、会社テンプレの本文枠(BODY)に収まっていて、読める大きさかどうか M3 共存 : ロゴ・罫線・フッターと物理的にぶつかっていないか M4 編集可能 : 図の中の文字が、画像ではなく <a:t> という生のテキストのrunとして残っているか。判定は、図のシェイプをXMLとして開いて <a:t> 要素を数えるのと、あわせて画像シェイプ( a:blip )が0件であることを見ます。runが1本でもあれば通してしまうと、ほとんどが画像でテキストが1本だけのスライドも合格になるので、この2つを対で見る必要があります 踏んだ罠 グループ化した図形をそのまま一括スケールすると、幾何だけスケールされてフォントがスケールされないことがあります(実測。0.5倍のグループでテキストがほぼ原寸のまま残りました)。幾何とフォントを別々に手でスケールするやり方は、実機で二重にスケールされる恐れがあります。ここは未確認の推定です 会社テンプレにレイアウトを足すときは、レイアウト本体・そのrels・ [Content_Types].xml のOverride・スライドマスターのレイアウト一覧とrelsの4か所を触ります。暗い地の色にする場合は、テキスト色の反転もセットで必要です(複製元が白地前提なので、プレースホルダもページ番号もフッターも沈みます) 装飾の契約はヘルパ越しに呼ばせます。ビルダ側が from figure_kit import (...) のように名前を束縛していると、ビルダの中で直接フックを呼んでもアダプタ側の差し替えが効きません 全面を覆う装飾は、共存の判定から外します。背景そのものなので、外さないと共存ゲートが全滅します 恒真になってしまうゲートは、合格の根拠にしません(本文枠に収まっている・ロゴ等とぶつからない、という判定は、写像の作り方からして自動で真になるため) フォントは指定しないとデフォルト(Arial)に落ちます。会社テンプレはプレースホルダにフォントの指定が無く、テーマ・マスター側のArialに落ちるので、Agentが書く図の中のrunにも明示しないと、日本語も記号も置き換え任せになります 変換元のデッキはバージョンで固定します。デッキ側が動くと、同じコードでも結果が再現しません 並列Agentへの投げ方 上のガイドを1エージェント1スライド=1ファイルに割り当てて、並列で投げています。途中の確認は Slidevの中身を会社のPowerPointテンプレに入れる話 ・ Slidevのデザインを実測してPowerPointのテンプレをコードで作る話 と同じ手順なので、そちらを見てください。 ではまた! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Slidevのスライドを、手で組み直さずに編集できるPPTXにする first appeared on SIOS Tech Lab .
ども!SlidevからPowerPointへスライドを渡す道を、ひとつずつ試している龍ちゃんです。 Slidevで作ったデッキをPowerPointに持っていく実験をずっとやっていて、今回はそのうちの1本です。Slidevにもpptxの書き出し機能はあるんですが、出てくるのは各スライドを画像にして貼っただけのファイルで、開いても誰も直せません。以前「 Claude CodeでSlidevのスライドを作る話 」で「配布用の最終形と割り切る」と書いたのがこれですね。 じゃあ画像じゃない形で渡そうとなるんですが、そのとき流し込む先のPowerPointテンプレが手元にありませんでした。ただ、デザインそのものはSlidev側でもう詰めてあるんですよね。色も文字サイズも帯の太さも、手元で表示を確かめながら決めた値がテーマに書いてあります。今回はそこで、テンプレの方をpython-pptxのコードから作ってみた、という話です。デザインを決めるのはHTMLとCSSの世界でやって、決まったものだけPowerPointへ持ち込む形ですね。 やってみて受け取れたものを先に書いておきます。 Slidevのデザインが、PowerPointのテンプレとして受け取れます。 自分のテーマの色もフォントも帯も、PowerPointの「新しいスライド」に並ぶレイアウトとして出てきます。しかもその変換器は、AIに書かせられます。何を渡せば揺れずに出てくるのかまで、この記事に置いておきます。 SlidevからPPTXへ渡す道はこれ一本ではなくて、この記事は僕が並べて検証したうちの一本です。全体像は SlidevからPowerPointへ渡す道を整理した記事 にまとめてあるので、自分に合う道から選びたい人はそちらへ。この記事だけで完結するので、そのまま読み進めても大丈夫です。 流し込む先が無いなら、テンプレの方をコードで作る 先にゴールを置きます。 Slidevで作り込んだ自分のデザインを、PowerPointで他の人が直せる形のまま残したい。 ここから制約を足していくと、やることが決まっていきます。 まず、Slidevのpptx書き出しをそのまま使う道は最初に外れます。出てくるのが画像なので、編集できる形にならないんですよね。なので運ぶものを出力からデザインの値そのものに切り替えます。色・フォント・帯の寸法といった、デザインを決めている数字の方を持っていく、ということです。さっきの割り切りを取り消したいわけではなくて、渡して見てもらうだけならあのままでいいと思っています。今回は自分のデザインを残したまま後から中身を直したい、という別の要件の話ですね。 次に、その値を流し込む先が無い。会社の空テンプレが出てこなかったので、 python-pptx でテンプレごとコードから作ることにしました。ここが今回のアプローチです。会社のPowerPointテンプレをちゃんと持っているなら、そこに中身を流し込む方が楽ですし、出てくる見た目は再現ですらなく本物です。そっちは Slidevの中身を会社のPowerPointテンプレに入れる話 に書いてあります。この記事は、そのテンプレが手元に無い側の話ですね。 最後に、編集できる状態のまま、手作業も増やしたくない。ここは欲張らないことにしました。見た目の完全再現と、手作業を最小にすることと、編集できることの3つは同時に取れません。前の2つを取ると画像を貼ったpptxになって編集できなくなる、というのがさっきの記事の結論でした。なので完全再現の方を諦めて、繰り返し出てくる枠が揃っていればいい、というところまで弱めています。 ついでに立ち位置も書いておきます。今回もAIは使っています。ただ書かせているのは変換器の方で、成果物ではありません。デザインの値はもう自分のテーマに書いてあるので、それをテンプレ.pptxに変換するスクリプトをClaude Codeに書いてもらって、あとはそれを実行するだけです。pptxを作る生成AI系のツールはたくさんあって、どれもテンプレも中身も毎回AIに出させる形なんですよね。一度変換器にしてしまえば、AIに毎回お願いするのは変換器にできない部分だけで済みます。 自分のテーマに書いてある値を、テーマとレイアウトに移す やっていることは、Slidev側にある値をPowerPoint側の入れ物に移し替える作業です。 前提として、デザインの値は目分量で測る必要がありません。自分で作ったテーマなら、色もフォントも寸法もCSSに書いてあるからです。僕の場合は色もサイズもCSS変数で縛る作りにしていて、そのあたりは以前「 SlidevのデザインシステムをCSS変数とCLAUDE.mdで作る 」に書きました。ただ、どこに何を書いてあるかは人それぞれなので、以下は僕のテーマの場合として読んでください。 移し先の呼び方も先に置いておきます。PowerPointで色とフォントの定義を持っているのが「テーマ」、スライドの型が「レイアウト」(それを束ねているのがスライドマスター)、レイアウトの中でテキストを入れる枠が「プレースホルダ」です。何を吸い出して何を作るかで並べると、こうなります。 吸い出す(Slidev側) 出どころ(僕のテーマの場合) 作る(PowerPoint側) 色6つ( --navy-900: #0B1F3A ほか)とグラデの停止点 theme/styles/index.css の :root テーマ の配色 フォント2種(Noto Sans JP / Inter) 同上と DESIGN.md テーマ のフォント 使っている文字サイズ(表紙64px・章扉48px・h2 30px・本文19px・kicker〈見出しの上に小さく載せるラベル〉12pxなど) DESIGN.md のタイポスケール 各 レイアウト のプレースホルダの既定書式 帯68px・本文の余白24×40px・kickerのamberチップ theme/layouts/*.vue レイアウト の上に置く図形と余白 表紙・章扉の全面navy theme/components/CoverHero.vue ・ section.vue 表紙 レイアウト ・章扉 レイアウト 見てほしいのは右の列で、色とフォントはテーマ側、見た目の骨格はレイアウト側、と行き先が分かれるところです。真ん中の列はあくまで僕のテーマの置き場所なので、自分のテーマだと違う場所にあると思います。 単位はSlidev側がpx、PowerPoint側がptやEMUなので変換が要るんですが、ここは一律の掛け算で済みます。掛ける数をひとつ決めたら、あとは機械的に置き換わるだけ。元の比率も元の数字もそのまま残るので、ここで悩むことはありませんでした(掛ける数の決め方は付録に置いておきます)。 で、ここが一番おいしいところなんですが、書式はテーマとレイアウトが持っている状態になります。 つまりここまでが一度だけの作業で、あとは内容を流すだけでSlidevで使っていたのと同じ見た目が出ます。 実際どこまで揃ったのかは、並べて見てもらった方が早いですね。 作ったテンプレを、元のデザインと並べてみた まず、そもそもテンプレとして成立しているのか。PowerPointで開いて「新しいスライド」を押した画面がこれです。 RAG STANDARD というグループ名の下に、表紙・本文・章扉が並んでいます。ここに出てくるということは、PowerPointの側からは普通のテンプレとして見えている、ということですね。3つしか並んでいないのは後で書きます。 次はデザインです。 確かめたのは、全スライドで繰り返し出てくる枠です。 色とフォント、文字サイズ、帯、表紙と章扉の骨格。ここから先の数字は全部この範囲の中の話になります。 元のSlidevと並べたのがこれです。 左が元のSlidev、真ん中がコードで作ったテンプレに同じ内容を流したもの、右は後で出てくる手直し版です。上が表紙、下が本文のスライドですね。 通ったものから書きます。 色は主要な6つ(navy-900・navy-700・blue-600・amber・背景の白・本文の文字色)がHEXでそのまま一致しました フォント2種(Noto Sans JP と Inter)と、実際に使っている8階層の文字サイズが、比率を保ったまま乗りました 帯の高さは設計値どおりで、スライド高さの6.30%(68px / 1080px)です 内容を変えても手作業は増えませんでした。違う内容3本で試して、毎回コマンド1発。項目を増やして本文があふれそうな3本目でも、あふれは出ていません 本文はテキストのまま残ります。照合は16/16で、画像になった箇所は0でした 確かめ方は3通りです。PPTXをzipとして展開して中のXMLを直接読む、LibreOfficeという別のエンジンでレンダリングして目で見る、そして内容を3本流してみる、の3つですね。 ちなみにこの帯もテーマの色も、python-pptxの高水準なAPIからは触れませんでした。1.0.2で確認したんですが、レイアウトの図形を扱うクラスには図形を足すメソッドがそもそも1つもないんですよね。なのでOOXML(PowerPointのファイルの中身に入っているXMLです)を直接書いて置いています。裏返して言うと、OOXMLまで降りれば届く層がある、ということでもあります。 ただ、並べると見えてしまうものがあります。測った項目としては通っているんですが、目で見ると気になる、という別の観点の話です。 気になるのは真ん中の列です。色も配置も比率も合っているのに、文字も帯も一律で小さい。全体が半分くらいのサイズで乗っています。原因は換算の分母で、僕は設計上の1920pxを取ったんですが、Slidevが実際にレンダリングしているキャンバスは980px幅なんですよね。同じCSS pxが、実物では倍くらいの存在感で出ていたわけです。帯の高さで言うと、元が12.38%に対してこっちは6.30%。ほぼ半分ですね。 対処は分母を実レンダの980pxに取り直すだけです。デッキの中での比率は分母に関係ないので、さっきの判定は動かないまま見た目だけが揃います。それが右の列なんですが、これで全部揃ったわけではありませんでした。帯と本文は左とほぼ同じ大きさになったのに、表紙の大見出しだけは行き過ぎて元の1.5倍くらいになっています。分母は一発の正解じゃなくて出発点で、表紙の大見出しは個別に詰める必要が残りました。 どこまで確かめたかも、ここで1回だけまとめておきます。このファイル、WindowsのPowerPointでも開いて確認しています。修復ダイアログは出ませんでしたし、さっきの画面のとおりテンプレとして機能しますし、本文はテキストとして直せますし、フォントも意図したとおりに出ました。残っている限定はひとつで、デザインを当てたのが繰り返し使う3つのレイアウトだけ、という範囲の話です。ただこれは弱点というより、次の話そのものなんですよね。 テンプレに入るのは繰り返す枠で、1枚モノは入らない じゃあ自分のデッキ、全部これで出せるのか。ここが今回の一番の話です。 線は1本だけで、繰り返すか、1枚モノかです。 入る側はもう見せていて、さっきの「新しいスライド」に並んでいた表紙・本文・章扉の3つがそれです。python-pptxが最初から持っているレイアウトは11個あるんですが、使うのはこの3つだけなので、残りは消してあります。繰り返し使うものだけ用意すれば足りるので、開いたときに3つしか出てこないのはそのためですね。 1枚モノを入れないのは、無理だからというより過剰だからです。レイアウトは同じ形を何度も使うための入れ物なので、1回しか出てこない図をレイアウトに彫っても、そのレイアウトは一生に一度しか使われません。作る意味がないんですよね。 ただ、これは「1枚モノは大したことない」という話ではないです。むしろ逆で、デッキの色が出るのはこっち側なんですよね。1枚だけ出しておきます。 文字は取り出せます。でもこのスライドで意味を持っているのはカードの並びとアイコンの方なので、枠に文字を入れても同じ絵にはなりません。こういった配置が自由にできて、誰でも編集ができるのがPPTXの強みです。 カードの配置そのものはOOXMLを手で書けば形にできますが、このアイコン(Material Iconsのリガチャ)と和文の文節折返し( word-break: auto-phrase )の2つだけは、OOXMLの仕様を当たった限り等価な書き方が無くて、ライブラリを変えても出せません。 そしてコストの形が、この線からそのまま出てきます。繰り返す枠の方は一度書けば終わりで、デッキを何本作っても手作業は増えません。減らないのは1枚モノの方だけで、デッキを作るたびにそこだけが毎回発生します。どれくらい時間がかかるかは今回きちんと計っていないので、重いと数字で言い切ることはしません。 正直に書くと、僕が一番やりたいのはそこなんですよね。今回のやり方で一番色が出るのは、ちょうどOOXMLを手で書かないと届かない側でした。テンプレの方はあっさり出来たのに、一番作り込みたいところにこの道では届かない。そこがつらいところだなと思っています。 なので今回の検証で得たものは2つあると思っていて、ひとつはテンプレが作れたこと、もうひとつは穴の在処がはっきりしたことです。だから別の手が要る、というところまでが今回ですね。 で、自分はこの道に乗る側なのか 判断は2段階でいいと思います。 ひとつめは、そもそも使える会社のPowerPointテンプレを持っているかどうかです。持っているならそれを開いて中身を流し込む方が楽ですし、出てくる見た目は再現ですらなく本物のブランドです。この道が要るのは、テンプレが出てこない場合か、自分のデザインを持っていてそれを残したい場合ですね。 ふたつめは、自分のデッキの見せ場がどちら側にあるかです。枚数の話ではありません。表紙や章扉や本文みたいな繰り返す枠に価値の大半が乗っているなら、一度書いた分がそのまま効いてきます。逆に、毎回作り込む1枚モノで勝負しているデッキだと、テンプレに入らない側が支配的になるので効きません。 実際のところ、たいていのデッキは混ざっていると思います。僕のもそうで、繰り返す枠のあいだに作り込んだ1枚が挟まる形です。その場合はどちらかを選ぶんじゃなくて、繰り返す枠はテンプレに任せて、1枚モノにだけ別の手を用意する、という振り分けになります。 その振り分けで進めるなら、けっこう気持ちよく回ります。デザインの値がコードの中にあるのでgitで差分が見えますし、色を変えたければ値を書き換えて作り直すだけです。リブランドがかかっても同じで、テンプレを作り直して内容を流し直すだけになります。実際に手を動かすときの材料(換算の決め方、デザイン値の書き出し方、僕が踏んだ罠)は、記事の最後に付録としてまとめてあります。 1枚モノに届かない話の続きも書いておきます。OOXMLで書ける部分だけAgentに書かせて、会社のテンプレと合体させる、というやり方があります。実際にそこまでやったのが Slidevの中身をAgentに書かせて会社テンプレへ合体させる話 で、この連載の到達点です。一番作り込みたいところが残ってしまった人は、そちらへどうぞ。 あと、会社のPowerPointの資料を持っている人には、そこからSlidevへ持ってくるという選択肢もあります( 会社のPowerPointの資料をSlidevへ移す話 )。 最後に一度だけ書いておくと、 繰り返す枠と1枚モノで切る、というのはこの道での結論 です。テンプレが手元に無い、という条件から出発した一本なので、そもそも自分の条件だとどこに着くのかを先に見たい人は、 SlidevからPowerPointへ渡す道を整理した記事 の方に4つ並べてあります。 正直なところ、PowerPointを一度も開かずに、Slidevで作ったものをデザインパターンとして持ち込めるところまでは来ました。ただ今回のやり方だと中身までは作ってくれないので、資料づくりの時間がガツンと減るかというと、たぶんそこまではいかないんですよね。そこがつらいところです。 付録:手元で再現するための情報 ここから下は、同じことを自分の環境でやるための材料です。実際に踏んだ罠もそのまま置いておきます。 環境 Python 3.12 と python-pptx 、それにlxmlです。python-pptxは1.0.2で確認しています。元のデッキ側はSlidev 51系です。仮想環境を作らずに試すなら uv が楽ですね。 uv run --with python-pptx --with lxml python build_template.py 換算の決め方 スライド幅を960pt、Slidevのキャンバスを1920pxとして、 k = 960pt / 1920px = 0.5 を一律で掛けています。EMUで言うと 12192000 / 1920 = 6350 がpxあたりの値です。文字サイズはcentipoint(ptの100倍)で書くので、 px × 50 になります。 ここで1920pxは設計上の分母です。Slidevが既定でレンダリングしているのは980px幅なので、投影したときの絶対的な大きさを実物に合わせたいなら、そちらを分母に取り直す運用になります。ただし本文で書いたとおり、取り直しても全部は揃いません(帯と本文は揃いますが、表紙の大見出しは目標の1.5倍くらいに行き過ぎます)。分母は一発の正解ではなく出発点だと思ってください。 デザイン値の出どころ 色は theme/styles/index.css の :root 、タイポスケールは DESIGN.md 、帯やpaddingは theme/layouts/*.vue から拾いました。コードを書き始める前に全部書き出して固めておくのがおすすめです。途中で拾いに戻ると値がぶれます。 AIに変換器を書かせるときに渡したもの このスクリプト、僕が1行ずつ書いたわけではなくてClaude Codeに書かせています。ただ「Slidevのデザインをpptxテンプレにして」だけ投げると、それっぽいけど値が微妙に違うものが出てきて、直すたびに別のところがずれる、みたいなことになります。振り返ると、揺れなくなったのは次の3つを先に用意してからでした。 デザイン値を凍結した1枚 。色のHEX、フォント、使っている文字サイズをpxとptの両方で、帯の高さは相対値( 0.06296 のような)で、許容誤差まで書いてJSONにしておきます。「 index.css を見て」ではなく、値を確定させたものを渡す。ここが曖昧なままだと出力が毎回変わります 入れ物の対応 。どの値をテーマに入れて、どの値をレイアウトに入れるか(本文に出した対応表がそれです)。これを決めずに書かせると、書式がスライド側に直接乗る形になって、下の罠2のとおりテンプレとして使い回せないものが出てきます 合否を機械で出せる判定コード 。作ったpptxをzip展開して、色・フォント・サイズ・幾何を1で凍結した値と突き合わせるスクリプトを先に書いておきます。これがあるとAIが自分で回して自分で直せるので、こちらが目視で差し戻す回数が激減します あと先に教えておくと詰まらないのが、下の罠1(レイアウトには高水準APIで図形を置けない)です。知らないと add_shape がある前提のコードを書いて、動かないので遠回りを始めます。 渡すのはコードではなく、値と、入れ物の対応と、合否の判定ですね。中身のコードはAIが書けます。 テンプレ構築コードの抜粋 帯を1本引く部分と、拾ったHEXをテーマの配色に置く部分だけ出しておきます。 PX = 6350 # EMU/px(12192000 EMU / 1920px) def cp(px): # px → centipoint(pt×100)。k = 960pt / 1920px = 0.5 return int(round(px * 50)) NAVY900, NAVY700 = "0B1F3A", "153764" # index.css の :root から拾った値 AMBER, BLUE600 = "F59E0B", "2563EB" # 帯:高さ68px(1080pxの6.30%)、navy-900 → navy-700 のグラデーション band = rect_sp(101, "fixed-band", 0, 0, SLIDE_W, 68 * PX, grad_fill_xml([(NAVY900, 0), (NAVY700, 100)], 0)) # CSSの90deg = OOXMLの0 append_to_layout(L_FIXED, band) # テーマの配色:拾ったHEXをそのまま srgbClr に置く f'<a:accent1><a:srgbClr val="{BLUE600}"/></a:accent1>' f'<a:accent3><a:srgbClr val="{AMBER}"/></a:accent3>' こんな調子で、色・帯・文字サイズを一個ずつ置いていってテンプレ( build_template.py )を組み、あとは内容を流す( fill_template.py )という構成です。 使わないレイアウトは消して、残す方に名前を付ける python-pptxが既定で持っているレイアウトは11個ありますが、作るのは繰り返し使う3つ(表紙・本文・章扉)で足ります。残りは SlideLayouts.remove() で消して、残した3つに日本語の名前を付けました。こうしておくと、テンプレを開いた人にそのまま「何を繰り返すか」が伝わります。消しても流し込む側のコードは無変更で動きます(インデックスの0/1/2が保たれるため)。 踏んだ罠 python-pptxの高水準APIは、レイアウトを組む用にはできていません。 1.0.2で確認したところ、レイアウトの図形を扱うクラスの公開APIは6つで、図形を足す add_* が1つもありません(スライド側のクラスには9つあります)。なので帯やkickerは <p:sp> のXMLを組んでspTreeにappendしています。テーマの中身も素のデータなので、配色・フォント・名前の3つをXMLで直接書き換えました。名前を忘れると「新しいスライド」のグループ見出しが Office Theme のまま残ります。画面に出る唯一の既定文字列なので、ここは直しておいた方がいいです。なお、レイアウトの削除と改名は高水準APIにあります。届かないのはレイアウトに図形を置くことと、テーマの中身の2つだけですね 流し込む側では書式を1行も書かないでください。 runに書式を書いた瞬間にレイアウト側の defRPr の継承が切れて、テンプレを差し替えても効かなくなります。この方式の旨味がそこで消えます レイアウトに図形として置いた文字は、そのレイアウトを使う全スライドで同じものになります。 kickerの RAG STANDARD と CHAPTER 01 は slideLayout2.xml と slideLayout3.xml にだけあって、どの slideN.xml にもありません。章扉を2枚作ったデッキを流し込んだら、2枚目もタイトルは「第2章」なのにeyebrowは CHAPTER 01 のまま出ました。変えたいならプレースホルダにしてください pandocにpython-pptx製のテンプレを --reference-doc で渡すと0スライドになります。 pandocは自前のreference.pptxの構造を前提にしているので、渡すなら純正のreferenceに色を着せ直す前段が要ります pandocという道もあります 「pandocでいいのでは」と思った人向けに書いておくと、純正のreference.pptxに同じデザインを着せれば、色とフォントと帯までは乗ります。ただし内容の形からレイアウトを自動で選ぶ仕組みなので、表紙と章扉のデザインは落ちます。そういう道もある、という提示までにしておきます。 途中の確認 コンテナの中で回しているので、1枚作るたびにPowerPointへ持っていくのは面倒です。僕はLibreOfficeでPDFにしてから画像にして目で見ていました。コマンドは Slidevの中身を会社のPowerPointテンプレに入れる話 の付録と同じなので、そちらを見てください。最後の確認は実機で開く、という使い分けで、今回もそこまでやっています。 ではまた! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post PowerPointテンプレが無いなら、Slidevのデザインから自作する first appeared on SIOS Tech Lab .
ども!最近はSlidevとPowerPointを行き来する検証にどっぷり浸かってる龍ちゃんです。 スライドを作るツールとして、Slidevはめちゃくちゃ良いんですよね。Markdownで書けるからgitで差分が見えるし、繰り返し出てくる部品はコンポーネントにして使い回せる。話の展開が文章のまま読めるので、流れが死んでいるのもスライドにする前に気づけます。生成AIに中身を書かせるのも相性がいいです。作る側の体験という意味では、正直もう手放したくないんですよね。 でも社内で共有するってなると、そこで詰まります。「うちのPowerPointフォーマットじゃないと通らない」「あとで他部署が直せないと困る」。僕の技術ブログでも以前「 Marp と Slidev の使い分け 」で触れたんですが、エンジニア以外が直接編集する前提ならPowerPointが正解、というのは今でも変わらない結論です。つまり書くのはSlidevでやりたい、でも出口は編集できるPPTXでないといけない。ここが今回のゴールです。 先に結論だけ書いておくと、 中身はSlidev、デザインはPowerPoint。 ただし持っていけるのはテキストとスライド割だけです。いま手元にあるデッキを移すんじゃなくて、これから作る分に効く話だと思ってください。 SlidevからPPTXへ渡す道はこれ一本ではありません。この記事は僕が並べて検証したうちの一本、 会社のテンプレファイルをそのまま開いて、そこに中身を入れる道 を切り出したものです。全体像は SlidevからPowerPointへ渡す道を整理した記事 にあるので、自分に合う道から選びたい人はそちらへ。この記事だけで完結するので、そのまま読み進めても大丈夫です。 Slidevから出るPPTXは画像だから、書き出しを使うのをやめた この検証のきっかけは「Slidevで作っていたスライドを編集可能でほしい」って言われたことがあって、そのとき最初に思ったのが「なんか超絶楽に変更できないかな〜」でした。 Slidevには pptx で書き出す機能があります。ただ、出てくるのは各スライドが画像として1枚ずつ貼られたファイルになります。見る分にはきれいなんですが、何も選択できないので誰も直せません。しかも画像なので、文字そのものが検索にかからない。PowerPoint内の検索でも、共有先の全文検索でも同じで、あとから「あの資料どこだっけ」と探そうとしても引っかからないんです。 以前「 Claude CodeでSlidevのスライドを作る話 」を書いたときは、ここは配布用の最終形と割り切る、と書きました。配布して見てもらうだけの資料なら、その割り切りは今も正しいと思っています。ただ、社内で回して他部署があとで数字を直す前提のケースには、別の道が要ります。 じゃあどうするか。まずはSlidevのPPTX書き出しを使うのをやめることにしました。 代わりに、会社のPowerPointテンプレの .pptx をコード側から開いて、レイアウトを指定しながらSlidev側で書いたテキストを流し込みます。PowerPointを人力で開いて調整するわけではありません。デザインはテンプレが元から持っているものがそのまま出るので、こちら側では何も作りません。出てくるのは画像を貼ったファイルじゃなくて、テキストが入ったPowerPointのファイルです。 担当分けを図にすると、こうなります。 ちなみにMarpを使っているなら話は変わってきます。 --pptx-editable (LibreOffice経由)で変換すれば、編集可能なPPTXとして使える可能性があります。使い分けは以前書いた「 Marp と Slidev の使い分け 」を見てもらえたらと思います。 考え方としてはこれだけです。実際どうやるのかは次で見ていきます。 会社テンプレのレイアウトを見て、選んで、プレースホルダにテキストを入れるだけ 先に読み方の前提を書いておきます。 ここから先の手順は、新規で作る資料を前提に読んでください。 PowerPointの正式な呼び方だと、スライドの型は「レイアウト」、その中のテキストを入れる枠は「プレースホルダ」といいます。やることはこの3つです。 会社テンプレが持っているレイアウトを一覧で見る。PowerPointでテンプレを開いて「新しいスライド」のドロップダウンを開くと、名前つきで並びます 中身に合うレイアウトを内容ごとに選ぶ。表紙・章扉・コンテンツ・2カラム・比較・裏表紙…といった具合です 選んだレイアウトでスライドを作って、プレースホルダにテキストを入れる。書式と配置はテンプレ側が持っているので、こちらでは何も指定しません この手順、選べるレイアウトが無かったり、入れる枠が無いテンプレだと成立しません。実際、最初に会社の実物テンプレを解析したときはそこでつまずきました。本文にプレースホルダがほとんど使われていなくて、フリーテキストボックスの山だったんですよね。あるスライドはテキストボックスが73個、別のスライドは15個+画像17枚。多いな〜と思いました。 ここで分かったのは、 手で作るPowerPointは、枠に入れるよりフリーテキストで組む方が普通 だということです。好きな位置に置いていけるので、手で作るならそっちが速いんですよね。だから「テンプレにプレースホルダがある」という前提は、思っているほど当たり前じゃありません。自分が使うテンプレを開いて、まずそこを確かめてほしいです。 これは、プレースホルダ運用されている会社公式の空テンプレートに差し替えることで解消しました。こっちは全レイアウトがちゃんとTITLE/BODYのプレースホルダ構成で、既存のスライドも書式ゼロの完全なレイアウト継承で組まれていて、テンプレ側がまともなら成立するという土台がここで確認できました。 逆に、こういう空テンプレが社内から出てこない場合、あるいは自分のデザインを持っていてそれを崩したくない場合は、テンプレの方を自分で作る話になります。そこは Slidevのデザインを実測してPowerPointのテンプレをコードで作る話 に回します。 プレースホルダにテキストを入れると、フォントや色だけじゃなく、枠の位置と大きさまでテンプレ側の指定がそのまま効きます。ただ、ここは言葉だけで説明しても伝わりづらいので、実際の見た目は次の節で見せますね。 この3手を実際に回しているのは Pythonと python-pptx です。 .pptx を読み書きするライブラリで、さっきの3手がそれぞれ1行で書けます。 prs = Presentation("company_template.pptx") # テンプレを開く slide = prs.slides.add_slide(prs.slide_layouts[1]) # レイアウトを選ぶ slide.placeholders[1].text = "Slidev側で書いた本文" # 枠にテキストを入れる 見てもらいたいのは、 書式が1行も出てこない ところです。フォントも色も座標も書いていないので、テンプレを差し替えても次の資料を作るときも、このまま使い回せます。Slidev側の中身は slides.md にMarkdownで書いてあるので、テキストはそこから普通に取り出せます。動く形の全体と、レイアウトや枠の番号の調べ方は記事末尾の付録に置いておきます。 実際にPowerPointに変換してみた このレイアウト一覧を見た瞬間、「これ、いま持ってる既存のデッキも全部ハマるんじゃないか」って思ったんですよね。それが実際どうだったかも含めて、ここから確かめていきます。 さっきの会社公式の空テンプレそのものは社内資産で外に出せないので、 同じ構造で起こしたサンプルのテンプレ で試しています。以降の画像はそちらです。 このスライドを作っているコードには、フォントも色も位置も一行も書いていません。流し込んでいるのはテキストだけです。 日本語がMeiryoでネイティブ表示されます。テーマ層のフォント指定はArialなんですが、各レイアウトの defRPr にMeiryoがlatin/ea両方へ焼かれていて、コード側で何も指定しなくても継承されました。全角括弧や「」、〜、①②まで確認して文字化けゼロでした ロゴ・ヘッダの帯・フッターの著作権表記・ページ番号がレイアウトから自動で描画されます。箇条書きもブランド色の■マーカーで出ます 本文がPowerPoint上で実際に編集できます。画像が貼られただけのファイルじゃなくて、テキストとして選択して直せます テンプレが持っている見た目が、そのまま出てくる。ここがテンプレをそのまま使うやり方の一番おいしいところだなと思いました。 このファイルはWindowsのPowerPointでも開いて確認しています。修復ダイアログは出ず、テーマもスライドマスターも意図した通りに効いていました。本文をクリックするとフォント名の欄はメイリオになっていて、実機でも同じ結果でした。 一方で、実害も書いておきます。良いところだけ見せて終わるのは誠実じゃないので。2点あって、どちらも直せます。 オーバーフロー: 長めの本文がautofitされず、スライドの下端から枠外へ流れ出る タイトル衝突: 長いタイトルが右上のロゴに重なる タイトルの方はこうなります。 ただ、これを「テンプレ流し込みの限界」と書くと言い過ぎです。どちらもデザインの領分の話で、直せるからです。対処は2通りあって、テンプレ側で直すか、流し込む側で直すかのどちらかです。 テンプレ側の対処: タイトルのプレースホルダをロゴの手前で止めておく 流し込み側の対処: タイトルを流す前に文字数を数えて、収まる長さに切る 片方でも効きます。両方やった状態がこれです。 本文のオーバーフローも同じで、流し込む前に分量を制御するか、autofit設定を入れるかで対処できます。 もうひとつだけ、直せない話も書いておきます。図です。今回使ったテンプレには画像用のプレースホルダが無いので、図を流し込むとなると座標指定になってしまって現実的じゃありません。図はPowerPoint側で貼ってもらう前提だと思ってください。 あと、フォントの実体(グリフ)はファイルに埋め込んでいないので、メイリオが入っていない端末で開くと別のフォントに置き換わります。社内で配る前提なら問題にならないと思いますが、そこは頭に入れておいてください。 でも、いま持っているデッキはそのまま移せない ここまで読むと、自分がいま持っているデッキもそのまま移せるのか、気になりますよね。実際に使ったセミナー資料3本、あわせて73枚で確かめてみました。 デッキ 枚数 移せた 手で作り直し ① 26 4(15%) 22(85%) ② 24 3(13%) 21(88%) ③ 23 1(4%) 22(96%) 合計 73 8(11%) 65(89%) 移せたのは、章扉のようにテキストだけで完結するスライドだけでした。残りは図が主役のスライドで、テキストしか持っていけません。3本とも、手で作り直す分が85%から96%を占めていました。 移せなかった側の具体を1枚だけ出しておきます。さっき出した「Marp と Slidev の使い分け」でも見せた、RAGの概念図です。 これはVueコンポーネントで作ってあって、中身は591行あります。3本のセミナーで使い回していた図です。文字だけなら取り出せます。ただ、この図で意味を持っているのはブロックの位置関係と矢印の向きなので、プレースホルダに文字を入れても図にはなりません。使い回していた分、3本すべてでこの1枚が手で作り直す側に落ちました。 だから、いま持っているデッキをテンプレへ移すのは諦めました。代わりに、これから作る分をレイアウトに合う簡単な構造で書くことにしています。デザインはPowerPointに任せたまま、こちらの手間はテキストを移すところだけになります。 自分はどっち側か というわけで、チェックは2つです。 ひとつめは、スライドを作る前に、展開をテキストで考える人かどうかです。アウトラインを書いて、スライド割(絵コンテ)を切って、それからスライドにする。この順で作る人なら噛み合います。Slidev側がそのままアウトラインになるので、テキストを書き終わった時点でスライド割まで決まっているからです。逆に、PowerPointを開いて図を置きながら考えるタイプなら、この道は回り道になります。 あと、図を貼るのとレイアウトの微調整はPowerPoint側でやる前提です。その作業が苦にならないことも条件に入ります。 ふたつめは、コピー元にできる、作り込んだ過去のデッキを持っているかどうかです。持っているなら、正直に言ってそこからコピーして組む方が速いです。この道は要りません。いま持っているデッキを移したい人にも、ここまで書いた通り無理があります。テンプレはあるけどコピー元になる資産が無い場合、たとえば新しく型を作るときや、AIで中身を作り始めたときなら、この先が効きます。 両方でこちら側だったなら、ブランドと編集可能性を両方取れます。リブランドもファイルを差し替えるだけで、コードは0です。やることはさっき書いた通りで、テキストを移すところだけで済みます。 手描きの図が主役のデッキを持っていて、それを活かしたまま会社のブランドにも乗せたいという人もいると思います。実は、会社テンプレを解析していて面倒さに直面したとき、「別の方法があるか、AIをフル活用すれば行けるんじゃね?」って思っていたんですよね。 その発想を実際に検証したのが、会社テンプレはそのまま使って、中身だけAgentにOOXMLで書かせて合体させるやり方で、これがこの連載の到達点です。その図を活かしたい人は、 Slidevの中身をAgentに書かせて会社テンプレへ合体させる話 を見てもらえたらと思います。 最後に一度だけ繰り返しておくと、 「テキストとスライド割はSlidev、デザインはPowerPoint」はこの道での切り分け です。今回はそのうちの一本を、動くところまで確かめて切り出したものなので、ほかの道と見比べてから決めたい人は SlidevからPowerPointへ渡す道を整理した記事 へどうぞ。 まぁ、コピペの手間が減れば上々かなって思ってます。 付録:手元で再現するための情報 ここから下は、同じことを自分の環境でやるための材料です。僕が実際に動かした手順と、途中で踏んだ罠をそのまま置いておきます。 環境 Python 3.12 と python-pptx 1.0.2 だけです。仮想環境を作らずに試すなら uv が楽です。 uv run --with python-pptx python fill.py 最小のコード テンプレを開いて、レイアウトを選んで、テキストを入れて、保存する。これで動く最小の形です。 from pptx import Presentation prs = Presentation("company_template.pptx") # 会社の空テンプレを開く slide = prs.slides.add_slide(prs.slide_layouts[1]) # レイアウトを選ぶ slide.placeholders[1].text = "Slidev側で書いた本文" # 枠にテキストを入れる prs.save("out.pptx") フォントも色も座標も、こちらからは1行も指定していません。これで書式が全部レイアウトから乗ります。 レイアウトと枠の番号を調べる コード側でレイアウトを指定するのは名前ではなく番号です。ドロップダウンで「コンテンツ」と見えていても、コードでは何番なのか分かりません。対応は一覧を出せば分かります。 for i, layout in enumerate(prs.slide_layouts): print(i, layout.name) for ph in slide.placeholders: # 枠の番号と役割 print(ph.placeholder_format.idx, ph.name) 枠の番号はレイアウトごとに違うので、レイアウトを変えたら番号も確認し直してください。 途中の確認はLibreOfficeで回す 検証はコンテナの中で回しているので、1枚作るたびにPowerPointへ持っていくのは面倒です。僕はLibreOfficeでPDFにしてから画像にして、コンテナの中で目で見ていました。この記事の画像も途中まではこの経路です。 soffice --headless --norestore --convert-to pdf --outdir . out.pptx PDFから画像にするのは pdftoppm (poppler)でもいいですが、入っていない環境なら pypdfium2 が手軽です。ただし LibreOfficeの描画は実機のPowerPointと完全に同じではありません (特にフォントの置き換わり)。手軽に回すのはこっちで、最後の確認は実機で開く、という使い分けをしています。この記事のフォントの話も、最終的には実機で確かめたものです。 踏んだ罠 フッターやページ番号がプレースホルダで置かれているテンプレだと、流し込んだスライドに何も出ません。 add_slide() はフッター・ページ番号系のプレースホルダを複製しないためです。テンプレ側がそれらを図形として持っていれば普通に出ます。自分のテンプレでフッターが消えたら、まずここを疑ってください 枠に長い文章を入れても自動では縮みません。 入れる前に文字数を見て切るのが確実です(本文で書いたオーバーフローがこれ) 画像用のプレースホルダが無いテンプレでは、図は座標指定になります。 実質的に手作業なので、図はPowerPoint側で貼る前提にした方が早いです ではまた! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Slidevの中身を、会社のPowerPointテンプレのレイアウトへ流し込む first appeared on SIOS Tech Lab .
PDFが10本、どこに何が書いてあるか言えますか? 調査資料でもレポートでも提案書の根拠でもいいんですけど、PDFが10本あって、そこから共通点と相違点を洗い出して議論しないといけない場面を思い浮かべてください。 僕がまさにそれをやっていて、正直きつかったんですよね。全部人力で一から読むのってかなりの時間がかかりますし、そもそも必要な情報を探すのに、その必要なキーワードがそもそも分からないみたいなことがずっと起きるんですよ。「このデータ、どのPDFのどこに書いてあったっけ」を毎回探しにいく感じです。 結論から言うと、PDFのまま抱え込むのはやめて、Markdownに起こして持つようにしました。結論をざっくりと、これによってAIの力を受けやすくなったよ!って話です。ただし「精度よく変換できます」という話ではなくて、 落ちるものは落ちると認めた上で、どう補うか という現実的な話もあります。今回はその起こし方と、落ちたぶんの埋め方を書いていきます。 PDFのままAIに渡すんじゃダメなんですか? PDFのままでも読めるんですよ。読み込めますし、中身も見られます。 でも、じゃあこのデータとこのデータがこういうのに使えそうです、ってなった時に、その都度文字起こしとかしてられないわけですよね。10本を横断して照らし合わせるとなると、この手間が本数ぶんそのまま乗ってきます。 もう1つ、何度も読ませるほど重くなる話があります。PDFをAIに渡すと、たとえばClaudeだと 公式のPDFサポート の仕組み上、各ページがテキストと画像の両方に変換されて渡されるんです。ページの文字を抜き出したテキストと、そのページをまるごと画像化したものが、セットでコンテキストに乗ります。Markdownはテキストだけなので、この画像ぶんがまるっと上乗せになります。同じ資料を何度も読ませるなら、ここでトークンをバカ食いするわけですね。 画像側の数字はClaudeが公式に出している上限値なので、実際はもう少し下振れします。Markdown側は文字数からの概算で、どちらも実測ではありません。 ただ、これは「トークンが安くなるからMarkdownにしろ」という話ではありません。起こすときに全ページ分の画像ぶんは一度払っていますし、1回か2回引いて終わる資料なら、PDFのまま渡した方が安く済みます。効いてくるのは 同じ資料を何度も引き直す前提のときだけ で、僕がMarkdownに起こしているのもそこです。そして何度も引くなら、引くたびに原典まで辿り着ける形になっていてほしいわけですよね。その形の話が、このあとの中心になります。 ここでちゃんと言っておきたいのは、Markdownに起こすと 捨てるものがある ってことです。段組みやページ内の位置関係といったレイアウトは消えますし、図やグラフの見た目そのものも残りません。何ページ目かという単位も失われますし、原文の文字列自体もAIの変換を一度経由したものになります。ここを「精度でカバーします」とは言いません。捨てると決めた上で、埋められるところだけ埋める、というのが次の話です。 じゃあ、PDFをどうやってMarkdownに起こすのか やっていることは大きく2つで、どう割って起こすかと、落ちたぶんをどう埋めるかです。 起こし方:章単位のPDFに割って、並列で起こす でかいPDFをそのまま扱おうとすると、そもそも全部読み込めなくて該当のエラーが出ることがあります。実際、201ページある社内向けのガイド資料でこのエラーに当たりました。 Claudeの公式ドキュメント を見るとページ数に上限があって、1リクエストあたり600ページ、コンテキストが1M未満のモデルだと100ページまでなんですよね。 理由はもう1つあって、実際必要なのは1章単位だったりするんですよね。全部のデータを一括で編集する必要はなくて、必要なところだけ抜き出せればよかったんです。 やっていることを順番に書くと、こうなります。 目次からチャプターごとのページ範囲を出す(ここは人が決めます) 自作したCLIで、その範囲を章単位のPDFとして切り出して保存する 章ごとのPDFを並列のAgentsに渡して文字起こしする 画像も含めてMarkdownで精査しながら変換する つまり、でかいPDFをAgentsの入力にできるぐらいの小さなPDFに変換する切り出しの作業を挟んでいるだけなんですよね。目次からページ範囲を対応づけるのは人の仕事で、切り出し自体はツールに任せています。 変換に何を使うかは正直お好みでいいと思っていて、この記事では変換ツールの比較には踏み込みません。ここで話したいのは、起こしたあとの形の話です。とはいえ切り出しの道具が何もないと始まらないので、僕が使っているCLIが何を満たしているかだけ、記事の最後に付録として置いておきます。 埋め方:該当ページと原文引用で、原典まで辿れるようにする ここは誤解されやすいので先に言っておくと、落ちたものをMarkdown側で復元しようとはしていません。やっているのは、 原典のその箇所まで人が辿り着けるようにすること だけです。 落ちるもの 原典まで辿るための手がかり レイアウト(段組み・位置関係) 置きません。見たければ原典を開きます 図表の見た目 図表一覧の索引(図表番号・タイトル・ページ・主な内容) 何ページ目かという単位 該当ページの記載 原文の文字列そのもの 原文のままの引用。原文性の保証ではなく、原典を検索して同定するためのキーです レイアウトだけは索引すら置いていません。ここまで埋めにいくと「結局全部取り戻せます」という話になってしまって、捨てる決断をしたこと自体が崩れるので。 ページ番号は章単位に割ったぶんズレるんじゃないか、と思われるかもしれません。切り出した章PDFを中間成果物として残してあるので、章PDFの何ページ目かと、切り出しの開始ページを足せば原典のページに落ちます。章PDFで区切るか原典の通しに揃えるかは紐づけ方の好みで、辿れさえすればどちらでも構いません。 なぜ原文のままの引用とページ番号にこだわっているかというと、情報を使うにあたってAIをかませて変換しているので、その情報がちゃんと変換されているか、確認作業が絶対必要なんですよね。実は原文の意味をちゃんと読んだら、逆のことを言ってるみたいなことがあったりするので、人間だろうがAIだろうが、その数字をまるっと信用するわけにはいかないんです。だから、採用する数字に関しては、元のPDFをきちんと読んで確認をするっていう作業が絶対的に必要になります。 実際にどんな形で持っているかというと、こんな感じです。調査資料を起こしたものから抜粋します。 ## 1. 調査概要(母集団) ── ✅ Web確認値と完全一致 **該当ページ**: p.1(SUMMARY 脚注)、p.8(調査先企業の属性) 原文(p.1 脚注): > ※ 調査期間は2026年3月17日~3月31日。調査対象は全国2万3,349社で、 > 有効回答企業数は1万312社(回答率44.2%) | 項目 | PDF記載値 | Web確認値 | 判定 | |---|---|---|---| | 調査対象 | 23,349社 | 23,349社 | ✅一致 | | 有効回答 | 10,312社 | 10,312社 | ✅一致 | 該当ページと原文の引用がセットで載っているので、この数字を使うときは引用文を検索キーにして原典のp.1を開けば確認できます。ファイルの冒頭には「『PDFでは確認できず』と明記した項目以外は、すべて原典に実在する記述」という書き分けの宣言も置いています。 ちなみに一度、トークンが余っていたので、起こしたMD24本をPDF原典と突き合わせる実験をしたことがあります。採用していた数字はPDFと合っていて、軽微な齟齬が2件(本文と図表のページ併記のずれ、係り先が少し曖昧な箇所)出ましたが、数字そのものの誤りはありませんでした。24本のうち2本は同じ突合をもう一度回してみて、結果は変わりませんでした。設計してやったわけではなく、手が空いたときの実験です。 そして、再現率については、そもそもこっちでどうしようもないことなんですよね。AIのモデルで変換をかましているだけなので。だいたい9割、99%くらいは再現できている感覚があって(これは測った数字ではなく体感です)、残りの1%の厳密性を背負っているわけではないんです。 再現率を追い求めるのはAIのモデル企業に頑張っていただいて、こちとら何もしません。 Markdownに起こして、何が変わったか 前は、PDFも資料もいっぱい読んで、「このデータとこのデータを」を頭の中で突き合わせていました。今は、この情報ソースからこれ、この情報ソースからこれ、じゃあ自分は確認だけします、というところまで来ています。 大きいのは、同じソースをセミナー資料にもブログにも使い回せるようになったことです。たとえばセミナーで「日本企業の生成AI活用率」を出したいときは、こういう順番になります。 起こしたMD群を横断して探す。該当の調査資料のファイルに、活用率の記述が該当ページと原文引用つきで載っている 採用する数字なので、原文の引用文を検索キーにして原典のPDFを開き、その場で一致を確認する セミナー資料側には、数字と一緒に「この数字がどのMDのどこから来たか」を残す 3が地味に効いていて、セミナー資料から辿ると、加工した数字、起こしたMD、原典のPDF、と鎖がつながったままになります。使った先にも原典への道が残るんですよね。 ただし、採用する数字は結局PDFを開いて確認する作業が残ります。そこは省けません。それと、一次ソースが章ごとに配布されているとも限らなくて、総務省白書みたいなのはむしろ少数派です。 原典に戻れること自体は、Anthropic の Citations API や NotebookLM みたいな製品側の機能にもあります。とくにNotebookLMは単体だと本当に優秀です。ただ、あそこに溜めた資産は外へ持ち出すのが地味に大変で、上でやったみたいに自分の書式で常設して別の成果物へ引き回す形にはならないんですよね。製品側の引用が出力を信頼させるために付いているのに対して、こっちの書式は人間が効率よく疑うために付いている。目的が逆なんだと思います。 検索の探し方(ベクトル検索やGraphRAGみたいな話)は別問題なので扱いません。見取り図は ベクトル検索以外のRAG手法7選 に、なぜMarkdownなのかという一般論は HTMLでブログ記事を保存してる奴、全員Markdownにしろ に書いています。 もし手元にPDFのデータソースが何本もあるなら、まずは1本、目次からページ範囲を拾って章単位に割ってみるところから始めてみてください。 付録:同じことをやるなら、各工程で何を満たせばいいか 使う道具は何でもいいと思っているので、コードは載せません。工程ごとに満たすべきことだけ書いておきます。 先に道具の話を済ませておきます。 切り出しは、Anthropicが公開している pdf skill でだいたい足ります 。ページ範囲の抽出( qpdf input.pdf --pages . 1-5 -- pages1-5.pdf )も、総ページ数の取得も、埋め込み画像の抽出( pdfimages )も中に書いてあるので、僕みたいに自分でCLIを書く必要はありません。 しかも間が悪い話で、僕がこのCLIを書いたのは2026年3月です。公式のskillは2025年10月から公開されていて、 qpdf でページ範囲を切り出す例も最初から載っていました。 5ヶ月遅れで同じものを作っていた わけですね。ちゃんと作り始める前に公式のskillを確認しましょう、というのが今回いちばん痛い学びです。 とはいえ、道具が揃っても埋まらないものが1つあります。 切り出しの開始ページを控えること です。これは機能ではなく運用の話なので、そこだけは自分で決めることになります。 1. 章の境界を決める 目次に書いてあるページ番号と、PDFビューアが表示する物理ページ番号がズレていないかを先に確かめます。表紙や前付けがあると一致しません。ここがズレたまま切ると、章の境界が丸ごとずれます。 出すものは、章ごとの開始ページと終了ページの一覧です。 2. 章単位のPDFに切り出す ページ範囲を指定して抽出できれば、手段は問いません。僕は自分でCLIを書きました(pypdfでページを抜いて、Typerでコマンドの口を付けただけのものです)。自分で用意する場合、要るのは次の3つです。 元PDFの総ページ数が分かること 。章の境界を決めるのに要ります ページ範囲を指定して1本のPDFに切り出せること 。「1-3,5,7-10」のように飛び番と範囲を混ぜて書けると、章が連続していない資料で楽になります 切り出しの開始ページを控えられること 。ファイル名に入れるでも記録に残すでもいいので、あとで原典のページに戻せる形にしておきます あるとうれしいのが、 埋め込み画像を抜き出せること です。図表が多い資料だと、起こしたMDと並べて「この図はこれ」を確認するのに使えます。 そして1つだけ大事なのは、 切り出したPDFを捨てずに残しておく ことです。あとで「章PDFの何ページ目か」を原典のページに落とすときに要ります。 3. まず1章だけ起こして、書式が決まったら残りを並列で流す いきなり全章を投げないで、 まず1章だけ起こしてみる のがおすすめです。1本目は書式の実験台で、出てきたMDを見ながら「該当ページはどこに置くか」「引用はどの粒度で残すか」を決めます。 指定する書式は、該当ページ、原文のままの引用、図表一覧の索引、そして「原典では確認できなかった」の書き分け。書式を後から揃えようとすると全章やり直しになるので、ここは1章目で決め切ってしまうのがいいです。 決まったら、あとは1エージェントに1章ずつ渡して並列で流すだけです。章が小さくなっているので、入力に載らない問題はここで消えています。まとめて走らせて出てきたものを眺めるだけになるので、章数が多い資料ほど気持ちよく効いてきます。 4. 原典に戻れることを確かめる 採用する数字について、原文の引用文を検索キーに原典を開いて、一致を見ます。 全部を検証しようとしないのがコツです。使う数字だけでいいので。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post AIに読ませるPDFはMarkdownに変換して、使い捨てにしない引用素材にする first appeared on SIOS Tech Lab .
ども!今月は狂ったようにブログを量産している龍ちゃんです。並行して執筆環境のメンテナンスを進めています。 テストを書け、CHANGELOGを更新しろ、命名規則はこれ、ドキュメントも一緒に直せ。SKILLやCLAUDE.mdにこういう手順を書いた覚え、ありますよね。僕もめちゃくちゃ書いてます。 で、それって守られてます? 僕はずっと守られていると思っていました。半年分のgit履歴から測ってみたら、手順に書いてあるのに、AIは3割無視していたんですよね。しかも4ヶ月間ずっとそうで、その間まったく気づいていませんでした。 今回の内容です。 SKILLに書いた手順が守れていなかった話を、数字で見る Claude Code の hook ではなく pre-commit に置いた理由 手順を仕組みにしたほうがいい場面と、そうでもない場面 強制のやり方を教える記事ではないです。どちらかというと、自分の手順を一回見返すきっかけになればいいなと思って書いてます。 SKILLの指示が守られておらず、手順が飛ばされていた うちのリポでは、AIに調べさせた結果をこんな流れで溜めています。 自作のスラッシュコマンド /research で調査させる 結果が docs/research/ にMarkdownで保存される docs/research/README.md の索引に1行追記する 次に調べ物をするとき、検索担当のサブエージェント( research-searcher )が過去の調査を引っ張ってくる このうち1と2は Claude Codeの調査品質がバラバラ?:/researchで解決する方法 で、3の索引の作り方は Claude Code設計術:AIフレンドリーなドキュメント管理 で書いています。索引を置いた狙いはトークン節約でして、全ファイルをGrepさせるよりも、AI用の簡易DBを1枚置いておくほうが安いよねという話ですね。 4の検索をサブエージェントに任せた話も Claude Codeのドキュメント検索を極力さぼれるようにした話 で書いています。索引が引けなくなった側はそっちで直したので、今日は索引に載っていなかった側の話ですね。 で、その索引の記事で僕はこう書いてるんですよ。「メンテナンスをAIに丸投げする」と。実際、手順は2026年2月4日に書き込んでいます。調査させるときの手順書に「調査結果を保存したら README も更新する」と置いただけの、1行の指示です。 形だけ取り出すと、片方を足したらもう片方も直す、というだけの話です。テストとテスト一覧でも、モジュールとREADMEでも同じですね。 最初の30本くらいは自分の目で確認していました。ちゃんと載っている。問題ない。じゃあこのまま回そう、と。 このとき僕は、AIに制約をかけたつもりだったんですよね。 で、この3番目が4ヶ月ずっと壊れたままでした。 気づいたのは完全に偶然でして、 git の差分を眺めていて「あれ、これ README に書き込めてないな」と思ったんです。1件見つけると気になるもので、ちょっと数えてみるかと。 そしたら思った以上に載っていない。マジかwと声が出て、普通に笑ってしまいました。ついでに「調査依頼、こんなに投げてたんか……」というのも同時に分かって、そっちにも驚きましたね。 半年分のgit履歴で、索引の登録率を測ってみた 笑ったあとにちゃんと測りました。ここが今回の本題です。 測ったのは、その月末に存在する調査ファイルのうち、何割が索引に載っていたかです。ある時点のスナップショットですね。 時点 索引あり / 実ファイル 登録率 2026-01末 25 / 26 96.2% 2026-02末 124 / 131 94.7% 2026-03末 173 / 226 76.5% 2026-04末 185 / 251 73.7% 2026-05末 210 / 280 75.0% 2026-06末 241 / 324 74.4% 2026-07末 261 / 357 73.1% 手順を書いたのが2月4日で、その翌月に落ちています。そこから4ヶ月、73〜75%のあたりでずっと寝ていました。 このグラフの形が結構こわくて、急に壊れるんじゃなくて、落ちたところで安定しちゃうんですよね。7割は守れているので、パッと見では壊れているように見えない。 でも裏返すと、3割は無視されてたわけです。手順に書いてあるのに、です。 割合だとまだ他人事に見えるので、件数でも出しておきます。7月末の時点で、索引に載っていない調査が96本ありました。この数字が僕には一番刺さりました。 pre-commitで、索引を直すまでcommitできないようにした この数字を見て思ったのは、指示が届いていないわけじゃないな、ということでした。届いた上で無視されている。だとしたら、同じ層にもう1回書き足しても、たぶん同じことになります。 なので、置く層を変えました。 判定はこの3つだけで足りた .pre-commit-config.yaml に自作の検査を1つ足しました。ステージされた変更を見て、調査ドキュメントが追加されているのに README が更新されていなかったらコミットを落とします。 判定は結構ざっくりでして、こんな感じです。 docs/research/ 直下の日付つき .md だけを見る(サブディレクトリの中間生成物は索引に載せる性質じゃないので無視) 追加とリネームのときだけ発火する(既存ファイルの修正では索引エントリが変わらないので黙って通す) ワークツリーではなくステージされた README で判定する( git add を忘れたまま通すと意味がない) 並べてみると分かるんですが、ここに書いてあるのは全部「ファイルがあるか」「差分の種類は何か」だけなんですよね。中身を読んで良し悪しを判断するところが1つもない。判定を形式で書けるところに寄せておけば、検査そのものがバグって誤爆する余地も小さくできます。 ここは作り方次第かなと。 もう1つよかったのが、落ちたときのエラーに理由を書いておけることでした。索引を直さないままコミットすると、こうなります。 $ git commit -m "docs: MCPのツール汚染の調査を追加" 調査ドキュメントの索引登録を確認.........................................Failed - hook id: check-research-index - exit code: 1 ::error::調査ドキュメントの索引(docs/research/README.md)登録に問題がある。コミットを中断した: - 2026-08-12-mcp-tool-poisoning.md | 索引に無い 索引に載らない調査は、research-searcher からは見つかっても README を入口にする人からは見えなくなる。 対処: 1. docs/research/README.md の該当セクションに1行足す | [<slug>](./<ファイル名>) | <何を調べて何が分かったか> | <YYYY-MM-DD> | 2. README も一緒にステージする git add docs/research/README.md どうしても今すぐ通したい場合: git commit --no-verify AIはこれを読んで索引を直して、もう一度コミットしにきます。止めるだけじゃなくて、直し方まで渡せるわけですね。 しかもここに「なぜ索引が要るのか」まで書いておけるので、僕が横で説明しなくても同じ判断ができます。 守るかどうかが、AIの裁量から外れた で、いちばん変わったのはここなんですが、守るかどうかがAIの裁量から外れました。前は「READMEも直してね」とお願いして、直っているかどうかは向こう次第でした。いまは直っていなければコミットが落ちるので、僕が見ていなくても、どのセッションのどのモデルが動いていても、結果が同じになります。 うちは複数のAIを並列で回しているので、ここが揃うのはかなりでかいです。 pre-commit そのものの入れ方は 公式ドキュメント を見るのが早いです。うちのリポでの導入は SCANOSS pre-commit:コードスキャンでコピーレフト混入を検出する に書いていて、公式提供のhookと自作hookの混ぜ方や、CIとの役割分担もそっちにまとめてあります。 Claude Code の hook で止めればよかったのでは? ここまで読んで、hook を使えばいいじゃんと思った方、正しいです。 Claude Code の hook はかなり強力でして、 PreToolUse を使えば AI がツールを呼ぶ直前に割り込んで止められます。同僚の佐々木さんが書いた 並列 Claude Code の git checkout で作業中のコードが書き換わるのでgit worktree + hook で解決した がまさにそれです。 危ない操作そのものを止めたいなら、hook が正解ですね。 なので「pre-commit や CI はコミット後にしか止められないけど、hook なら書いた瞬間に止められる」という整理も、それ自体は間違っていないと思います。 ただ、今回の検査には僕は選びませんでした。実務的な理由が2つあります。 1つは、発火しすぎることです。hook はツール単位で動くので、 Write に引っ掛けると調査ドキュメント以外のファイルでも鳴りっぱなしになります。判定を細かく書けば絞れますが、そこまでやるのかというと微妙でして。 もう1つのほうが厄介で、「新規作成のときだけ」という絞り方が効かないんですよ。うちの調査はサブエージェントが並列で動いていて、一時ファイルを作ってあとで統合するような書き方をします。そうすると新規作成のタイミングが実際の完成とズレるので、そこを掴んでも意味がないんですよね。 そして、これが本当の理由なんですが、hook から Claude に文章を返したところで、返せるのは結局「READMEも直してね」というお願いなんですよ。さっき pre-commit のエラーに理由を書ける話をしましたが、エラーを直さないとcommitが通過しないのでお願いより誓約になっているんですね。 止まっていないところに文章を足しても、書く場所が SKILL から hook に変わるだけ。層は同じままです。 ちなみに、指示の層で解くのをやめるという方向は、研究のほうでも同じことが言われているみたいです。査読前のプレプリントなので扱いには注意が必要なんですが、AIエージェントがリポジトリのルールをどこまで守るかを調べた arXiv:2607.26819 にこう書いてありました。(これは関連情報探してたらAIが見つけてきました…) For bans on AI contributions or rules that require a human to approve a step, no amount of policy placement works. Every agent we ran failed at self-enforcing these rules. A project that means them must place the control outside the agent: a CI check that blocks the merge, a required human review, or a bot that closes AI-authored pull requests. AIへの禁止事項や人間の承認が要るルールについては、指示をどこに置いても効かない。どのエージェントも自分では守れなかったので、制御はエージェントの外に置くしかない、という話ですね。指示の書き方を工夫する方向では解けない種類がある、という点でうちの実感と合っていました。 とはいえ、commitするまでは気づけない これで全部解決したみたいに書いてますが、pre-commit も万能ではないです。commit するまで気づけません。書いている最中はずっと間違ったままなので、その意味では hook のほうが早いという指摘は当たっています。 あと --no-verify を付ければ普通に素通りできますしね。 なので、全部を機械で縛るというより、仕組みにしたほうがいい場面がある、くらいの温度でちょうどいいのかなと思ってます。今回でいうと「2つのファイルが連動しているか」という検査で、これは片方を書いた瞬間には判定できないタイプでした。 ただ今回は、pre-commitが最適な用途にはまりました。 自分の手順、最後に確かめたのはいつですか 冒頭で、AIに制約をかけたつもりだったと書きました。 今回分かったのは、あれが制約どまりだったということですね。縛ったつもりでいたけど、破ってもなにも起きない。破られたことにこちらが気づけもしない。 誓約って、破れないから誓約なんですよ。commitが落ちて先に進めなくなって初めて、あ、これがガードレールかとなりました。AIって同じ指示でも通ったり通らなかったりするので、そのブレを吸収する制約としては、機械で止める形はかなり優秀というか必須だなと思っています。 とはいえ、ここまで書いておいてなんですが、明日からやってほしいのは強制を入れることじゃないです。 いま自分が「守られている」と思っている手順を、ひとつ思い浮かべてみてください。 テストも一緒に足す、ドキュメントも直す、どこかの一覧に登録する。なんでもいいんですが、それが守られているのを最後に見たのはいつですか。僕は半年ぶりに確かめて73%でした。守られている前提のまま回していた期間が、4ヶ月そのまま残っていたわけですね。 確かめ方はリポジトリの作りによって変わるので、ここで出せるのは僕の数字だけです。ただ見るところは共通していて、連動しているはずのものが、いま実際に揃っているかどうか。それだけなんですよね。うちの場合は索引の行と実ファイルを突き合わせるだけでした。しかもgitに履歴が残っているので、いつから崩れたのかまで遡って数えられます。 静かに壊れているものは、測らないと静かなままなので。 誓約と制約ですね… ほなまた〜 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post SKILLに書いた手順、AIは3割無視!半年分のgit履歴で測った話 first appeared on SIOS Tech Lab .
はじめに こんにちは、サイオステクノロジーの小沼 俊治です。 「理屈はいいから、まずは実際に CI/CD パイプラインというものを動かして体験してみたい」。 本記事は、そんな方々に向けて、ソフトウェア開発に不可欠な CI/CD の基礎を手を動かしながら学習できる、実践的な入門ガイドとして用意しました。 単なるビルドやデプロイの自動化にとどまらず、近年その重要性が叫ばれている「サプライチェーンセキュリティ(SBOM の活用)」や「脆弱性可視化」、さらには検証フェーズとして独立したジョブで実行する「動的セキュリティテスト(DAST)」までを包括したパイプライン環境を無料で体験できるハンズオンを提供します。 本ハンズオンでは、以下のオープンソース・プロダクトのみで構成された環境をコンテナを使って一括で立ち上げ、ソースコードのコミットからセキュリティチェック、そして本番デプロイに至るまでの一連の流れを体験していただきます。 Jenkins: パイプライン実行 GitLab: ソースコード管理 Dependency-Track: SBOM / 脆弱性可視化 Google OSV (Open Source Vulnerabilities): 脆弱性データベース Artifactory: アーティファクト管理 Ansible: デプロイプロセス自動化 OWASP ZAP: 動的セキュリティテスト (DAST) なお、本記事は「CI/CD のプロセスそのもの」を体感していただくことを主目的としています。そのため、複雑になりがちな環境構築の手順解説はあえて割愛しており、「まずは動かしてみたい」という方に最適です。もし環境構築の裏側に興味を持っていただいた場合は、ぜひ GitHub リポジトリの設定ファイルを解析してみてください。 構成概要 ハンズオン環境の構成 筆者が動かした際の主な構成要素は以下の通りです。 Windows 11 Professional WSL 2.5.9.0 Ubuntu 24.04.3 LTS Docker Engine 28.4.0 Jenkins GitLab 18.2.4 Dependency-Track JFrog Artifactory OSS Ansible OWASP ZAP Windows (WSL) 以外のOSをご利用の方も、条件が満たしていれば以下の手順からハンズオンを進められます。 Ubuntu (Linux) 環境の方: WSL の構築は不要なため、「 Docker Engine 環境の構築 」章から開始してください。 macOS 環境の方: Docker Desktop for Mac などでコンテナ実行環境が準備済みであれば、「 ハンズオンに必要なコマンドの準備 」章から開始してください。 ハンズオンを構成する環境は以下の通りです。 CI/CD パイプラインを構成するツール群、およびデプロイ先となるサーバーをすべてコンテナとして構築します。 Jenkins:CI/CD の中核として、ジョブやパイプラインの実行を管理します。 GitLab:ビルド対象となるソースコードを管理します。本ハンズオンでは、ここでのマージがパイプラインを起動するトリガーとなります。 Dependency-Track:ビルド時にパッケージと一緒に生成したソフトウェア部品表(SBOM)を取り込み、脆弱性の有無を分析・可視化します。 Artifactory:Maven リポジトリのプロキシとして機能するほか、ビルドして生成された成果物(アーティファクト)をプロダクション環境(本番環境)へデプロイするために保管します。 Ansible:事前に定義された Playbook(インストール手順書)に従い、Artifactory にあるアーティファクトを利用してプロダクション環境へデプロイします。 OWASP ZAP:Jenkins の verify-dast-webapp ジョブから Docker-out-of-Docker (DooD) の仕組みを利用してコンテナを一時的に構築し、動的セキュリティテスト(DAST)を実施します。 Web アプリケーション(Ubuntu):デプロイ対象となるプロダクション環境のアプリケーションサーバーです。 Dependency-Track コンテナの構築には、OWASP Foundation が公開している docker-compose の YAML ファイルを取得して使用します。 https://dependencytrack.org/docker-compose.yml Artifactory コンテナの構築には、JFrog 社が公開している docker-compose の YAML ファイルを取得して使用します。 Community – Download Artifactory OSS デプロイ対象の Web アプリケーションは、プレゼンテーション層のフロントエンド、アプリケーション層の Web API、そしてデータ層のデータベースから成る三層アーキテクチャーで構成されています。 このうち「フロントエンド」と「Web API」を CI/CD パイプラインによるデプロイの対象とし、データベース(MySQL)については、コンテナ構築時に作成した環境をそのまま利用するためデプロイ対象になりません。 CI/CD の実演では、以下のリポジトリから Web アプリケーションのソースコードをダウンロードし、ハンズオン環境内に構築した GitLab のリポジトリへコミットすることで、CI/CD の一連の流れを体験します。 Toshiharu-Konuma-sti/hands-on-rollingdice-webapp 単にツールを動かすだけでなく、シーンごとに以下の登場人物になりきって実際の開発現場を想定したロールプレイング形式で進めます。 開発担当:ソースコードの実装や修正を行い、GitLab へコミットします。レビューアーへレビュー依頼として、マージリクエスト(プルリクエスト)を作成します 。 レビューアー:開発担当から起案されたマージリクエストを元にコードをレビューし、問題なければマージを行います 。 運用担当:CI/CD パイプラインの実行状況を監視します。 ユーザー:プロダクション環境へデプロイされた Web アプリケーションを操作して楽しみます 。 環境構築や各種設定に使用するそれぞれのファイルは、以下の GitHub リポジトリで公開しています。 Toshiharu-Konuma-sti/hands-on-jenkins $ tree ~/handson/hands-on-jenkins/ hands-on-jenkins/ |-- container/ …… 「環境構築」章でコンテナ作成で使う素材 | |-- docker-compose.yml | |-- docker-compose-webapp.yml | : | |-- setup/ …… 「環境構築」章でツール準備の環境準備に必要な素材 | |-- SETUP_HANDS-ON.sh | : | `-- try-my-hand/ …… 「ハンズオン実施」章で CI/CD の実演を進める環境 |-- PREPARE_LOCAL_GIT_REPO_TO_PUSH.sh : CI/CD の概要 ハンズオンで実演する CI/CD の流れを DevOps のライフサイクル(Infinity Loop)に当てはめると、以下の工程が該当します。 Code (GitLab):ソースコードの開発とレビューを実施し、バージョン管理ツールでソースコードを管理します。 Build (Jenkins):バージョン管理ツールから取得したソースコードをビルドしてローンチ候補のパッケージを生成します。 Test (Jenkins, Dependency-Track, OWASP ZAP):ローンチ候補のソースコードやパッケージを元にテストやセキュリティ検査を実施します。 Release (Artifactory):ローンチ対象のパッケージをアーティファクト管理ツールに保存・管理します。 Deploy (Ansible):アーティファクト管理ツールからパッケージを取得し、プロダクション環境へインストールします。 CI/CD のハンズオンでは、Operate(運用)、Monitor(監視)、Plan(計画)の工程は対象外となります。 開発者によるソースコードの開発から、プロダクション環境へデプロイするまでの一連の CI/CD フローを示します。 GitLab:開発を終えたソースコードを開発者がプッシュし、レビューアーがソースコードをマージします。これを契機に、Webhook で Jenkins へ最新版のソースコードが登録されたことを通知します。 Jenkins(ビルドジョブ):通知を受けると GitLab からソースコードを取得し、ビルドを行ってパッケージを生成してからテストを実行します。テストに成功するとパッケージを Artifactory に登録します。 Artifactory:デプロイ対象のパッケージ(アーティファクト)を保管・管理します。 Jenkins(デプロイジョブ):デプロイ実行の司令塔となり、Ansible と連携してプロダクション環境を最新の構成に更新します。 Ansible:Artifactory にある最新のアーティファクトを利用し、人手を介さずにプロダクション環境へ自動的にデプロイします。 DASTは、本来ビルドジョブ内で自動実行するのが理想です。しかし、スキャン完了までに10分以上かかる場合があるため、本ハンズオンではスムーズな進行を優先し、専用ジョブ(verify-dast-webapp)として独立させています。 DevOps の Code 工程において、品質を担保しつつ効率的にソースコードの開発を進めるには、適切なソースコード管理(ブランチ戦略)が欠かせません。 本ハンズオンでは GitHub Flow をベースとした手法を用いて、プロダクション環境用の main ブランチと開発作業用のブランチを明確に分けることで、安全で効率的な開発フローを実現します。 各ブランチの役割は以下の通りです。 main ブランチ:プロダクション環境と常に同じ状態を保つ「安定版」のブランチです。このブランチへのマージがプロダクション環境へのデプロイのトリガーとなります。 feature/* ブランチ:新しい機能の開発を行うためのブランチです。 main から枝分かれして作成し、開発とコードレビューが完了した後に、再び main へマージします。 hotfix/* ブランチ:プロダクション環境で発生した緊急のバグ修正専用のブランチです。通常の開発とは別に、迅速な対応が求められる際に利用します。 開発者は feature/* や hotfix/* といった作業ブランチを作成して開発を進めます。直接 main ブランチに開発した差分をコミットしません。 開発が完了した作業ブランチは、必ずレビューアーの承認を経てから main ブランチにマージされます。 基礎環境の構築 WSL 環境の構築 Windows PC(社用標準 PC)の場合には、 以下手順を参考に WSL と Linux ディストリビューション(Ubuntu)環境を用意します。 初期環境構築: WSL 環境 on Windows Docker Engine 環境の構築 コンテナ環境を使うため、以下手順を参考に Ubuntu へ Docker Engine 環境を用意します。 初期環境構築: Docker Engine on Ubuntu ハンズオンに必要なコマンドの準備 本ハンズオンの実施には、以下のコマンドやランタイムが必要です。 これらは主に、環境構築やリポジトリ準備を行う際に使用します。 JDK 21:Jenkins の設定を操作する CLI ツール( jenkins-cli )の実行環境として必要です。 通信先となる Jenkins サーバーが JDK 21 で稼働しているため、クライアント側もバージョンを統一する必要があります。 利用箇所: CI/CD 連携設定スクリプトの実行 (Jenkins ジョブ登録時) jq コマンド:API のレスポンス(JSON 形式)から、特定の値を抽出・整形するために使用します。 利用箇所: CI/CD 連携設定スクリプトの実行 (GitLab 連携設定時) unzip コマンド:GitHub から取得した Web アプリケーションのソースコード(Zip 形式)を展開するために使用します。 利用箇所: ローカルリポジトリ準備スクリプトの実行 (既存のアプリ開発を模した「ローカルリポジトリ環境」の準備時) インストールされていない場合は、以下手順を参照して Ubuntu 環境へ用意します。 初期環境構築: ユーティリティツール on Ubuntu CI/CD 環境の構築 GitHub からハンズオン用のリポジトリ取得 ハンズオンを進めるための環境構築用の設定ファイルやスクリプトを含んだリポジトリを GitHub からダウンロードして取得します。 本章ではターミナルを使用してハンズオン用の作業ディレクトリを作成して作業を実施します。 $ mkdir -p ~/handson/ $ cd ~/handson/ 「 $ git clone 」コマンドで本ハンズオン用のリポジトリを取得します。 $ git clone https://github.com/Toshiharu-Konuma-sti/hands-on-jenkins.git $ cd hands-on-jenkins/ コンテナ構築スクリプトの実行 本章ではターミナルを用いて以下のディレクトリで作業を実施します。 $ cd ~/handson/hands-on-jenkins/container/ コンテナ構築用に用意してあるスクリプトを実行して、CI/CD 環境の各種コンテナを構築します。 $ ./CREATE_CONTAINER.sh コンテナが構築されてから Jenkins と GitLab の初期パスワードの準備までに少々時間がかかるので、暫く待った後に info オプションを付けてスクリプトを実行すると、Jenkins および GitLab の初期パスワードと、後続の環境構築手順概要を表示することができます。 $ ./CREATE_CONTAINER.sh info /************************************************************ * Information: * - Navigate to Web ui tools with the URL below. * - Jenkins: http://localhost:8080 * - Artifactory: http://localhost:8082 * - GitLab: http://localhost:13000 * - Dependency-Track: http://localhost:8981 * - Navigate to the deployed webapp with the URL below. * - webapp: http://localhost:8181 ***********************************************************/ - Password: - Jenkins Default: 66aa56faf61f47ddaed8c0f5777679a6 - GitLab root user: fNvqnjyFC08DMXNov1X0z+zLrc2rxY7CbjMI1SZMFu4= - Setup Instructions: 1. Go to Jenkins and apply JCasC: /var/jenkins_home/my-config/jcasc/jenkins.yaml : なお、コンテナ構築スクリプトで実行する内容は以下を参照してください。 コンテナ構築スクリプトの解説 Jenkins の基礎設定 本章ではブラウザを用いて作業を実施します。 初期セットアップ Jenkins で推奨されているプラグインのインストールと Admin ユーザの作成を行います。 Jenkins へブラウザでアクセスし、初期パスワードを入力のうえ「Continue」ボタンをクリックしてセットアップを開始します。 http://localhost:8080 Administrator password:テキストボックス上部に書かれている、Jenkins コンテナ内のファイルパスから取得して入力します。(「 コンテナ構築スクリプトの実行 」章を参照、もしくは、ターミナルで $ docker container exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword コマンドを実行して取得できます) 「Install suggested plugins」を選び、推奨するプラグインをインストールします。 インストールの経過をしばらく待ちます。 Admin ユーザーを作ります。 ユーザー名:「admin」を入力します。 パスワード:「password」を入力します。(別の値を入力した場合は「 variables.sh#L14 」の定義も変更します) フルネーム:「admin」を入力します。 メールアドレス:「admin@example.com」を入力します。 URL はデフォルト値のままで変更せずに「Save and Finish」ボタンをクリックします。 「Restart」ボタンをクリックすれば、初期セットアップは完了です。 JCasC の適用 Jenkins の各種設定は、画面右上の歯車アイコンをクリックすると表示する「Jenkins の管理」で GUI で設定を行いますが、本ハンズオンでは Jenkins Configuration as Code(JCasC)を使い、設定ファイル(yaml)から一括で適用します。設定される内容は以下章を参照してください。 JCasC 適用内容の解説 トップ画面に遷移したら画面右上の歯車アイコンをクリックすると表示する「Jenkinsの管理」メニューをクリックします。 Jenkins の管理項目から「Configuration as Code」を選択します。 画面中央の「Setup configuration」ボタンをクリックします。 「Path or URL」へ JCasC の実装で用意した YAML ファイルのパスを入力し、「Apply configuration」ボタンをクリックして JCasC ファイルを適用させます。 Path or URL:「/var/jenkins_home/my-config/jcasc/jenkins.yaml」を入力します。(ファイルの中身は「 jenkins.yaml 」を参照) JCasC で定義された各種設定が反映されました。 Artifactory のリポジトリ作成 本章ではブラウザを用いて作業を実施します。 ローカルリポジトリ作成 Jenkins のジョブでビルドしたアーティファクトを格納するリポジトリを用意します。 ブラウザで Artifactory へアクセスして、admin ユーザーで初期パスワードを入力してログインします。 http://localhost:8082 Username:「admin」を入力します。 Password:初期パスワードの「password」を入力します。 Welcome 画面にて「Create a Repository」ボタンをクリックします。 画面右上の「Create a Repository > Local」のメニューを選択します。 パッケージタイプの一覧から「Gradle」を選択します。 New Local Repository 画面にて「Repository Key」にリポジトリ名を入力して「Create Local Repository」ボタンをクリックします。 Repository Key:「hands-on-rollingdice-webapp-webapi」を入力します。 表示されたポップアップウィンドウで「Add Users」は押さずに、ウィンドウの右上の×ボタンで閉じます。 ここまでの「hands-on-rollingdice-webapp-webapi」リポジトリを作った同じ手順で、「hands-on-rollingdice-webapp-webui」リポジトリも作ります。 リポジトリの作成で入力する値は以下の通りです。 パッケージタイプ:「Gradle」を選択します。 Repository Key:「hands-on-rollingdice-webapp-webui」を入力します。 リモートリポジトリ作成 ビルドする際に Maven Repository をプロキシしてキャッシュとして機能するリポジトリを用意します。 リポジトリ一覧画面で右上にある「Create a Repository」ボタンをクリックすると表示するメニューから「Remote」を選択します。 Select Package Type で「Gradle」を選択して New Remote Repository 画面に遷移します。画面内で「Repository Key」にリポジトリ名を入力して「Create Remote Repository」ボタンをクリックしてリポジトリーを作成ます。 Repository Key:「maven-central-remote」を入力します。 URL:デフォルトで入っている URL のままにします。 バーチャルリポジトリ作成 ここまでに作成したローカルリポジトリとリモートリポジトリを一つにまとめ、単一のアクセスポイントで機能するリポジトリを用意します。 リポジトリ一覧画面で右上にある「Create a Repository」ボタンをクリックすると表示するメニューから「Virtual」を選択します。 Select Package Type で「Gradle」を選択して New Remote Repository 画面に遷移します。画面内で「Repository Key」にリポジトリ名を入力したら「Repositories」セクションまで画面をスクロールします。 Repository Key:「gradle-virtual」を入力します。 Repositories セクションで左側リストに表示されているここまでの手順で作成した Available Repositories(ローカルリポジトリ2つと、リモートリポジトリ1つ)を「>>」ボタンをクリックして、右側リストの Selected Repositories に移します。「Create Virtual Repository」ボタンをクリックしてリポジトリーを作成します。 ハンズオンで必要なローカルリポジトリ、リモートリポジトリ、およびバーチャルリポジトリが全て揃った状態のリポジトリ一覧画面は、以下の構成になります。 CI/CD 環境セットアップスクリプトの実行 Jenkins へのジョブ登録や GitLab のプロジェクト作成などを、スクリプトを使って一気に行います。これにより、複雑な連携設定を手動で行う手間を省きます。 本章ではターミナルを用いて以下のディレクトリで作業を実施します。 $ cd ~/handson/hands-on-jenkins/setup/ セットアップ用に用意してあるスクリプトを実行して、CI/CD 環境の各種設定を行います。コンテナ構築直後は GitLab が完全に起動していない場合があり、スクリプトは GitLab の Web API が応答するまで自動的にリトライを繰り返しますので、完了するまでそのままお待ちください。 $ ./SETUP_HANDS-ON.sh コマンドが足りずにスクリプトが終了した際は、以下を参照してインストールしてください。 初期環境構築: ユーティリティツール on Ubuntu セットアップ用のスクリプトで実行する内容は以下を参照してください。 CI/CD 環境セットアップスクリプトの解説 CI/CD の実演 環境構築が完了しましたので、いよいよここから実際の開発フローによる CI/CD を体験していきます。 「 CI/CD の概要 」章でも説明した CI/CD フローを、開発者、レビューアー、運用者などの役割を演じながら、コードの変更がどのようにパイプラインを通過し、プロダクション環境へデプロイされるかを確認しましょう。 GitLab ローカルリポジトリ準備 GitLab 上のリポジトリを確認し、ローカル環境にソースコードを準備します。まずは開発担当の視点から開始します。 初期状態のリモートリポジトリ確認 CI/CD の実演で使用するリポジトリが作成されているか確認します。 ブラウザで GitLab へアクセスして、root ユーザーでログインします。 http://localhost:13000/root/webapp-webapi ユーザー名:「root」を入力します。 パスワード:gitlab コンテナ内の「/etc/gitlab/initial_root_password」に書かれている初期パスワードを入力します。 (「 コンテナ構築スクリプトの実行 」章を参照、もしくは $ docker container exec gitlab cat /etc/gitlab/initial_root_password コマンドで取得できます) Web API のソースコードを管理する「webapp-webapi」リポジトリが存在していて、「README.md」ファイルのみが存在する初期状態であることを確認します。 同様にフロントエンドのソースコードを管理する「webapp-webui」リポジトリについても確認します。 なお、「webapp-webapi」と「webapp-webui」のリポジトリは、「 CI/CD 連携設定スクリプトの実行 」章で実行したスクリプトで作成しています。 ローカルリポジトリ準備スクリプトの実行 通常であれば、ここで初期状態の Git リポジトリを git clone してゼロから Web アプリケーションのソースコードを実装するところですが、本ハンズオンでは CI/CD の体験に集中するため、「実装済みのソースコード」を一括で適用するスクリプトを用意しています。これにより、面倒な準備なしで、すぐに CI/CD の実演(ハンズオン)を始められる状態を作ります。 本章ではターミナルを用いて以下のディレクトリで作業を実施します。 $ cd ~/handson/hands-on-jenkins/try-my-hand/ GitLab から初期状態のリモートリポジトリを取得して、実装された Web アプリケーションのソースコードをローカルリポジトリへ用意するスクリプトを実行して、リモートリポジトリへプッシュできる状態を準備します。 $ ./PREPARE_LOCAL_GIT_REPO_TO_PUSH.sh 本ハンズオンでは一からのコーディング作業を省略するため、以下のリポジトリから各種ハンズオン向けに用意してある Web アプリケーションのソースコードを取得し、実装が完了した状態を再現して進めます。 Toshiharu-Konuma-sti/hands-on-rollingdice-webapp ローカルリポジトリ準備スクリプトで実行する内容は以下を参照してください。 ローカルリポジトリ準備スクリプトの解説 GitLab リモートリポジトリ更新(webapi) ローカルリポジトリに Web アプリケーションの実装が完了しました。 ここからは、この変更をリモートリポジトリへ反映し、CI/CD パイプラインを始動させるまでの流れを体験します。 ローカルからリモートリポジトリへプッシュ ローカルリポジトリに実装した Web アプリケーションをリモートリポジトリへ反映して CI/CD の実演を本格的に開始します。 「webapp-webapi」リポジトリから開始するため、以下のディレクトリで作業を実施します。 $ cd ~/handson/hands-on-jenkins/try-my-hand/webapp-webapi/ 既に Web アプリケーションが実装された状態を再現しているため、ローカルリポジトリにソースコードや設定ファイルの差分が生じていることが確認できます。 $ git status ブランチ feature/sample 追跡されていないファイル: (use "git add <file>..." to include in what will be committed) .gitattributes .gitignore RUN.sh build.gradle : nothing added to commit but untracked files present (use "git add" to track) 差分のファイルをコミット対象として登録します。 $ git add . リモートリポジトリへプッシュするためにローカルリポジトリへ登録します。 $ git commit -m "implement the source code of the web app" 28 files changed, 2009 insertions(+) create mode 100644 .gitattributes : create mode 100644 src/test/java/jp/sios/apisl/handson/rollingdice/webapp/webapi/util/UtilEnvInfoTest.java ローカルリポジトリで開発した開発用ブランチ ( feature/sample ) をリモートリポジトリへプッシュします。 $ git push --set-upstream origin feature/sample Username for 'http://localhost:13000': root Password for 'http://root@localhost:13000': Enumerating objects: 63, done. : branch 'feature/sample' set up to track 'origin/feature/sample'. Username for ‘http://localhost:13000’:「root」を入力します。 Password for ‘http://localhost:13000’:gitlab コンテナ内の「/etc/gitlab/initial_root_password」に書かれている初期パスワードを入力します。 (「 コンテナ構築スクリプトの実行 」章を参照、もしくは、ターミナルで $ docker container exec gitlab cat /etc/gitlab/initial_root_password コマンドを実行することで取得できます) リモートリポジトリでマージリクエスト作成 ソースコードのプッシュが完了したら、GitLab 城で「マージリクエスト(プルリクエスト)」を作成してレビューを依頼します。 ブラウザで GitLab にアクセスし、トップページ、もしくはリポジトリページに「Create merge request」ボタンが掲示されている場合には、該当のボタンをクリックしてマージリクエストの作成を開始します。 http://localhost:13000/root/webapp-webapi 「Create merge request」ボタンが掲示されていない場合には、左ペインのメニューから「Pinned > Merge requests」を選択し、画面中央部にある「New merge reuqest」ボタンを押下してマージリクエストの作成を開始します。 マージリクエストの対象となるマージしたいブランチを指定して、「Compare branches and continue」ボタンをクリックします。 Source branch:マージしたい開発した最新のソースコードを含む「 feature/sample 」ブランチを指定します。 Target branch:マージ先となるプロダクション環境と同じ状態を保っている「 main 」ブランチを指定します。 マージリクエストの情報を入力するフォームが表示されるので、Title、Description など必要な項目の入力を進めます。 情報の入力フォームの画面下部に存在する「Create merge request」ボタンをクリックしてマージリクエストを作成します。 出来上がったマージリクエストをレビューアーに提示して、開発担当からレビューアーにバトンタッチしてレビューフェーズに進みます。 GitLab でコードレビューとマージ 開発担当がコーディングしたソースコードや設定ファイルをレビューアーがレビューを実施し、品質に問題が無ければ承認、およびマージして、DevOps の Code 工程を完了します。この章はレビューアーの視点になります。 レビューアーとしてブラウザで GitLab にアクセスし、左ペインのメニュー一覧より「Merge requests」を選択してマージリクエストの一覧を表示します。一覧に開発担当が作成したレビュー待ちのマージリクエストがあるので、クリックしてレビューを開始します。 http://localhost:13000/dashboard/merge_requests マージリクエストがアクティブになりましたので、「Change」タブをクリックして差分表示に切り替えます。 マージ前後の差分表示になっているので、こちらを利用してソースコードの変更点をレビューします。 レビューが問題なければ「Overview」タブをクリックして「Approve」ボタンをクリックして承認します。続けて「Merge」ボタンをクリックすると開発用の「 feature/sample 」ブランチが「 main 」ブランチへマージされレビューは完了です。 直後に GitLab から Jenkins へ Webhook で「 main 」ブランチにマージが発生したことの通知が飛び、Jenkins ではビルドジョブが自動的に開始します。次章でその様子を確認しましょう。 Jenkins ビルドジョブ実行(webapi) GitLab で「 main 」ブランチへのマージが完了すると、Jenkins 側で自動的にビルドジョブが開始されます。 その様子と、実行されたセキュリティチェックの結果を確認しましょう。この章は開発担当がメインとなりますが、レビューアーと運用担当も関わりを持ちます。 ビルドジョブ実行確認 ビルドジョブが実行されるので確認します。 ジョブの一覧から自動的に実行開始された「build-webapp-webapi」ジョブをクリックして詳細情報に遷移します。 Stage View で、ビルドジョブを構成するステージが順に実行されていることと、各ステージの処理に掛かった時間も確認することができます。 順にステージの実行を繰り返し、「Declarative: Post Actions」ステージまで到達するとジョブも完了です。Stage View の最左列のジョブ番号(例:[#1])をクリックしてビルドジョブの結果詳細を見てみましょう。 ビルドジョブ結果確認 ビルドジョブの完了後、生成された各種レポートや成果物を確認してプロダクトの品質を評価します。 本ハンズオンのパイプラインには、「品質(Quality)」と「セキュリティ(Security)」の検証に加え、「仕様の可視化(Visibility)」を行うため、以下のプロセスが組み込まれています。 これらの結果から改善点を見つけ出し、コードを修正して再びプッシュする ―― このサイクルを回すことで、品質と安全性を継続的に向上させます。 SCA (Software Composition Analysis):Dependency-Track を使用し、利用している OSS ライブラリに既知の脆弱性がないかを分析します。 単体テスト (Unit Testing):JUnit を使用し、プログラムの機能が正しく動作するかを検証します。 SAST (Static Application Security Testing):SpotBugs と PMD を使用し、ソースコード等の不具合やセキュリティホールの原因となる記述を検出します。 Linter:CheckStyle を使用し、コードの書き方(インデントや命名規則など)が規約に沿っているかをチェックします。 ドキュメント生成 (Documentation):ソースコードから Javadoc や OpenAPI 仕様書を自動生成し、実装と乖離のない最新の仕様を可視化します。 ビルドジョブの結果ページでは、脆弱性の解析や静的コード解析などの各種結果のサマリー表示と、結果の詳細情報へ遷移するリンクで構成されています。 Dependency-Track と連携した脆弱性解析の結果レポートです。一覧の各行で「Name」列の先頭にある「+」ボタンをクリックすると、展開表示で各脆弱問題に対する対処方法も確認できます。 JUnit による単体テストの実行結果のレポートです。アプリケーションを構成するパッケージごとに、単体テストの実行に掛かった所要時間、成功数や失敗数などが確認できます。 単体テストのカバレッジのレポートです。テストコードがプロダクションのソースコードをどれだけ網羅しているか確認することができます。 SpotBugs による静的な検証結果のレポートです。実行時エラーに繋がるバグの発見など、動かなくなるリスクの回避を手助けします。 PMD による静的な検証結果のレポートです。非効率や冗長なコードなどによる、保守しにくいリスクの回避を手助けします。 CheckStyle による静的な検証結果のレポートです。コーディング規約の違反など、読みにくいリスクの回避を手助けします。 ソースコードから生成された Javadoc 形式のプログラム仕様書を確認できます。 Web API を対象とした「build-webapp-webapi」ビルドジョブでは、ソースコードから生成された OpenAPI 形式の API 仕様書も確認できます。 GitLab リモートリポジトリ更新 ~ Jenkins ビルドジョブ実行(webui) ここまでの手順で、Web API のソースコードを管理する「webapp-webapi」リポジトリのビルドまでが完了しました。次は、フロントエンドのソースコードを管理する「webapp-webui」リポジトリを対象に同じ手順を実行してビルドまで実施します。以下の各章内の説明で「webapi」を「webui」に読み替えて実行します。 GitLab リモートリポジトリ更新(webapi) 対象ディレクトリを webapp-webui に読み替えて、プッシュおよびマージリクエストの作成を行います。 GitLab でコードレビューとマージ 対象リポジトリを webapp-webui に読み替えて、コードレビューおよびマージを行います。 Jenkins ビルドジョブ実行(webapi) Jenkins 上で build-webapp-webui ジョブが実行されることを確認します。 Artifactory リポジトリ確認 Jenkins で「build-webapp-webapi」と「build-webapp-webui」の各ビルドジョブが完了すると、各ビルドジョブでリリースされたアーティファクトが Artifactory に登録されていることが確認できます。これがデプロイの原資となります。この章あたりから、運用担当がメインとなってきますが、開発担当とレビューアーも関わりを持ちます。 Artifactory の画面上部で「Platform」タブがアクティブな状態で、左ペインのメニューから「Artifactory > Artifacts」を選択するとリポジトリツリーが表示します。ツリーから事前に作成したローカルリポジトリをクリックしてツリーを展開すると、ローカルリポジトリに登録されたアーティファクトを表示することができます。 一覧で一つアーティファクトを選んでいる状態で、「Properties」タブをアクティブにするとビルド時の情報が確認できます。 「apisl.handson.rollingdice.webapp.webapi-0.0.1-SNAPSHOT.jar」と「apisl.handson.rollingdice.webapp.webui-0.0.1-SNAPSHOT.jar」のアーティファクトが登録されていることが確認できたら、アプリケーションレイヤーにデプロイする Jenkins のデプロイジョブの実行に進みます。 Jenkins デプロイジョブ実行 Jenkins のデプロイジョブから Ansible と連携して、Artifactory に登録されているアーティファクトをアプリケーションレイヤーのアプリケーションサーバーにデプロイし、Web アプリケーションを構築します。この章は運用担当の視点になります。 Jenkins のジョブ一覧から「deploy-webapp」デプロイジョブを選択します。 デプロイジョブは Jenkins 自身でジョブを開始するため、左ペインのメニューから「ビルド実行」をクリックしてジョブを実行します。 全てのタスクが完了すると Web アプリケーションの構築が完了しました。 このジョブの中で、Ansible が Playbook に従って「アーティファクトのダウンロード」「インストール」「サービスの再起動」を自動的に行っています。 Webアプリケーション実行 Jenkins のデプロイジョブで Web アプリケーションが構築されているので、アクセスしてアプリケーションが動いているか確認しましょう。この章はユーザーによる利用がメインとなりますが、サービス稼働の裏方として運用担当、開発担当とレビューアーも関わりを持ちます。 ブラウザで Web アプリケーションにアクセスしてデプロイ結果を確認します。 http://localhost:8181 画面が表示され、サイコロを振るなどの操作ができれば成功です!CI/CD による開発からアプリケーションの稼働開始までの一連の流れは以上となります。 Web アプリケーションの稼働確認で改善点や改修点が見つかった場合には、GitLab のローカルリポジトリでソースコードや設定ファイルの改修を行って、再度「 GitLab リモートリポジトリ更新(webapi) 」章の手順から実行し直すことで、常に「コード」と「環境」が一致した状態を保ちながら、安全かつ高速に機能改善(継続的デリバリー)を行うことが可能になります。 Jenkins DAST(動的アプリケーションセキュリティテスト)ジョブ実行 診断ツール「OWASP ZAP」を利用した DAST ジョブを実行します。通常、DAST はビルドジョブの一環として自動化することが一般的ですが、スキャン完了までに時間を要する(10分以上)傾向があるため、本ハンズオンではパイプラインの即応性と実演の円滑さを考慮し、ビルドジョブとは切り離した独立したジョブとして用意しています。 Jenkins のジョブ一覧から「verify-dast-webapp」検証ジョブを選択します。 検証ジョブは Jenkins 自身でジョブを開始するため、左ペインのメニューから「ビルド実行」をクリックしてジョブを実行します。 全てのタスクが完了すると DAST の検証が完了しました。左ペインのメニューで「DAST Scan Report (webapi)」および「DAST Scan Report (webui)」をクリックすると、DAST の検証結果が確認できます。 検出されたリスクがあれば、レベルや検出に至った経緯が掲載されているので、改善に向けた検討を試みてください。 GitLab CI/CD 実行 本ハンズオンでは Jenkins を利用した CI/CD の体験をメインとしているが、GitLab CI/CD による簡易な CI/CD も体験できます。 「webapp-webapi」リポジトリを選んでいる状態で左ペインのメニューから「Build > Pipelines」を選びます。GitLab CI/CD を実行するために画面右上にある「New pipeline」ボタンをクリックします。 Run new pipeline 画面に遷移してきたら「New pipeline」ボタンをクリックします。 パイプラインの「launchers」のボックス内で、「trigger-build-release」ジョブ名の右隣にある再生ボタンをクリックして、ビルドジョブを起動します。 ビルドジョブが走り出し、その配下にあるステップが順番に実行されている様子が確認できます。 「trigger-build-release」ジョブ配下の全てのステップが完了したら、左ペインのメニュー「Deploy > Pakage registry」より、アーティファクトが保存されているのを確認します。 続いて「Deploy > Pages」にて、「trigger-build-release」ジョブの実行中に生成されたテスト結果や仕様書が閲覧できます。 アーティファクトが出来上がったので、アプリケーションサーバーへデプロイします。左ペインのメニュー「Build > Pipelines」で遷移し、「trigger-build-release」ジョブを起動したパイプラインをアクティブにします。今度は「trigger-deploy」の右隣にある再生ボタンをクリックしてジョブを開始します。 「trigger-deploy」ジョブの全てのステップが完了するとアプリケーションサーバーにデプロイも完了しています。 説明は割愛しますが、「webapp-webui」リポジトリも同様にパイプラインでジョブを実行したら、Web アプリケーションにアクセスしてお試しください。 DevOps の実演(CI/CD + オブザーバビリティー) 前提条件 本章を進めるには、「 Webアプリケーション実行 」章までの手順がすべて完了していることが必須となります。以下の状態になっていることを確認してから開始してください。 コンテナ環境の稼働:Jenkins を含む CI/CD ハンズオン環境のコンテナ群が起動していること。 デプロイの実績:パイプラインを通じて、プロダクション環境へ一度はデプロイが成功し、Web アプリケーションが稼働していること。 DevOps 統合環境の概要 本ハンズオンの CI/CD 環境でアプリケーションレイヤーを軸に、「 Grafana OSS LGTM スタックで体験する『オブザーバビリティー入門』 」のオブザーバビリティー環境を追加構築することで、DevOps のハンズオン環境を用意します。 CI/CD 環境:アプリケーションレイヤーに最新のソースコードで実装された Web アプリケーションを提供します。また、オブザーバビリティーによって見つかった改善点を改修した Web アプリケーションも更新提供できます。 オブザーバビリティー環境:CI/CD 環境によって提供された Web アプリケーションを観測し、安定稼働していることの確認や、時によっては改善箇所の発見を行います。 CI/CD のハンズオン環境では、DevOps ライフサイクルの Code から Deploy までが範囲でしたが、オブザーバビリティー環境を足すことによって、DevOps ライフサイクルの循環を体験することが可能になります。 Code から Deploy:本ハンズオン「 CI/CD の概要 」の説明を参照してください。 Operate(Web アプリケーション):デプロイされたアプリケーションが稼働し、オブザーバビリティーに必要なテレメトリーデータ(ログ、メトリクス、トレース)を継続的に出力します。 Monitor(Grafana LGTM Stack):収集したデータをダッシュボードで可視化・分析し、アプリケーションの健全性やボトルネック、エラーの発生をリアルタイムに検知します。 Plan:監視データから得られた客観的な事実に基づいて改善点や修正方針を策定し、次の開発サイクル(Code)へとフィードバックします。 構築手順 ブラウザで Jenkins にアクセスし Web アプリケーションを Grafana へのメトリクス送信に対応したジョブ(「deploy-webapp-with-grafana」デプロイジョブ)でデプロイし直します。 http://localhost:8080 ターミナルを使い、Web アプリケーションのメトリクス収集用として NodeExporter をサイドカーコンテナで立ち上げます。 $ cd ~/handson/hands-on-jenkins/container/ $ ./CREATE_CONTAINER.sh up-exporter Grafana のハンズオン環境のリポジトリを GitHub から取得します。 $ cd ~/handson/ $ git clone https://github.com/Toshiharu-Konuma-sti/hands-on-grafana.git CI/CD のハンズオン環境に対し、Web アプリケーションを除く Grafana 関連のコンテナ群を追加で立ち上げます。 $ cd ~/handson/hands-on-grafana/container/ $ ./CREATE_CONTAINER.sh up-to-jenkins DevOps フィードバックループの体験 ブラウザで Web アプリケーションにアクセスし、サイコロを振ってアプリケーションを楽しみます。 http://localhost:8181 Web アプリケーションを動かすことでテレメトリーデータが Grafana に送られますので、ブラウザで Grafana にアクセスします。 http://localhost:3000 Grafana で各種テレメトリーデータを確認します。Grafana で確認する際の操作手順については、以下のハンズオン資料を確認してください。 Grafana OSS LGTM スタックで体験する『オブザーバビリティー入門』 Grafana で観測した結果から Web アプリケーションのソースコードを改修してます。以下のソースコード改修例は、観測結果から「タイトルの視認性を上げたい」などの改善点が見つかったと仮定してソースコードを改修しています。 $ cd ~/handson/hands-on-jenkins/try-my-hand/webapp-webui/ $ vim src/main/resources/templates/include/title.html $ git diff src/main/resources/templates/include/title.html diff --git a/src/main/resources/templates/include/title.html b/src/main/resources/templates/include/title.html index 51f33eb..7fa53b1 100644 --- a/src/main/resources/templates/include/title.html +++ b/src/main/resources/templates/include/title.html @@ -1,3 +1,3 @@ <div> - <h1>Let's pray for a good eye!!</h1> + <h1>Let's pray for a good eye!! v2</h1> </div> 改修が完了したらコミット・プッシュを行い、以下手順で再度 CI/CD を回します。これにより、改善されたアプリケーションがプロダクション環境へリリースされ、再び「Operate」→「Monitor」へと続く DevOps の無限ループが回り始めます。 CI/CD の実演 以上で、DevOps の実演を含めた CI/CD のハンズオンはすべて終了です。お疲れ様でした! コードのコミットからデプロイ、そして稼働状況の観測から次の改善へ。この一連の「DevOps ループ」が実際に回る様子を通して、CI/CD とオブザーバビリティーが連携することでどのように継続的な改善が実現されるのか、その「手応え」を感じていただけたなら幸いです。 続く Appendix では、このハンズオン環境を裏で支えている設定ファイルやスクリプトについて解説します。「どうやってこの環境を作ったのか詳しく知りたい!」という方は、ぜひこのままご覧ください。 Appendix ハンズオン環境の構築や設定手順で利用した各種スクリプトや設定ファイルの実装内容について解説します。 docker-compose / Dockerfile の解説 jenkins/Dockerfile 「 container/jenkins/Dockerfile#L3-L4 」に記述した Jenkins CLI コマンドを実行して、「 container/jenkins/my-config/ref/plugins.txt 」で列記した以下のプラグインがインストールされたコンテナを用意します。 Configuration as Code:Jenkins Configuration as Code (JCasC)による設定ができるようにします。 SSH Credentials:SSH ノードや Git 接続に必要な SSH 認証情報(秘密鍵やパスワードなど)を暗号化して安全に管理できるようにします。 GitLab:GitLab でコミットやマージなどのイベント発生時に、WebHook を受信してジョブが実行できるようにします。 Pipeline: Stage View:ジョブのページで Stage View を表示できるようにします。 Coverage:単体テストのカバレッジを確認できるようにします。 Warnings:コーディングルール(CheckStyle、PMDなど)の結果を確認できるようにします。 Javadoc:ソースコードから Javadoc 形式で仕様書を生成できるようにします。 Generic Tool:JCasCで「tool:」セクションが使えるようにします。 OWASP Dependency-Track:ビルド時に生成した SBOM を Dependency-Track へ自動送信し、脆弱性解析結果を連携・確認できるようにします。 JFrog:ジョブから JFrog Platform(Artifactory)にアクセスしやすくします。 コンテナ構築スクリプトの解説 「 コンテナ構築スクリプトの実行 」章で使用する「 container/CREATE_CONTAINER.sh 」スクリプトの処理内容について解説します。 Artifactory 構築用の YAML ファイル取得 Artifactory コンテナを構築するための docker-compose YAML ファイルを取得します。JFrog 社が公開しているファイルを利用して構築するため、以下 OSS 版のダウンロードページより、Linux Installer で「Docker Compose」を選択した際に表示される Download URL から tar.gz ファイルを取得します。 Community – Download Artifactory OSS 取得した tar.gz ファイルを解凍すると artifactory-oss-{version}/templates/ ディレクトリ配下に、いくつか docker-compose YAML ファイルが存在しているが、その中から Artifactory と PostgerSQL コンテナを構築する「 docker-compose-volumes.yaml 」を利用します。 Web アプリ向け MySQL 設定ファイル取得 本ハンズオン、および、Grafana LGTM スタックのハンズオン向けに用意した Web アプリケーションのリポジトリから MySQL 設定用のファイルを取得します。Clone で取得するのではなく、リポジトリを zip 圧縮したファイルで取得します。 Toshiharu-Konuma-sti/hands-on-rollingdice-webapp 取得した zip ファイル解凍すると hands-on-webapp-rolling-dice-main/mysql/ ディレクトリ配下に、MySQL を設定するファイルが存在するので、これらファイルを Web アプリケーション構築用の docker-compose-webapp.yaml ファイルに定義した MySQL コンテナから volume 参照して利用します。 コンテナ構築のコマンド実行 本ハンズオンで利用するコンテナは 3 種類の docker-compose YAML ファイルを利用して構築するため、これらのファイルを指定して docker-compose コマンドを実行します。 $ docker-compose \ -f docker-compose.yml \ -f docker-compose-webapp.yml \ -f docker-compose-volumes.yaml \ up -d -V --remove-orphans ネットワークへ登録 Artifactory コンテナは JFrog 社で公開している docker-compose YAML ファイルで構築しているため、ハンズオン用に独自で運用している Docker ネットワークに後から追加します。 $ docker network connect hands-net artifactory $ docker network connect intra-net artifactory $ docker network connect intra-net postgresql JCasC 適用内容の解説 「 JCasC の適用 」章で使用する Jenkins Configuration as Code(JCasC)ファイル「 container/jenkins/my-config/jcasc/jenkins.yaml 」で適用する内容について解説します。 master ノードのラベル付与 「Jenkins の管理 > System Configuration > Nodes」のノード一覧にある「master」ノードに、デフォルトでは付与されていないラベルを付与します。JCasC ファイルでは「 container/jenkins/my-config/jcasc/jenkins.yaml#L8 」の定義が該当します。 ラベル:「master」を付与します。 ノード追加 「Jenkins の管理 > System Configuration > Nodes」でノードを追加します。JCasC ファイルでは「 container/jenkins/my-config/jcasc/jenkins.yaml#L10-L32 」の定義が該当します。 エージェントノードとしてあらかじめ登録しておくことで、Jenkins の起動時に jenkins-agent や ansible コンテナへ自動的に SSH 接続されます。これにより、デプロイジョブ側で個別に SSH 接続処理を実装することなく、直接 Ansible を操作してデプロイを実行できるようになります。 追加するノード情報は以下の通りです。 ノード名「jenkins-agent-node」を追加します。 ラベル:「jenkins-agent」を入力します。 ホスト:「jenkins-agent」を入力します。 ノード名「ansible-node」を追加します。 ラベル:「ansible」を入力します。 ホスト:「ansible」を入力します。 各ノード共通で以下情報を登録します。 リモートFSルート:「/root」を入力します。 起動方法:「SSH経由でUnixマシンのスレーブエージェントを起動」を入力します。 認証情報:「root/*******」を選択します。 Host Key Verification Strategy:「Non Verifying Verification Strategy」を選択します。 ツール(Plugin)設定 「Jenkins の管理 > System Configuration > Tools」でインストール済みの中から以下のプラグインを設定します。JCasC ファイルでは「 container/jenkins/my-config/jcasc/jenkins.yaml#L34-L50 」の定義が該当します。 Gradle(初期セットアップの Suggested Plugin でインストール) JFrog(コンテナ作成時に Jenkins CLI でインストール) Gradle に設定する内容は以下の通りです。 name:「my-gradle」を入力します。 自動インストール:「On」でチェックを入れます。 アーカイブダウンロードURL:「https://services.gradle.org/distributions/gradle-9.6.0-bin.zip」を入力します。 バージョン:「Gradle 9.0.0」を選択します。 JFrog に設定する内容は以下の通りです。 name:「my-jfrog-cli」を入力します。 自動インストール:「On」でチェックを入れます。 Version:最新版をインストールするため空欄のままにします。 外観設定 「Jenkins の管理 > System Configuration > Appearance」で外観を設定します。JCasC ファイルでは「 container/jenkins/my-config/jcasc/jenkins.yaml#L52-L57 」の定義が該当します。 外観で設定する内容は以下の通りです。 Pipeline Stages Show pipeline stages on job page:「On」にします。 Show stage names by default:「On」にします。 Show stage durations by default:「On」にします。 Pipeline Graph Show pipeline graph on build page:「On」にします。 クレデンシャル追加 「Jenkins の管理 > Security > Credentials」で認証情報を登録します。JCasC ファイルでは「 container/jenkins/my-config/jcasc/jenkins.yaml#L59-L81 」の定義が該当します。 「Stores scoped to Jenkins」の表で Store = System 行の Domains 列で「(global)」をクリックしてグローバルドメイン画面に遷移します。 「+ Add Credentials」ボタンをクリックして「Jenkins-Agent SSH 接続用」、「Ansible SSH 接続用」、「Artifactory Push 用」と「Dependeny-Track SBOM 登録用 API Key」の4つの認証情報を作ります。 「Jenkins-Agent SSH 接続用」は以下の値を入力してから「Create」ボタンをクリックして作成します。 種類:「ユーザー名とパスワード」を選択します。 スコープ:「グローバル」を選択します。 ユーザー名:「root」を入力します。 パスワード:「password」を入力します。 ID:「jenkins-agent-node-credential」を入力します。 「Ansible SSH 接続用」は以下の値を入力してから「Create」ボタンをクリックして作成します。 種類:「ユーザー名とパスワード」を選択します。 スコープ:「グローバル」を選択します。 ユーザー名:「root」を入力します。 パスワード:「password」を入力します。 ID:「ansible-node-credential」を入力します。 「Artifactory Push 用」は以下の値を入力してから「Create」ボタンをクリックして作成します。 種類:「ユーザー名とパスワード」を選択します。 スコープ:「グローバル」を選択します。 ユーザー名:「admin」を入力します。 パスワード:「password」を入力します。 ID:「artifactory-app-credential」を入力します。 「Dependeny-Track SBOM 登録用 API Key」は以下の値を入力してから「Create」ボタンをクリックして作成します。 種類:「Secret Text」を選択します。 スコープ:「グローバル」を選択します。 シークレット:SETUP のスクリプトで埋めるので適当な文字をを入力します。 ID:「dependency-track-api-key」を入力します。 Artifactory 設定 「Jenkins の管理 > System Configuration > System」で JFrog のプラグイン情報を設定します。JCasC ファイルでは「 container/jenkins/my-config/jcasc/jenkins.yaml#L90-L96 」の定義が該当します 「JFrog Plugin Configuration」セクションで以下の値を入力します。 Server ID:「my-artifactory」を入力します。 JFrog Platform URL:「 http://artifactory:8081 」を入力します。 Credentials:「admin/******」を選択します。 Allow HTTP Connections:On でチェックを入れます。 CI/CD 環境セットアップスクリプトの解説 「 CI/CD 環境セットアップスクリプトの実行 」章で使用する「 SETUP_HANDS-ON.sh 」スクリプトの処理内容について解説します。 必須コマンドの存在確認 CI/CD の連携をスクリプトで行う際に必要となるコマンドがインストールされているかどうかを確認します。コマンドの存在が確認できなかった場合には、スクリプトは停止しますので、手作業でコマンドのインストールをお願いします。詳細な実行内容は以下実装を確認してください。 setup/SETUP_HANDS-ON.sh#L10 Jenkins ジョブ登録 Jenkins CLI クライアントをダウンロードして、Jenkins CLI でジョブを登録します。 $ wget -O jenkins-cli.jar http://localhost:8080/jnlpJars/jenkins-cli.jar $ java -jar jenkins-cli.jar -s http://localhost:8080/ -auth admin:password create-job build-webapp-webapi < ./jenkins/jobs/config-build-webapp-webapi.xml $ java -jar jenkins-cli.jar -s http://localhost:8080/ -auth admin:password create-job build-webapp-webui < ./jenkins/jobs/config-build-webapp-webui.xml $ java -jar jenkins-cli.jar -s http://localhost:8080/ -auth admin:password create-job deploy-webapp < ./jenkins/jobs/config-deploy-webapp.xml 詳細な実行内容は以下実装を確認してください。 step11-jenkins_create_job.sh Dependency-Track 設定 Admin 初期パスワード変更 Admin ユーザーの初期パスワードを変更します。詳細な実行内容は以下実装を確認してください。 step21-dtrack_change_admin_password.sh OSV 有効化 Google 提供の OSV を脆弱性判定ソースとして有効化します。初期同期の時間を最小限に抑えるため、対象エコシステムを「Maven」のみに限定し、設定後は即座に同期を実行します。詳細な実行内容は以下実装を確認してください。 step22-dtrack_enable_osv.sh Dependency-Track API Key の Jenkins 連携 Jenkins ジョブと Dependency-Track 間の脆弱性スキャン連携における認証を設定します。Dependency-Track の「Administrators」チームで生成した API Key を、Jenkins の認証情報(Credentials)へ同期します。詳細な実行内容は以下実装を確認してください。 step23-dtrack_generate_apikey_and_update_jenkins_secret.sh GitLab Admin 設定 Administrator 権限が必要な Admin area の GitLab 環境全般の各種設定を行います。詳細な実行内容は以下実装を確認してください。 step31-gitlab_update_admin_setting.sh GitLab export からのインポート有効化 GitLabトップページに移り左ペイン最下部から「Admin」ボタン押下 左ペインから「Settings > General」選択 「Import and Export settings」セクションを選択 「GitLab export」をOn Auto DevOps pipeline無効化 GitLabトップページに移り左ペイン最下部から「Admin」ボタン押下 左ペインから「Settings > CI/CD」選択 「Continuous Integration and Deployment」セクションを選択 「Default to Auto DevOps pipeline for all projects」をOff Webhook 許可設定 ローカルネットワーク内にWebhookを送信するには、ここでOn設定をする必要がある Filtering outbound requests | GitLab Docs CIDR計算すると分かるが、コンテナのIPアドレスは「172.16.0.0/12」に当てはまる 手順は以下の通り GitLabトップページに移り左ペイン最下部から「Admin」ボタン押下 左ペインから「Settings > Network」選択 「Outbound requests」セクションを選択 「Allow requests to the local network from webhooks and integrations」をOn 「Save changes」ボタン押下 GitLab リポジトリ設定 リポジトリ作成 事前にエクスポートして用意してあるファイルをインポートして、初期状態のリポジトリを Web UI、Web API の2つ分を用意します。 「Create a project」→「Create a blank project」押下 以下入力して「Create project」押下 Project name = webapp-webapi Project URLで「root」選択 Visibility levelで「Public」選択 「Create project」押下 同様に「Project name = webapp-webui」も作成します。 詳細な実行内容は以下実装を確認してください。 step32-gitlab_import_repository.sh リポジトリへ WebHook 登録 「webapp-webapi」「webapp-webui」の各リポジトリでマージイベント発生時に、各リポジトリに対応する Jenkins のビルドジョブへ Webhook を送信するための設定を行います。 GitLabトップページ移り「webapp-webui」プロジェクトをアクティブにする プロジェクト画面の左ペインより「Settings > Webhooks」を選択 「Add new webhook」押下 以下入力 Name = jenkins-build-webapi URL = http://jenkins:8080/project/build-webapi Secret tokne に Jenkins でジョブに発行したTokenを入力(1234567890abcdefghijklmnopqrstuvwxyz) Trigger は「Merge request events」のみ「On」にする Enable SSL verification = Off 「Add webhook」押下 事前にプロジェクトに「マージリクエスト(=プルリク)」を作ったうえで、リストに移ったら登録したWebhookの右側にある「Test > Merge request events」でテストを実施し、画面上部に「Hook executed successfully: HTTP 200」で成功(マージリクエストが存在しない状態だと、いくらテスト実行してもエラーとなる) 詳細な実行内容は以下実装を確認してください。 step33-gitlab_setting_repository_webhook.sh GitLab CI/CD 向け設定 Jenkins ではなく、GitLab CI/CD でビルドやデプロイを行うために必要な各種設定を行います。 リポジトリへ CI/CD 変数登録 GitLab CI/CD からデプロイする際に Ansible へアクセスするために必要な変数を設定します。詳細な実行内容は以下実装を確認してください。 step34-gitlab_setting_repository_variable.sh グループ Runner 紐付け用のグループ作成 GitLab CI/CDの実行環境を一元管理するため、グループ Runner の紐付け対象となる専用グループを新規に作成します。詳細な実行内容は以下実装を確認してください。 step35-gitlab_create_group.sh グループ Runner 作成 先に作ったグループを紐づけてグループ Runner を作成します。詳細な実行内容は以下実装を確認してください。 step36-gitlab_create_group_runner.sh ローカルリポジトリ準備スクリプトの解説 「 ローカルリポジトリ準備スクリプトの実行 」章で使用する「 PREPARE_LOCAL_GIT_REPO_TO_PUSH.sh 」スクリプトの処理内容について解説します。 リモートからローカルリポジトリ取得 Web API のリモートリポジトリを取得して、開発作業を進めるためにローカルリポジトリへディレクトリを遷移します。 $ git clone http://localhost:13000/admin/webapp-webapi.git $ cd webapp-webapi/ 手順説明では Web API を対象に進めますが、Web API が終わったら手順のコマンドに書かれている「webapp-webapi」を「webapp-webui」に差し替えて、フロントエンドも同様の流れで実施します。 ローカルリポジトリへ開発ブランチ作成 アプリケーションを開発を GitHub flow ベースのソースコード管理に準じて進めるために、開発用のブランチを作成して、該当のブランチがアクティブにします。 $ git checkout -b feature/sample $ git branch -a * feature/sample main remotes/origin/HEAD -> origin/main remotes/origin/feature/sample remotes/origin/main ローカルリポジトリで Web アプリケーション開発 本来であれば、Web アプリケーションをゼロから実装してリポジトリに登録するのが理想的な手順ではありますが、本ハンズオンでは Web アプリケーションの開発ではなく、CI/CD の実施を体験することが主な目的となるため、既に開発されている以下の Web アプリケーションのソースコードを利用して、開発したつもりで進めます。 Toshiharu-Konuma-sti/hands-on-rollingdice-webapp Web アプリケーションのリポジトリをダウンロードするディレクトリを作成します。 $ mkdir ~/handson/hands-on-jenkins/download/ 圧縮形式のリポジトリをダウンロードします。 $ curl -LO \ --output-dir ~/handson/hands-on-jenkins/download/ \ https://github.com/Toshiharu-Konuma-sti/hands-on-rollingdice-webapp/archive/refs/heads/main.zip 圧縮しているリポジトリを展開します。 $ unzip -o ~/handson/hands-on-jenkins/download/main.zip -d ~/handson/hands-on-jenkins/download/ 展開したリポジトリから Web アプリケーションのソースコードを、CI/CD 実演用のリポジトリに移動して持ってきます。「webapi」が終わったら「webui」に置き換えて実行します。 $ mv -f \ ~/handson/hands-on-jenkins/download/hands-on-rollingdice-webapp-main/webapi/* \ ~/handson/hands-on-jenkins/try-my-hand/webapp-webapi/ $ mv -f \ ~/handson/hands-on-jenkins/download/hands-on-rollingdice-webapp-main/webapi/.git* \ ~/handson/hands-on-jenkins/try-my-hand/webapp-webapi/ GitLab CI/CD の各リポジトリ設定 「webapp-webapi」「webapp-webui」の各リポジトリで、GitLab CI/CD を動かす設定ファイルは以下の通りです。 Web API https://github.com/Toshiharu-Konuma-sti/hands-on-rollingdice-webapp/blob/main/webapi/.gitlab-ci.yml https://github.com/Toshiharu-Konuma-sti/hands-on-rollingdice-webapp/tree/main/webapi/.gitlab-ci Web UI https://github.com/Toshiharu-Konuma-sti/hands-on-rollingdice-webapp/blob/main/webui/.gitlab-ci.yml https://github.com/Toshiharu-Konuma-sti/hands-on-rollingdice-webapp/tree/main/webui/.gitlab-ci まとめ Jenkins や GitLab をはじめとするすべてオープンソース(OSS)のプロダクトを組み合わせ、ソースコードのコミットからビルド、セキュリティ検査、デプロイ、そしてオブザーバビリティーと連携したフィードバックループに至るまで、CI/CD および DevOpsの一連の流れをハンズオン形式で体験していただきました。 今回のハンズオンで体験・学習できる主なポイントは以下の通りです。 オール OSS で揃う DevSecOps 環境 Jenkins、GitLab、Dependency-Track、Artifactory OSS、Ansible、OWASP ZAP などのオープンソースのみをコンテナで一括構築し、手軽に実践的な CI/CD環境を用意できること。 シフトレフトを意識した多角的なセキュリティ&品質検証 単体テストや静的解析(SAST: SpotBugs/PMD)だけでなく、SCA(Dependency-TrackによるSBOM・脆弱性可視化)やDAST(OWASP ZAP)まで組み込んだ、安全なサプライチェーンセキュリティのプロセス。 仕様書自動生成とアーティファクト管理 JavaDoc や OpenAPI Spec 形式の仕様書自動生成による最新仕様の可視化と、Artifactory を用いた成果物の一元管理。 継続的な改善を回す DevOps フィードバックループ デプロイして終わりではなく、Grafana 等のオブザーバビリティー環境と連携することで、アプリケーションの稼働状況を観測し、次のコード改修(Plan/Code)へと循環させる「DevOpsの無限ループ」を体感できること。 「CI/CD」や「DevSecOps」、「シフトレフト」といった概念は、言葉や図で理解しようとすると難しく感じられがちですが、実際に手元でコンテナを動かし、パイプラインが実行されてアプリが更新される様子を目の当たりにすることで、その本質やメリットを実感していただけたのではないでしょうか。 本ハンズオンで使用したスクリプトや設定ファイルはすべてGitHubで公開していますので、環境構築の裏側の仕組みを解析してみたり、ご自身の開発アプリを載せてカスタマイズしてみたりと、CI/CD・DevOps 実践の第一歩としてぜひご活用ください。 最後までお読みいただき、ありがとうございました ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Jenkins + GitLab などで体験する『CI/CD入門』 first appeared on SIOS Tech Lab .
画面を人に説明するとき、スクショ(スクリーンショット)ってあったほうが良いじゃないですか?ブログでも、手順書でも、人に聞かれて答えるときでも。「設定画面はここです」「このボタンを押すと」を1つずつ説明していく、あれ。あったほうがいいのはわかるんですよね。 くそだるいんですよね。 自分が作っているものなら Playwright (ブラウザ操作を自動化するやつ)に撮らせればいいんですけどね。他社のサービスだとそこまで手間をかけるのもな~と思いまして。 で、こうしました。動画を1本撮って、 Claude に成形してもらう。 この記事で書くのは、なぜ動画のままでは渡せないのか。何を作ったのか。そして、 僕がそれをどう使い分けているか 。この3つです。 Claude に動画を渡そう!と思うんですが 渡せません。Claude Code に限った話ではなくて、Claude はそもそも動画に対応していないんですよね。 受け取れるのは JPEG / PNG / GIF / WebP の4つです。Claude Code で .mp4 を Read させると、こう返ってきます。 This tool cannot read binary files. The file appears to be a binary .mp4 file. メッセージは「バイナリだから」と言っていますが、PNG もバイナリです。そして PNG は読めます。 つまり動画を狙って弾いているのではなく、読める形式が決まっているだけ です。 ちなみに Gemini はそのまま渡せます。 これは後で効いてくる ので、覚えておいてください。 ここで諦める必要はなくて。動画って、要するに画像の連続じゃないですか。1秒のなかに何十枚も絵が詰まっていて、それがパラパラ漫画みたいに並んでいる。その1枚1枚を フレーム 、日本語だとコマと呼びます。 画像は読めるんです。だったら、 要るコマだけ画像にして渡せばいい。 そこで作ったのが、 動画から指定した場所のコマを取り出して PNG で書き出す CLI と、 それを Claude から呼ぶための Skill です。Python で書いていて、使っているのは PyAV と typer、それに書き出し用の Pillow。 やることは2つだけです。 動画から狙った位置のコマを取り出して、PNG で書き出す 。それだけ。 正直に言うと、 ffmpeg が入っている環境なら ffmpeg -i rec.webm -vf fps=1 out_%04d.png の1行で足ります。僕がわざわざ作ったのは、 apt install を増やしたくない環境 だったからです。逆に言えば、そこにこだわりが無いなら ffmpeg で構いません。 Python には動画を扱うライブラリが他にもあります。 それぞれの特徴は、この記事の一番下に付録としてまとめました。 あとは3ステップです。 録る → 画像にする → 渡す 画面を録画して、CLI に通して、出てきた PNG を順番に Claude へ読ませる。 僕の使い方は、2つに分かれています ここからは使い方の話です。 同じ道具でも、動画のどこを渡すかで2通りに分かれます。 先に言っておくと、普段よく使うのは2つ目のほうです。 どこが要るか分からないときは、等間隔で切り出す 素朴に考えると、こうなります。どこが要るか分からないんだから、 等間隔で全部コマを抜いて、順番に読ませればいい。 手順を丸ごと追いかけたいときは、これで合っています。このときは、 まず1秒ごと で試してください。そこから、多すぎたら粗く、足りなかったら細かくする。 何秒ごとが正解、という答えはありません 。動画の中身によって変わります。1秒ごとというのは「探索するときの出発点」であって、正解の粒度ではないです。 で、これをやると何が起きるか。 トークンを、クソ喰います。 どのくらい喰うかは計算できて、1枚あたりのトークン数は 公式ドキュメント の式で出ます。 ⌈幅 ÷ 28⌉ × ⌈高さ ÷ 28⌉ 解像度 1枚あたり 1280 × 720 約 1,200 1280 × 1000 約 1,700 1920 × 1080 約 2,700 あとは枚数を掛けるだけです。1280×1000 を40枚なら、約68,000トークン。手元で実測しても式と一致しました。しかも渡した画像は会話に残るので、そのあと何往復かすれば入力側はこの何倍かになります。 尺にも気をつけてください。1秒ごとなら1分で60枚、3分で180枚。1回に渡せる枚数にも上限があるので、 長い録画は範囲を切る 。丸ごと処理できるのは数分までだと思っておけば大きく外しません。 秒が分かるなら、その1枚でいい ここで戻ります。 そもそも、40枚も渡す必要ありますか? 覚えておいてくださいと言った話が効いてくるのはここです。 Gemini は動画をそのまま渡せます。 そこそこ大きいファイルでも受け取ってくれる。 だったら、探すほうは Gemini にやらせればいい。動画ファイルを渡して、 会話しながら「何秒のところ?」を出させる 。「設定を保存したのは何秒ですか」と聞けば返ってきます。 あとはその秒だけ、Claude 用に切り出す。 40枚渡すのと、3枚渡すのとでは、桁が違いますからね。 動画を読めるモデルに探させて、読めないモデルには答えだけ渡す 、という分担です。 僕が普段やっているのはこっちです。 並べてみると、道具に求めるものが違う この2つ、 要件で見ると別物なんですよね。 使い方 要る要件 秒が分かっている (Gemini に出させた、または見当がつく) 秒数を指定して、 そのコマが正確に取れる こと 手順を追いかけたい (どこが要るか分からない) 等間隔に切り出せる / 順序が保たれる (ファイル名の並び=時間の並び) / 枚数に上限がある / 切り出す前に枚数を確認できる / 録画形式が通る 上は1枚か数枚抜くだけなので、要件も1つで足ります。下は違う。40枚を順番に読ませることが前提になるので、順序も、枚数も、事前の確認も、全部が要件になる。 つまり、要件は道具の性能ではなく、使い方から出てきます。「等間隔に切り出せること」は、機能一覧を眺めていても出てきません。「どこが要るか分からないまま渡す」という使い方を決めて、初めて出てくる。 ここで言いたいこととしては、自分で作るAI用のCLIはメンテしていく必要があるんで要件を詰めて必要なものを作って継続的にメンテしていこうね!って話です。 だから僕は、コードを配るのをやめました。配るのは要件です。環境も好みも人それぞれなので、それを AI に読ませて自分の環境で作らせたほうが早い。この記事の付録がそれです。 正直、使わなくていい場面もあります ここまで書いておいてなんですが、僕もいまだに使い分けています。こういうときは、画像に変えていません。 そもそも Playwright で足りる なら、それでいい。自分が開発している画面はだいたいこれです 動画のままでいい。 見せる相手が人間なら、画像に変える理由がありません。YouTube に上げて記事に埋めるほうが早い 動画ですら要らない。 1回しか映らない画面を見せたいだけなら、スクショ1枚で終わりです 1つ目に補足が要ります。他社のサービスでも Playwright は使えるんですよ。使えるんですが、 検証中はどこを撮ればいいか自分でも分かっていない 。撮る場所を先に指定できないなら、自動化しても嬉しくないんですよね。 Playwright に頼れないとき の判断の軸はこうです。映る画面が分かっていて、しかも動きが無いなら、動画にする必要はありません。どちらか一方でも崩れたら、動画にしたほうが早い。 で、結局なにができるようになるのか 動画を渡せない前提で動いていたときは、 「どこを撮るか」を撮る前に決めるしかありませんでした。 撮り終わってから「あそこも要ったな」と気づいても、もう一度操作をやり直すしかない。 Claude が動画を受け取れるようになったわけではないです。でも、画像に変える経路ができた時点で、撮り方の制約のほうが消えます。 「どこを撮るか」を、撮る前に決めなくてよくなりました。 これが一番大きいです。記事を書いている最中って、まだ検証中なんですよ。どの瞬間が素材として使えるか、撮る前には分からない。触って、返ってきて、それを見て初めて「ああ、ここか」となる。 動画にしておけば、分かってから選べます。「やっぱりここも要る」となっても、同じ動画から抜き直せる。 そしてもう1つ。 撮った動画は、あとからいくらでも使い回せます。 実際にやったのは、この3つです。 記事の素材にする。 Gemini でスプレッドシートを触る検証をしたときは、操作をまるごと録っておいて、あとから必要な場面だけ画像にして記事に貼りました。 記事に載っている画面は、全部その動画から抜いたもの です 手順書を起こす。 業務アプリの操作を1手順ぶん録って、40枚に切り出して、正解を知らない別の AI に「この画像から手順書を書いて」と渡しました。 返ってきた手順書に「保存されたかどうかが画面から判別できない」と書いてあったので実物を見にいったら、 アプリのバグでした 。テストは緑だったのに、です 自分の姿勢を見てもらう。 ジムで「このフォーム合ってる?」となったとき、14秒だけ撮って渡す。返ってきたのは「頭・腰・かかとは一直線。ただし体が立ちすぎで、床から見て60〜65度くらい」でした( ジムで Claude にコーチをやらせている話 もあります) 3つとも、渡しているのは 画像 です。やっていることは同じで、被写体が違うだけなんですよね。 画像にした先で何ができたかは、別の記事で1つずつ書きます。 (予定) 最後に正直なところを1つだけ。 これは効率化ではなく、人間の手間をトークンで買っている という話です。僕はそれで釣り合うと思っていますが、判断は分かれると思います。 まずは手元の動画を1本、 読めるモデルに投げて「何秒のところ?」と聞いてみてください 。そこが決まらないときだけ、等間隔で切り出せばいい。 付録:動画からコマを切り出すツールの要件 以下をまるごとコピーして、AI に「これを満たすものを作って」と渡してください。言語も構成もお好みで構いません。 できあがったら、 秒数が画面に写っているもの (時計・タイマー・動画プレイヤーの再生位置)で試してみてください。指定した秒と、出てきた画像に写っている秒が一致するか。ここがズレる実装は、黙って近くのキーフレームに寄っています。 僕が動かしている環境 要件のほうにバージョン番号は書いていません。値は環境と時期で腐るので、性質だけにしてあります。参考までに、僕の手元はこれです(2026年8月時点)。 Python 3.12 PyAV 16.1.0 / Pillow 12.3.0 / typer 0.24.1 録画はブラウザの画面収録(webm) この要件自体も、記録からではなく動いている実物から起こしました。 何をするものか 動画ファイルから、指定した時点のフレームを画像として取り出すコマンドラインツール。 全体に効くこと 実行環境に追加のシステムパッケージを要求しないこと。 デコーダを同梱した言語パッケージだけで完結させる 出力は AI が読める画像形式 (PNG / JPEG)。どちらで出すか選べること 動画のメタデータを表示できること (尺・fps・解像度)。切り出す前に「何秒あるのか」が分からないと粒度を選べない 尺が取れない動画で、黙って推測しないこと。 形式によっては映像トラック側に尺が無く、コンテナ側にしか無い。順に探して、どこにも無ければ失敗させる。ここを推測で埋めると、以降の計算が全部静かにズレる 入力が無い・壊れている・範囲外を指定した。これらは、 すべて明示的なエラーで落とすこと 使い方A:どの秒を見たいか分かっているとき 秒数を指定して、その時点のフレームが取れること。 複数の秒をまとめて指定できること 指定した秒に正確に届くこと。 多くのデコーダはキーフレーム単位でしか飛べないので、飛んだあと目的の時刻までデコードを進める実装ができること。 シークに渡す値の単位にも注意 (秒とは限らず、例外も出ずに見当違いの位置へ着地する) 出力先はファイル1つでも、ディレクトリでも指定できること。 無ければ作ること その時刻がファイル名に入っている こと 使い方B:どこが要るか分からず、手順を追いかけたいとき 等間隔に切り出せること。 指定の仕方は3通りあり、 そのうち必ず1つだけ を受け付けること 秒間隔(N 秒ごと) fps(1秒あたり N 枚。秒間隔の逆数と同じ結果になること) 総枚数(範囲を等分して N 枚。両端を含む。 1枚と言われたら開始点だけ返す ) 0個でも2個以上でもエラーにする。 黙ってどれかを優先すると、指定したつもりの粒度と違うものが出る 範囲を切れること (開始・終了)。録画の冒頭は起動待ちで何も写っていないことが多く、そこに払う理由がない 割り切れない間隔でも、終端を越えないこと ファイル名の辞書順が時間順と一致すること。 連番をゼロ埋めで先頭に置く。ファイルの列挙は辞書順で返り、その順のまま読ませることになるので、ここが崩れると手順の順序が崩れる 枚数に上限があり、超えたら1枚も書かずに止まること。 粒度を1段細かくすると枚数は一気に増える。途中まで書いて止まると後始末が要る。止めるときは対処法まで出す(粗くする / 範囲を狭める / 上限を上げる) 切り出す前に、何枚になるかを確認できること。 しかも、 フレームをデコードせずに 答えること。粒度選びは試行錯誤になるので、毎回デコードを待たされると選べない 何枚になるかを表示するとき、 全部は並べないこと (先頭だけ出して残りは件数で示す) 一般的な録画形式が通ること。 ブラウザの録画出力は MP4 とは限らず、尺の求め方が形式ごとに違う 作らなくていいもの 動画の編集・変換・フォーマット変換 GUI シーン変化の自動検出(等間隔で足ります) 参考:動画を扱う Python ライブラリの特徴 特徴 PyAV FFmpeg のバインディング。デコーダ同梱でシステムへの追加インストールが要らない。低レベルなので、シーク後にどこまで進めるかを自分で制御できる OpenCV (headless 版) 同じくデコーダ同梱で追加インストール不要。API がシンプルで扱いやすい。シークの精度は用途によって確認が要る ffmpeg を直接呼ぶ 挙動は最も素直。ただし ffmpeg 本体のインストールが必要 moviepy 高レベルで書きやすい。こちらも ffmpeg 本体が必要 「追加インストールを増やさない」を最優先にすると、上2つが残ります。そのうえで使い方Aを満たすなら、 シーク後の位置を自分で制御できるか で選んでください。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 要るコマだけ画像にすれば、Claudeに動画を見せられる first appeared on SIOS Tech Lab .
ども!最近はスマホから Claude Code に指示を投げてばかりいる龍ちゃんです。 少し前に「AI に任せたんなら散歩でも行ってきたら」みたいな話を見かけまして。真に受けて、本当に行ってきました。歩きながらスマホをいじっていたんですが、そのとき効いていたのは散歩そのものではなくて、 席を立ってもいい状態のほう でした。 Claude Code には、PC で動いているセッションにスマホから入って続きができる機能があります。公開されたときに一度触って、そのまま戻した人も多いんじゃないでしょうか。僕もそうでした。スマホからだと結局なにも確認できないな、と思って。 そこから半年ほど使い込んで、いまは毎日これで動かしています。今回はその話です。 今回の内容です。 繋ぎ方と、スマホ側は入力が速いという話 リモートだと作業を分けられない、という詰まり スマホから一言で、セッションごと増やす リモートに残るつらさ2つと、その凌ぎ方 繋ぐのは一瞬。あとは喋って、撮って、投げるだけ 動いているセッションの中で、こう打ちます。 /remote-control 以上です。名前を付けたいときは /remote-control main のように渡します。あとはスマホの Claude アプリを開いて「コード」タブを見ると、そのセッションが並んでいます。タップすれば続きから喋れます。 起動のしかたは他にもありますし、プランの条件や環境変数の要件もあります。そのあたりは 公式ドキュメント が正なので、そちらを見てください。ここに手順を書き写すのはやめておきます。5ヶ月前に自分用のメモとして書いた手順を今日読み返したら、見事に腐っていました。 繋いだあとの入力の話をします。外では、文字を打っていません。喋っています。 歩きながら画面を見て打つのは単純に危ないですし、周りにも迷惑です。かわりに口で言って、送信を押す。これだと前を向いたまま指示が出せます。 外に持ち出したときに効くのは、この入力のほう でした。 音声入力なので表記は崩れます。固有名詞は化けるし、英語と日本語が混ざるとだいたい変なところで切れます。それでも通ります。整えてから出さなくていいので、思いついた形のまま投げています。 写真も同じで、撮ってその場で投げられます。僕はジムに Claude を連れて行ってトレーナーをやらせていて、トレッドミルの表示を撮って送ると、数字を読んで前回と並べて返ってきます。その運用そのものは「 Claude Codeをジムに連れ出したらトレーナーになっていた 」に書いたので、ここでは写真が飛んでいくところだけ。 机の前だと、スクリーンショットを撮って、保存して、パスを渡して、という手数が要るところです。スマホは撮る動作がそのまま入力になります。 喋っても撮っても、前提がひとつあります。 動いているのはあくまで手元の PC なので、そこが止まると出先で何を打っても進みません。 蓋を閉じただけなら大丈夫で、復帰したときに繋ぎ直してくれます。ただ 寝ている間は当然ながら1ミリも進んでいません 。出先で指示を出したつもりが、帰ってから動き出すやつです。回線のほうはもう少し厳しくて、10分ほど繋がらない状態が続くとセッションは時間切れになり、プロセスごと落ちます。 要するに、 起こしておいて、繋いでおく 。ここは社内に良い記事があるので、そちらへどうぞ。佐々木さんの「 満員電車でも Claude Code を動かし続ける技術 」です。僕は電源設定まわりをこれで直しました。 リモートだと、作業を分けられない 喋れば投げられる状態でうろうろしていると、思いつくことがどんどん増えます。ログも見たいし、別の記事のネタも転がしたいし、さっき止めた調査の続きもやりたい。歩いているときや電車の中って、なぜか発想が散らかるんですよね。 普段、手元で作業しているときはこういう状態にはなりません。関係ない話を1本のセッションに積むと動きが鈍るのは体感で分かっているので、用が変わったら素直にセッションを分けます。ペインを増やすなり、ウィンドウを開くなり、やりようはいくらでもあります。 ところがリモートだと、これができません。 繋がっているのは動いているセッション1本で、そこから増やす手段が見当たらない。まったく手が無いわけではなくて、Agent に投げればコンテキストは分かれます。ただあれは会話の相手にはならないんです。投げて、返ってくるだけ。走っている途中で「やっぱりそっちを先にして」が言えない。 分けたほうがいいのは分かっているのに、分ける手段がない。ここが半年くらい、ずっと引っかかっていた場所でした。 スマホから一言で、セッションごと増やす そこで登場するのが tmux です。手元でいつもペインを増やしているのも、これです。 tmux はターミナルを多重化するツールで、1枚のターミナルから新しいターミナルを切り出せます。切り出した1枚が「ペイン」です。効いてくるのはもう片方の顔で、ペインからペインへ操作を送り込めるんですよね。隣で代わりにこれを打っておいて、が別のペインから頼める。つまりClaude Codeに依頼をすればBashコマンドで増殖できるんですね! 手元のセッションに auto mode を渡してあるなら、そいつに tmux を触らせればいい。 スマホからこう言います。 サブを追加で切って Auto mode remote control blog -idea でよろしく これも音声入力なので、表記は崩れたままです。 blog -idea はハイフンの前にスペースが入っているし、真ん中だけ英語になっている。それでも通ります。 手元がやっているのはシェル3本です。 tmux send-keys -t 0:1.3 \ "claude --permission-mode auto --remote-control blog-idea" Enter; \ sleep 6; tmux capture-pane -p -t 0:1.3 | tail -25 ペインを割って、そこにコマンドを送り込んで、6秒待って、 その画面を読み返しています 。 最後の読み返しが効きます。起動して終わり、にしないので、生えた側が本当に立ち上がったかを確認してから報告が返ってくる。スマホ側に「たぶん動いてるはず」を抱えなくて済むのが、思っていたよりありがたかったです。 起動する場所はリポジトリのルートに固定しています。ここを外すと .claude/ に置いた設定を拾ってくれないので、生えた先だけ別人みたいな振る舞いになってしまいます。 46秒後、スマホの一覧はこうなっていました。 blog-idea が先頭に増えています。URL を叩いて入る必要はなくて、勝手に並びます。スマホで口走った blog -idea が blog-idea として見出しになっているのも、あとから見分けるときに助かりました。(このあたりはClaudeがいい感じに解釈してくれます。) 嬉しかったのは、 事前に建てておかなくていい ことでした。用途ごとにセッションを用意しておく運用も一度やってみたんですが、結局そのとき欲しくなる箱は事前には分かりませんでした。思いついた順に、思いついた名前で生やすほうが性に合っていました。 「1プロセスにつき1リモートセッション」という制約は、いまも生きています。これはその制約を破っているのではなくて、プロセスのほうを増やして回り込んでいるだけです。 ここまで書いておいてなんですが、公式には サーバーモード という手もあります。 claude remote-control で起動しておくと1プロセスで複数のセッションを捌けますし、 --spawn worktree を付ければセッションごとに git worktree まで分けてくれます。 それでも僕がペインを切っているのは、増やしたくなるのが いつも作業を始めたあと だからです。机で普通に claude を立ち上げて、出先で用が増える。そのときにはもうサーバーモードでは起動していません。あとから起こすにしても、結局どこかでプロセスを1つ立てる必要があって、それをスマホから頼めるのがこの手の効きどころでした。 2026-08-10 DevContainer に tmux を入れて、設定ごと配る 繋いだ先には、つらいところが2つある ここまでが良かった話です。良くない話もします。 変化の全体像が見にくい よく「スマホからだと何も見えない」と言われますし、僕も最初はそう思っていました。これは違いました。 変更したファイル名は出ます。増減の行数も出ます。タップすれば、その編集が何を書いたかまで読めます。読めるんです。 しんどいのは「で、結局どこがどう変わったの」のほうです。1件ずつなら読めるので、材料は全部そこにあります。ただ5ファイル触ったあとに全体を俯瞰しようとすると、1件ずつ開いて頭の中で組み立て直すことになる。まとめて1枚で見る手段がないぶん、そこだけ極端に手数が増えます。 「差分が見えない」と一言で片付けると嘘になるので、ここは分けて書いておきます。 見えないのではなく、見にくい。 届いていないものは無くて、まとめて見られないだけです。 放っておくと切れる さっきのジムのセッションも、器具を移動して戻ってきたら「このセッションはアーカイブされました」と出ていました。写真を投げて遊んでいる分にはよかったのに、少し放っただけで切れます。 何分で切れるかは、正確なところが分かっていません。僕の体感でもばらばらですし、調べてみたら公開されている報告も5分から30分くらいに散らばっていました。 たぶん定数ではないです 。なので僕の環境の数字を書いても、あなたのところでは再現しないと思います。 GitHub の Issue は上がっていますし、一度は直っています。ただ、その修正が全員に届いているわけではない、というのが現状に近い説明になります。直ってはいるけれど、自分のところには来ていない。 送信ボタンを毎回手で押すのが面倒、みたいな小さい不便もありますが、まあそれは些細な話で。困るのは上の2つです。 そしてこの2つ、 繋いだ先をどういじっても直りません。 直すのは、繋いだ先ではなく手元 なので手元のほうを変えます。やっているのは2つです。 見にくいなら、見やすい形で返させておく。 図や表は作らせればセッションにそのまま出るので、確認したいものは最初から画像で出させます。長い一覧や比較表は Artifact に逃がして、スマホのブラウザで開きます。作った時点では自分にしか見えないので、そのまま個人用のビューアとして使えます。小さい画面に何を出させないかを決めておくと、ずいぶん読みやすくなりました。 ファイルそのものを送らせる手もあります。「アウトライン送って」と言えば、その .md がスマホに届きます。 差分の一覧から1件ずつ開いていくのとは別で、こちらは そのファイルの現在形が丸ごと 来ます。確かめたいものが1つに決まっているときは、これがいちばん速いです。 ただ、長いものは長いまま届きます。86行の md は86行のまま来るので、小さい画面で上から読み下すのはやっぱり疲れます。 1点を確かめるには効きますが、全体像のほうはこれでも埋まりません。 なので、いちばん使っているのはこれです。 !git push origin main ! を付けると Claude を挟まずシェルに直接届くので、「これから push します」の口上が挟まりません。結果だけ返ってきます。押してしまえば、あとは GitHub のスマホ表示が差分を全部見せてくれる。横断した眺めは元から Git が持っているので、そこへ寄せれば済む話でした。もちろん、「プッシュして」という依頼もありですね。 これ、リモート用の特別な仕込みがゼロなのが良くて。普段からやっている push が、そのまま解決策になっています。 見にくいほうはこれで足ります。残りは、切れるほうです。 切れるなら、切れる前につついておく。 繋ぎっぱなしにする小細工はいくつか試したんですが、効いたのは結局これでした。定期的に本物の仕事をさせておく。裏側の理由まで書くと長くなるので畳みますが、「生きてます」という信号を送るだけでは足りなくて、実際のやりとりが要るようです。 つついておけば、繋がったままにはできます。 並べてみると、どちらも同じことをしています。 向こう側は変えられないので、こちら側を変えている。 見にくいほうは受け取る形を変えて、切れるほうは触る頻度を変えているだけで、やっていることは一緒でした。 ここまでを1枚にすると、こうなります。 行きは太いままです。喋れば通るし、撮れば送れるし、一言でセッションまで増える。細いのは帰りだけで、そこは繋いだ先では太くできません。 おわりに 半年前に一度戻した機能ですが、いまは毎日使っています。 窓口はその場で増やせるようになりましたし、残ったつらさも手元で凌げます。散歩に行ってこい、はわりと本当でした。 最後に2つだけ。向きの違う注意です。 ひとつは 内向き の話で、この記事は auto mode を前提に書いています。確認を飛ばして動く設定なので、何を触らせるかは分かったうえで渡してください。僕は自分のリポジトリの中だけで使っています。基本的にコンテナで運用しているので、リポジトリの内部がメインの対象になります。 もうひとつは 外向き の話で、人に見せるなら公開範囲を一度見ておいてください。セッションの共有は Settings > Privacy > Shared chats から棚卸しできます。Artifact のほうは、個人プランだと共有=公開リンクの一択です。しかもヘッダに自分の名前と、作った Artifact が全部並ぶギャラリーへのリンクが付くので、1枚だけ見せたつもりにならないように。 そして、繋がるようになったことと、任せられるようになったことは、まだ別の話です。受け取ったものを見てどう判断するか、そこは別の記事で書きます。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Claude Codeをスマホでリモート、その場でペインが増える first appeared on SIOS Tech Lab .
ども!最近は一日中 Claude Code を回している龍ちゃんです。 コンテナの中で作業していると、ターミナルを分けたくなる場面があります。片方で Claude Code を走らせて、もう片方でログを見たりコマンドを叩いたり。VS Code のターミナルでも分割はできるんですが、増えてくるとタブがどれも同じ見た目で並ぶので、どれで何を動かしていたか分からなくなるんですよね。あと、閉じると中で動いていたプロセスも一緒に死にます。 tmux を入れるとこの2つが解決します。名前を付けたセッションの中で画面を割って並べられるのと、セッションから抜けても中のプロセスが動き続けるやつですね。 ただ、コンテナに入れるとなると素の環境とは少し勝手が違います。 ~/.tmux.conf に設定を書いても、リビルドしたら消えるんですよ。なので設定の実体はリポジトリ側に置いて、コンテナを作るときに配る形にします。 今回の内容です。 Dockerfile に tmux を足す 設定をリポジトリに置いて postCreateCommand で配る 実際に使っている .tmux.conf の全文と、各設定の理由 なお、この記事はコンテナの中の話に絞ります。WSL のセットアップとか、ターミナルエミュレータをどれにするかは扱いません。セッション・ウィンドウ・ペインといった tmux の概念も、公式 Wiki の Getting Started に任せます(「Sessions, windows and panes」の節が図つきで分かりやすいです)。 何をいまさらですけど、汎用的に使えるものなので記録として置いておきます! Dockerfile に tmux を足す まずはインストールから。うちのベースイメージは python:3.12-slim (Debian bookworm ベース)なので、 apt-get install に1行足すだけです。 FROM python:3.12-slim RUN apt-get update \ && apt-get install -y --no-install-recommends \ tmux \ && rm -rf /var/lib/apt/lists/* すでに apt-get install している行があるなら、そこに tmux \ を1行差し込めば大丈夫です。これで tmux 3.5a が入りました。この記事の検証環境もこれです。 ここはベースイメージによって書き方が変わるところなので、 FROM を確認してから足してください。Debian / Ubuntu 系なら上のとおりですが、Alpine 系( FROM node:22-alpine など)なら RUN apk add --no-cache tmux になります。DevContainer の公式イメージ( mcr.microsoft.com/devcontainers/* )は Debian ベースなので apt-get で大丈夫です。 設定の実体はリポジトリに置く ここがコンテナならではの部分です。 tmux を入れたあと、普通は ~/.tmux.conf に設定を書きます。素の Linux ならそれでいいんですが、コンテナの中のホームディレクトリって、ボリュームでもバインドマウントでもない、ただのコンテナ内のファイルなんですよね。 実際に見てみるとこうなります。 $ findmnt -T ~/.tmux.conf -o TARGET,SOURCE,FSTYPE TARGET SOURCE FSTYPE / overlay overlay overlay が返ってきました。イメージのレイヤの上に乗っているだけなので、コンテナを作り直したら当然消えます。 なので、 設定の実体は .devcontainer/ の中に置きます 。書いた設定を .devcontainer/.tmux.conf として保存するだけです(中身は後述します)。 リポジトリの中に置くと、設定を変えた履歴が Git に残りますし、他のメンバーがクローンした時点で同じ設定が入ってきます。「あのキーどうやるんだっけ」と聞かれても、リポジトリの中を見てくれで済むのが地味に助かってます。 ちなみに DevContainer には dotfiles を取り込む機能もあって、そっちを使う手もあります。ただあれは「個人の設定を自分のコンテナに持ち込む」仕組みなんですよね。今回やりたいのは「このリポジトリで作業する人全員に同じ設定を渡す」ほうなので、リポジトリに実体を置く形にしています。個人の好みはそのまま dotfiles 側に置けばいいので、両立もできます。 postCreateCommand で配る .devcontainer/ に置いた conf を、コンテナ作成時にホームへコピーします。 devcontainer.json の postCreateCommand の末尾に1つ足すだけです。 { "postCreateCommand": "... && cp .devcontainer/.tmux.conf ~/" } postCreateCommand をまだ書いていなければ、頭の ... && は要りません。 "postCreateCommand": "cp .devcontainer/.tmux.conf ~/" だけで動きます。 これでコンテナを作るたびに ~/.tmux.conf へ配置されます。リビルドしても、新しいメンバーが初めて開いても、同じ状態から始まる。 ここまでで触ったファイルを1枚にするとこうなります。 実際に使っている .tmux.conf うちで動いている全文です。そのままコピーして使えます。 # ============================================================ # プレフィックスキー: Ctrl+a(片手で押しやすい) # ============================================================ unbind C-b set -g prefix C-a bind C-a send-prefix # ============================================================ # 基本設定 # ============================================================ set -g default-terminal "tmux-256color" set -as terminal-features ",xterm-256color:RGB" set -sg escape-time 0 set -g history-limit 50000 set -g base-index 1 setw -g pane-base-index 1 set -g renumber-windows on set -g mouse on # ============================================================ # ペイン操作(直感的なキーバインド) # ============================================================ # 分割: \ で左右、- で上下 bind \\ split-window -h -c "#{pane_current_path}" bind - split-window -v -c "#{pane_current_path}" unbind '"' unbind % # ペイン移動: Shift+矢印キー(プレフィックス不要) bind -n S-Left select-pane -L bind -n S-Right select-pane -R bind -n S-Up select-pane -U bind -n S-Down select-pane -D # 新しいウィンドウも現在のパスで開く bind c new-window -c "#{pane_current_path}" # 設定リロード bind r source-file ~/.tmux.conf \; display "Reloaded!" # ============================================================ # ステータスバー # ============================================================ set -g status-bg colour234 set -g status-fg colour137 set -g status-left-length 40 set -g status-right-length 60 set -g status-left "#[fg=colour229,bold] #S " set -g status-right "#[fg=colour233,bg=colour241] %Y-%m-%d %H:%M " デフォルトから変えたところを表にしておきます。デフォルト値は tmux 3.5a を隔離ソケット( tmux -L 別名 -f /dev/null )で起動して実際に確認したものです。 設定 デフォルト うちの値 変えた理由 プレフィックス Ctrl+b Ctrl+a ホームポジションから指を動かさずに押せる。GNU Screen 由来の定番 左右分割 % \ \ は | (縦棒)と同じキー。縦の仕切りが入る=左右、と対応する 上下分割 " - - が横線そのもの。どちらも Shift 不要になる ペイン移動 prefix → 矢印 Shift+矢印 -n を付けるとプレフィックス無しで直接反応する。一番回数の多い操作なので escape-time 10 0 Esc キーの反応が一瞬遅れるのを消す history-limit 2000 50000 Claude Code の出力が長いので、2000行だとすぐ流れる base-index 0 1 ウィンドウ番号を1始まりに。キーボードの並び順と揃う pane-base-index 0 1 同上。ペイン番号も1始まり mouse off on ペインの境界をドラッグしてリサイズできる。スクロールもホイールでそのまま いくつか補足です。 Ctrl+a はシェルの行頭移動とぶつかります。なので bind C-a send-prefix をセットで入れていて、 Ctrl+a を2回押すとシェル側に本物の Ctrl+a が届くようにしてあります。 分割の -h / -v は名前と結果が逆に見えるので注意です。 split-window -h (horizontal)が 左右 分割で、仕切り線は縦に入ります。 $ tmux -L dc2 -f /dev/null list-keys | grep split-window bind-key -T prefix \" split-window bind-key -T prefix \% split-window -h -c "#{pane_current_path}" は「分割したペインを、今いるディレクトリで開く」指定です。これが無いとホームディレクトリで開くので、毎回 cd する羽目になります。 デフォルトの - には delete-buffer (コピーバッファの削除)が割り当たっているので、上書きすることになります。僕は使ったことがなかったので割り切りました。 あと default-terminal を tmux-256color にする設定、これ tmux 3.5a だともうデフォルト値になってたんですよね。古いバージョン向けに書かれた記述が残っていただけでした。バージョンによっては要るので消してはいませんが、新しく書くなら不要です。 プラグインマネージャ(tpm)は入れていません。ここまでの設定で困っていないので、増やしていないだけです。 ここまでの設定が効いているかは、コンテナをリビルドしてから tmux を立ち上げて Ctrl+a → \ を押すのが手っ取り早いです。左右に割れたら、conf がコピーされていて中身も読まれています。割れなかったら ~/.tmux.conf があるかを見てみてください。 入れるとこうなる 僕は今、こういう置き方で使っています。 左をメインにして Claude Code を回し、右はサブとして上下に割って調べものと確認用。 Ctrl+a → \ で左右に割って、右側にカーソルを移してから Ctrl+a → - で上下に割る、という手順です。 あともう1つ、コンテナで効くのがデタッチです。 Ctrl+a → d でセッションから抜けても、中のプロセスは動き続けます。Claude Code に長い作業を投げたまま抜けて、あとから tmux attach で戻れる。VS Code のターミナルを閉じたら死ぬのとは全然違いますね。 この記事で変えたのはペイン操作まわりだけなので、それ以外のキーはデフォルトのままです。何ができるのか一度眺めておくと拾いものがあるので、 man page の DEFAULT KEY BINDINGS を貼っておきます。足したい設定が出てきたら .devcontainer/.tmux.conf に書き足せば、それも消えずに残ります。 ここから先、たとえば「スマホから指示を出して、その場でペインを増やして新しい Claude Code を立ち上げる」みたいな使い方もできるんですが、それは話が長くなるので「 Claude Codeをスマホでリモート、その場でペインが増える 」に分けました。 tmux send-keys で隣のペインの Claude Code にコマンドを送る話も、そちらに入っています。 まとめ コンテナの中で tmux を使うと、画面を割って並べられるのと、抜けてもプロセスが残るのが効く ~/.tmux.conf は overlay 上のただのファイルなのでリビルドで消える Dockerfile に tmux 、 .devcontainer/.tmux.conf に設定の実体、 postCreateCommand で cp の3つで解決する リポジトリに入るので、クローンした時点でチーム全員に同じ設定が渡る コンテナの環境って、作り直すたびに素の状態に戻るのが利点でもあるんですが、育てた設定まで戻ってしまうと使い物になりません。実体をリポジトリ側に置くだけで、その両方が成立します。 もし他にも「この設定は入れとけ」ってのがあったら、ぜひ教えてほしいです。 ほなまた〜 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post DevContainer に tmux を入れて、設定ごと配る first appeared on SIOS Tech Lab .
ども!最近バグ調査のたびに Azure CLI を叩いている龍ちゃんです。 バグ調査でコードを読むところ、AI にやらせると爆速なんですよね。処理の順序を追って、どこで何が呼ばれているかを整理するくらいなら、もう完全に任せてます。ここはめちゃくちゃ助かってます。 で、あるとき思ったんです。次はバグの原因まで見つけられるように、アプリのログも自由に見れる形にしたら、調査もっとはかどるんじゃね?と。 ただ、正直に言うと僕、 KQL そんなに書けないんですよ。Application Insights のログを引くやつですね。書けないから今までポータルのグラフを眺めて終わっていました。 で、そこを整備してみたら、 書けるようにならないまま読めるようになった んです。整備といっても大したことはしていなくて、Azure CLI を Claude Code に叩かせる形にしただけです。これがめちゃくちゃ良かったので共有します。 今回の内容です。 仮説を渡すと、それを確かめる集計がそのまま返ってくるという話 実際にどう頼んで、どう叩かせているか AI に Azure を触らせる前に、こっちで決めておいたこと それでも仮説は人間が立てたほうがいい、という話 なお、Application Insights そのものの説明はしません。社内の先輩の武井さんが書いた 世界一わかりみの深いAPM 〜Application Insights〜 という決定版があるので、そちらに譲ります。計装の入れ方も扱いません。すでに入っていて、必要な情報が出ている状態からのスタートです。KQL の書き方も解説しません。書けないので、動いたものをそのまま貼っていきます。 あともう1つ。Azure 側にも 自然言語で聞ける仕組み が乗っています(Observability Agent)。ただ Azure Copilot へのアクセスが要って、今のところ英語のみなんですよね(2026年8月時点)。 この記事はそっちの話ではないです。 手元の Claude Code から Azure CLI を叩く形の話をします。 うれしいのは、仮説をそのまま確かめられることでした 先に一番おいしいところを言います。 思いついた仮説を、そのまま確かめられる ようになったんですよね。 バグを追っていると「たぶんこうなってるんじゃないか」が浮かぶじゃないですか。最初の1回だけ遅いんじゃないか、とか。特定のエンドポイントだけ詰まってるんじゃないか、とか。 前はそこで止まってました。確かめるには KQL を書かないといけなくて、書けないから調べながら組み立てて、たいてい1本目で力尽きる。だからポータルのグラフを眺めて「なんとなくそれっぽい」で終わっていたんです。 今は、その仮説を言葉のまま渡せば、確かめる集計が返ってきます。表を見て当たりか外れかが分かって、外れていたら次の仮説を投げる。 この回転が速いこと が、僕にとっての「ログが調査に使える」でした。 グラフを眺めるのと決定的に違うのはここだと思っていて、ポータルって「用意された切り口」を見る場所なんですよね。自分の仮説に合わせた切り口は自分で作るしかない。そこを肩代わりしてもらえると、仮説を出すコストだけ払えばよくなります。 生ログを丸ごと渡してるわけではないです 「AI にログを読ませる」と言うと、生ログをどさっと投げるイメージがあると思います。僕も最初はそう思っていました。 でも実際は逆で、KQL 側で集計してから返させています。AI が受け取るのは集計済みの表だけです。生ログが要る場面なら読ませますけど、基本は表です。 これ、トークンをケチるためだと思われがちなんですが、そこはあんまり関係なかったですね。効いたのは、 集計の条件がクエリに残る ことのほうでした。 生ログを渡して集計させると、その集計をどうやったのかが会話の中に消えます。後から「この数字どうやって出した?」と聞き直すことになる。クエリで集計してあれば、そのクエリ自体が手順書なんですよね。数字を疑いたくなったら、同じものをもう一度叩けばいい。 あと単純に、数を数えるところを AI にやらせるのが怖いんですよね。なので集計が要る作業は全部 KQL 側に寄せています。 ログで見えたことを、そのまま設定と突き合わせられます これは後から効いてきたやつです。 ログを引くのが az monitor app-insights query で、リソースの設定は az functionapp show 。同じ CLI なんですよね。 az functionapp show -g <リソースグループ> -n <関数アプリ名> \ --query "properties.functionAppConfig.scaleAndConcurrency" -o json { "alwaysReady": [ { "instanceCount": 1, "name": "http" } ], "instanceMemoryMB": 2048, "maximumInstanceCount": 40, "triggers": {} } この出力は Flex Consumption プラン のものなので、別のプランだと形が変わります。 「ログ上はこう見えるけど、設定はこうなってる」の突き合わせがそのままつながります。ポータルだと画面を行き来してスクショを渡すことになって、渡した時点でどれがどこの値なのか分からなくなるんですよね。 実際は、知りたいことと仮説を渡してるだけです じゃあ何を渡しているのか、という話です。 先に1つだけ言っておくと、 az は権限が付いていればリソースを消せます 。この後の「決めておいたこと」に注意書きを書いたので、手を動かす前にそこだけ読んでもらえると安心です。 渡しているのは2つだけです。 知りたいこと と、 仮説 。 こんな感じで言ってます。 この時間帯で遅かったやつをエンドポイント別に見たい。たぶん最初の1回だけ遅いんだと思う これで返ってくるのがこういうクエリです。 requests | where timestamp between (datetime(2026-07-20T00:00:00Z) .. datetime(2026-08-04T08:00:00Z)) | where cloud_RoleName == "<関数アプリ名>" | extend path = tostring(parse_url(url).Path) | where path endswith "/auth/csrf" or path endswith "/auth/login" | summarize reqs=count(), slowPct=round(100.0*countif(duration>5000)/count(),1), p50=round(percentile(duration,50),0), p90=round(percentile(duration,90),0) by name, resultCode ここで一回止まって、僕がクエリに目を通します。読み取りだけになっているか、対象がちゃんと絞れているか。この確認だけは毎回やっていて、問題なければ叩かせます。KQL が分からなくても、なんとなくは読めますよね(英語力さえあれば…)。引っかかったところは AI に聞けば済みます。 叩くのはこれです。 az monitor app-insights query --app <appId> \ --analytics-query "$(cat q.kql)" \ --start-time 2026-07-20T00:00:00Z --end-time 2026-08-04T08:00:00Z \ --query "tables[0].rows" -o tsv エンドポイントごとに件数と p50 / p90 が並んだ表が返ってくるので、それを見て仮説が当たっていたかを判断します。外れてたら「じゃあインスタンス別でも見たい」と言えばすぐ次が出てきます。 細かいところを補足しておきますね。 クエリは ヒアドキュメントでファイルに落として から食わせています。シェルに直書きするとパイプと引用符だらけで事故るんですよね。ファイルにしておくと、クエリだけ差し替えて何回も回せます。何を叩いたのか?という痕跡をファイルで残すということですね。 <appId> は Application Insights のアプリケーション ID です。ポータルの Application Insights リソースから「API アクセス」を開くと載っています。 -o tsv にしているのは、そのまま次の集計に食わせたいからです。JSON のままだと、AI が読むにも人が見るにも一手増えます。 --start-time と --end-time は KQL 側の between と同じ範囲にしておくのがおすすめです。ずらすと、どっちの条件で絞られた結果を見ているのか分からなくなります。 ちなみに az monitor app-insights query は application-insights 拡張のコマンドなんですが、入っていなければ初回に勝手に入ります。ここは気にしなくて大丈夫でした。 あと、複雑なクエリだと CLI 側が弾いてくることがあります。そのときは「直して」と言えばそのまま通る形にしてくれるので、楽々です。ただ、内容は読んでおいて勉強につなげています。 AI に Azure を触らせる前に、決めておいたこと ここからは運用の話です。 先に言っておくと、 Azure 側の権限をどう設計するかは書きません 。RBAC をどう振るかは組織ごとの運用の話で、僕が「こうしろ」と言えるものじゃないので。 ここで書くのは、AI に触らせる前に こっち側で決めておいたこと です。3つあります。 認証方式は1つに決めておく 僕は az login で自分がログインして、 その資格で AI に叩かせています 。 理由は、とりあえず一番手軽だからです。常設のトークンをどこにも置かなくて済むのもいいところですね。 ただこれ、 権限の縛りやすさと安全マージンを上げたくなったら、選択肢は増えていきます 。サービスプリンシパルとか、マネージド ID とかですね。 このへんも武井さんが書いてくれているので、そちらに譲ります。 もう多分怖くないサービスプリンシパル と 世界一わかりみの深いマネージドID 〜ソースコードから資格情報を追い出そう!!〜 です。僕は今回どちらも使っていないので、名前を挙げるだけにしておきます。 どれを選ぶかは次の注意書きにつながる話なので、そこでもう一度触れます。 どれを選ぶにしても、 先に1つに決めておくこと が大事だと思っていて。その場の思いつきで方式が混ざると、何の権限で何を叩いたのかが後から追えなくなります。 見せる対象を固定する --app で読みにいく Application Insights を1つに固定して、 where cloud_RoleName == "..." で対象のアプリまで絞っています。上に貼ったコマンドにもう入っているやつですね。 これ、 安全の話であると同時に、精度の話でもあります 。 安全のほうは単純で、対象を固定しておけば、AI が見にいける範囲がそのアプリのログの外に出ません。関係ない別チームのアプリのログまで読める状態にしておかない、という話ですね。 で、精度のほうがちょっと厄介でした。1つの Application Insights に複数のアプリが相乗りしていることがありますよね。僕の環境がまさにそれで、調べたい対象の API よりも、まったく関係のない別のアプリのほうがリクエスト件数が多かったです。あと、別のアプリの数字を自分のだと思って読むのが一番まずいですからね。 知らずに数を数えると全部混ざった値が出ますし、見た目は普通の数字なので間違いに気づけません。なので最初の1本は、これを流しておくのがおすすめです。 requests | where timestamp between (datetime(...) .. datetime(...)) | summarize reqs=count(), instances=dcount(cloud_RoleInstance) by cloud_RoleName | order by reqs desc cloud_RoleName ごとの内訳を出すだけのクエリです。1回叩いておけば、以降のクエリにフィルタを入れ忘れなくなります。僕は後から気づいたクチなので、ここだけは先にやっておいてほしいです。 読み取りだけにする 叩かせるのは読み取り系のコマンドだけにしています。 調査でやりたいのは現状を知ることなんで、書き込みが要る場面ってそもそも無いんですよね。無いなら最初から候補に入れない。 で、どうやって守らせているかというと、正直に言うと仕組みでは塞いでいません。 クエリを先に作らせて、実行前に自分で目を通す 。さっき書いたやつですね。あとは Claude Code に読ませる指示ファイル( CLAUDE.md )に方針を書いているくらいです。 なのでこれ、「AI が絶対に書き込めない」状態ではないです。 人が見ているから成立している 、くらいのものだと思ってください。ツール側の許可設定で塞ぐ手もあって、そっちのほうが確実だとは思っています。ここはまだ模索中で、運用フローに載せて正式に導入するところまでは行けていないですね。 注意:権限次第では、リソースを吹き飛ばせます ここは強めに書いておきます。 az は調べるためだけのコマンドじゃないです。 権限が付いていれば、リソースを消すこともできます。 つまり、AI に渡した資格に何が付いているかで、事故ったときの被害範囲が決まります。 上に書いた「読み取りだけにする」は、あくまで こっち側の運用 でそうしているだけで、権限そのものを塞いでいるわけではありません。ここが効くのは権限側です。 なので、 始める前に社内の Azure 運用の担当に確認してください。 どこまでの権限で触っていいのか、そもそも AI に叩かせていいのか。ここは環境ごとに答えが違うので、僕が書けるのは「確認しよう」までです。 さっきの認証方式の話に戻ると、 縛りやすさと安全マージンを上げたければ選択肢は増えます 。僕が az login にしているのは手軽さを取った結果であって、ここが正解というわけではないです。 じゃあ人間は要らないのか、というと逆でした 最後に、一番言いたかったことを書きます。 そもそも最初の KQL は、レビュアーの方が Issue のコメントで投げてくれたものでした。確認項目を並べたうえで、クエリの例まで付けてくれていたんです。 ただ、後から考えると、効いたのはクエリそのものじゃなかったんですよね。クエリは仮説さえ渡せば AI が書いてくれるので、あれが無くても回ったと思います。ありがたかったのは、その前に並んでいた 「何を確認すべきか」 のほうでした。 しかもその方は、コメントの最後にノイズの注意まで書いてくれていました。サンプリングで最初の要求が欠落することがある、といったやつです。App Insights は量が多いと ログを間引くことがある ので、そうなると数え上げた件数がそのまま実数ではなくなるんですね。 この件、僕は最後まで確認していません。全数ある前提で率を出しました。ここは反省点です。 思ったんですけど。 良い仮説を出せるかどうかって、そのシステムをどれだけ知っているかで決まる んですよね。 どんなログを吐いているのか。どういう構成で動いているのか。どこが詰まりやすいのか。それを知っている人間のほうが、確かめる価値のある問いを立てられます。AI はその問いを 確かめる のがめちゃくちゃ速いだけで、問いそのものを出してくるわけじゃない。 だから「AI に読ませる形にしたら人間が要らなくなる」ではないです。むしろ逆で、 仮説を出すところに人間が集中できるようになった 、が正しい。KQL を書く時間が消えた分、何を疑うかを考える時間が増えた感じですね。 書いていないことが3つあります。実際どんなシナリオで何が見つかるのか、どんなログを吐いて customEvents に何を積むべきかは、APM をどう設計するかの世界なのでまた別で。読ませた数字をどう疑うかも、それだけで一本になるので入れていません。GitHub 側でも同じことをやっていますが、認証まわりの事情が違うので今回は Azure だけで区切りました。 まとめ KQL を書けるようにならないまま、Application Insights の仮説検証を AI に任せた話でした。 渡すのは知りたいことと仮説の2つ。KQL に落とすのは AI の仕事です うれしいのは 仮説をそのまま確かめられる こと。当たり外れが表で返ってきて、次の仮説に進めます 生ログではなく集計した表を返させます。クエリが手順書として残るのが効きます AI に触らせる前に、認証方式・見せる対象・読み取り限定の3つを決めておきます 権限次第ではリソースを消せます。始める前に社内の Azure 運用に確認してください 仮説を出すのは人間の仕事のままです。むしろそこに集中できるようになります 最初の一歩は、 cloud_RoleName ごとの内訳を出すクエリ1本です。相乗りに気づかないまま数えると、以降の数字が全部ずれるので。 KQL が書けないから諦めていたログ、けっこうあると思うんですよね。書けるようになるより、叩かせる形を作るほうが早い気がします。 もし間違いを見つけたら、SNS でもメールでも問い合わせください。 ほなまた〜 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post KQL は書けないまま、Application Insights の仮説検証を AI に任せた first appeared on SIOS Tech Lab .
「技術ブログを書け、PVを伸ばせ」と言われたあなたへ ども!1年間AIと一緒に活動してきた龍ちゃんです。 「技術ブログを書け、PVを伸ばせ」 会社からそう言われているエンジニア、いませんか?文章書くのは苦痛で、タイトルの付け方もメタディスクリプションの書き方もマーケの話も全然わからない。 先に、この記事で渡したいものだけ。個別のTipsでも「AIがすごい」って話でもなくて、 文章が苦手なエンジニアでも、ネタ切れせずにブログを続けられる仕組み です。検証から公開までの全工程を、ひとつのパイプラインとして見せます。タイトルもマーケも苦手なまま始められますよ。 これだけ「苦手」と言っておきながら、「この1年でAI活用が一番進んだ業務は?」と聞かれたら、答えはブログなんですよね。書き方も、昔は「やったことを順に書く実装ログ」だったのが、今は「読者のつまずきを起点に、検証結果を証拠として置く」形に変わりました。同じ人間が書いてるのに、です。AI活用の集大成として、今回まとめることにしました。 ちょうど1年前にも 「これが僕の執筆環境です」という記事 を1本書いてるんですけど、そこから工程ごとに道具を足し続けた結果が、今回話すパイプラインです。 大事にしている考え方は2つあります。 検証してから書く 。ネタを探してから書くのではなく、検証した結果が記事になる。だからネタに詰まらない 道具を使い捨てず仕組みに沈める 。便利な使い方をその場限りにせず、次も使えるように残す 順番に説明していきますね。 検証・調査からはじまる、Claude Codeで組んだパイプラインの全体像 まずざっくり全体を見てもらいます。 全工程にAIが居ます。調査もアウトライン作りも公開前のチェックも図もサムネも、どこを切ってもAIが何かやってます。ただ、 執筆だけ役割が反転します 。他の工程はAI主導で人間が確認するイメージなんですが、執筆だけは人間主導でAIが補助に回る。なぜそうなるのか、は後半で回収しますね。 なお、この記事に出てくる research-searcher みたいな名前は、それぞれ役割を持たせたAIに付けた呼び名です(僕は Claude Code の skill とサブエージェントで組んでます)。名前は覚えなくて大丈夫なので、「何をする担当か」だけ追ってもらえればOKです。あと、これ全部を自作しないと始まらない話でもないです。普段使ってるAIに同じ役割を振るだけでも回ります。 もう1つ前置きを。7工程のうち5つ(アウトライン / 執筆 / 公開前チェック / 図・サムネ / タイトル・SEO)は、それぞれ単独の記事で深掘りしてあります。なのでこの記事では、その5工程は「何をする工程で、なぜその形にしたか」と、さらっとしたコツだけを置いておきます。気になる方はぜひ個別記事も読んでみてください。 工程ごとに、上流から歩いてみる 工程は7つ。順に見ていくと、AIへの任せ方が工程ごとに違うのが見えてくると思います。 検証してから書く。だからネタ切れしない ブログの起点は「ネタ出し」じゃないんですよね。「検証・調査」なんです。 「よくネタが尽きないね?」って聞かれることがあるんですが、答えはシンプルで、自分の中に1つルールがあって。 検証した内容を基本的にブログ化する ってことを守ってます。 何か気になる技術があって、実際に手を動かして、「あ、こういう結果になった。これはブログにする価値があるな」と判断してから書く。逆に、ネタだけ考えて実装なしで「仮想のブログ」を書こうと思えば書けるんですけど、個人的にそれをやったら終わりだなと思ってて。技術ブログとして 動くものを提供できる 状態を維持することは、最低限守ることにしてます。 ここで効いてくるのが、 検証する場所と記事を書く場所が同じリポジトリにある 、という構成です。気になったら即AIに調べさせて 「放置案件」を作らない 。 調査の品質を /research で揃えて から書き始めると、書いてる途中で主張がブレなくなる。そうやって手を動かした結果は、 docs/research や docs/experiment にメモとして溜まっていきます。検証と執筆が地続きだから、検証した結果がそのまま記事の素材になるんですよね。 で、ネタの話なんですけど。溜まった検証メモを横断検索すると、「これ1記事になるな」という種が自然と立つんですよ。ここは Skill×サブエージェントで調査を速く回す構成 にした専用のエージェント(調査メモを横断検索する research-searcher や、過去のブログを照合する blog-searcher )に探させてます。だから僕の中に「ネタを探すフェーズ」って実はないんですよ。むしろ逆で、 検証したことがネタになる んだから、検証していないことはそもそも記事にならない。ネタは探すものじゃなくて、検証から生まれるものなんです。そう捉えると、手を動かして検証を続けている限り、「ネタ切れ」という状態そのものが起きなくなるんですよね。「ネタを探す」は「自分の活動の振り返り」に近いかもしれません。 いきなり書かず、アウトラインで勝負を決める ネタが決まったら、次はアウトラインです。誰に・どの検索で届く記事か・どの角度で書くかを、本文を書く前に決めきる工程ですね。少しマーケティング的知識を入れておきます。 書く前に決めきる理由は単純で、「誰に届ける記事か」の設計は執筆より上流でしか機能しないからです。書き始めてから「あ、読者像ずれてたな」となると直すのが本当に大変なので。あと、ここで「うーん、わざわざ書くほどでもないな」となった記事を、1文字も書かずに畳めるのも大きいです。 この工程のコツを1つ。 アウトラインが「方法をN個並べる」形になったら、1記事に詰めるのをやめて分けたほうがいい です。見るのは、並べた方法どうしで読者に要求する思考が同じかどうか。「読者のペインが同じ」はまとめる理由になりません。実はこのシリーズがまさにそれで、最初は1本に詰めるつもりだったのが、工程ごとに読者へ要求する思考が違いすぎて5本に割れました。あと量の目安として、アウトラインは50〜60行で止めてます。超えたら本文を先食いしてるサインで、執筆が「アウトラインの清書」になるんですよね。 アウトラインを先に作ることで、前提知識などが見えてくることがあります。そういった「自分は知っている」けど、みたいな情報に気づくとまたそれも新規のブログネタになるという感じですね。あとは分量が増えたら分割みたいなこともとれるので、時間がなくてもサクッとアウトラインだけ作るのはおすすめです。AIと話して作ればそんなに時間もかからないですしねw そのアウトラインをAIにレビューさせる話(狙う角度を探す担当と、読者の代弁をする担当の2体)は、別記事に書きました。 2026-08-06 技術ブログの手戻りが消える、アウトラインのAIレビュー 執筆は、素材を渡して下書きを作り、最後は自分の目で通す 執筆は、ここまでで溜めた検証メモ・調査・過去記事を素材として渡して、文体を担当するAI(writing-stylist)に下書きを起こさせる工程です。音声入力でしゃべった内容を素材に混ぜることもあります。 ただ1個だけ、あえて仕組みに「落とさない」ところがあって。出てきた下書きを 自分の目で見て「これ、自分が書きそうか?」を判断して直す 。ここはAIに渡さず自分でやります。 正直、執筆の完全分離はできずにAIとけんかしている日々ですね。それでも執筆速度自体は上がっているので結構お勧めな手法です。 自分の文体をどこまで再現できて、どこから中身が捏造されるのか。その実物は別記事にあります。 2026-08-06 技術ブログで自分の文体をAIに再現させて時短を目指す 公開前は1体に全部見せず、観点で割る 下書きができたら、公開前に検査します。 ここで大事なのは、1体のAIにレビュー観点を盛り込みすぎないことです。それをやると観点が混ざって、「全体的によく整理されています」みたいな当たり障りない感想しか返ってこない。なので 観点ごとに別のAIに割って 、1体には1つだけ目的を持たせてます。いま割ってるのは、書いてはいけないことが混ざってないかのリスク検査、辛口の批評、論理の検算です。割ると、各担当が遠慮なく尖った指摘を返してくるんですよね。 ここで効いてるやり方が2つあって。 1つは、 表現の検査を別立てにする ことです。過剰な言い切り、前振りのない唐突な話題転換、「様々な」みたいなぼかし語といったやつですね。これは論理の破綻とは別の壊れ方なので、専用の担当に分けたほうが当たります。あわせて、約物(記号やカッコの揺れ)はAIに判断させず、スクリプトで機械的に正規化してます。揺れてるかどうかは読まなくても分かるので、ここにAIを使うのはもったいないんですよね。 もう1つは、 内部リンクをAIに探させる ことです。公開済みの記事を全部リポジトリに取り込んであるので、「この段落から張れる過去記事ある?」と聞けるんです。自分の記憶で思い出せる過去記事なんて直近の数本だけなので、ここはAIに探させた方が確実です。これは後で出てくる「1リポジトリに集約する」構成の副産物ですね。 検査の中身と、どこまで無人化してどこから人間が裁くのかは、別記事で書きました。 2026-08-06 技術ブログの公開前チェックをAIに任せる:機械が弾き、人が裁く 図とサムネはAIに作らせて、いい出来は貯めて育てる 記事が一通りできたら、図とサムネを付けます。 ここもAIに作らせてます。図は説明したい構造をそのまま渡してHTML+Tailwindで組んでもらい、それをPNGに変換します。サムネは記事のタイトルと内容を渡して生成する感じですね。文章だけで説明していたところに図が1枚入ると、理解のしやすさが結構変わるんですよね。 最初から一発でいい感じに出てくるわけではないので、出来を見て「もうちょっと詰めて」と作り直すループを回します。そして、いい図ができたらそれを references に保存しておく。次に作図させるとき、それが“お手本”として効いてくるんですよね。この記事に載っている図も、全部この流れで作りました。 サムネイルに関しては、HTMLのコードをパターンとして保存しておくことで再利用ができるようになっています。HTML→PNGの撮影もPlaywrightを使用するとAIが勝手に判断してくれて撮影まで行ってくれるので超便利ですね。 なお、図を「作る」ことより残ってた判断は「どこに置くか」なんですが、そっちの話は1本に切り出しました。 2026-08-06 ブログの図は「どこに入れるか」をAIに決めさせる 作り方を具体的に知りたい人はこちら。 Mermaid図をAIで自動生成する — フロー図やシーケンス図を、Figmaで2時間かけてたのが5分に。最初に試すならここから 図解をAIに丸投げして育てる — 気に入った図をreferenceに貯めるだけで、AIの作図が勝手に上手くなっていく仕組みの話 タイトルとSEOは、マーケティングを知らないままAIで補える 正直、文章を作るよりも苦手だった領域です。タイトルの付け方、メタディスクリプション、競合調査。マーケッターの方に聞いても何から手を付けていいか全然分からなくて、最初にAIに任せ始めたのもここからでした。 やってることは2段階で、上位記事のタイトルの付け方を調べさせて相場を掴んでから、自分の記事のタイトルとメタの案を出させる。採るか直すかは自分で決めます。ここがAIに任せやすいのは、 正しさの基準が自分の外にあるから なんですよね。上位に並んでる記事の付け方が相場で、公開後のPVが答え合わせになる。SEOの知識が自分になくても、相場に照らせば案の良し悪しは判断できるわけです。 1つだけ書いておくと、 タイトルを詰めるついでに、各章の見出しも一緒に見直します 。見出しは執筆しながら立てるんですが、書き終わってから読むと、看板に内輪の用語(エラーの内部名とか実装用語)が乗ってることがあるんですよ。それが出たら文言をいじって済ませず、その章が書き手目線になってるサインとして中身の順番から疑います。看板は「困ってる人が打つ言葉」で書きたいので。 正直に言うと、この工程はまだ仕組み化の途上です。「仕組み完成してます!」と書きたいところですが、そうじゃないので正直に書いておきます。 2026-08-06 技術ブログのタイトルとSEOは、書いた後にAIで詰める 公開したら記録する。書いて終わりにしない 公開して終わり、にはしてないんですよ。 ここで効いてくるのが、また「1リポジトリに集約してある」という構成です。公開した記事も、そのPVデータも、同じリポジトリに置いてある。すると PV分析をこのリポジトリの中で回せる んですよね。このブログはどういう読者層に届いて、なぜ伸びたのか。それを分析して、次の記事のアウトライン(企画)に戻す。 ポイントは「作業ログを残す」というより、 後から参照できる資産として記事と分析結果が同じ場所にある ことです。記事の中身も、PV分析の結果も、ひとつながりで手元に残る。 実際これが効いたことがあって。リポジトリの中でPVを分析したら、生成AI系の記事が圧倒的に読まれていて(うちのブログの場合、PVの8割以上がそこ)、その中でも伸びてるのはツール名を題に置いた実用ノウハウ系で、しかも新記事を出さない月は数字が落ちる、というのが見えたんですよ。結論はシンプルで「 Claude Code 系に絞って、新規記事をとにかく出し続けろ 」。で、実際にそのテーマでシリーズを書き始めました。分析と記事と過去ログが同じリポジトリにあるから、「数字 → 次に何を書くか」の判断をそのまま回せるんですよね。 調査メモも記事の素材も過去ログもPVデータも同じ場所にあると、「次に何を書くか」を決めるのに別の場所を探しに行かなくて済むんですよね。正直やってみないと分からないんですが、続けてみると「あ、これか」ってなると思います。 記録と集約は、それぞれ単独記事にしてます。 git logを作業ログにする話 — コミットをAIに書かせるだけで、git logがそのまま作業履歴に。中断からの復帰や週次の振り返りがラクになる 全業務を1リポジトリに集約した話 — 開発も調査もブログも1つのリポジトリへ。やった仕事が消えず「資産」として積み上がる作業基盤の作り方 全工程に共通して効いてる考え方 工程を全部並べてみると、根っこに通ってるものが見えてきます。冒頭で2つ挙げましたが、こうして並べるともう1つ足したくなるので、3つにして書いておきます。 ① 検証が先、ネタは後 ネタを探すのではなく、検証する。検証したことがネタになるんだから、検証していないことは記事にもならない。記事は検証の副産物として立ち上がるもので、この順番にしておくと「ネタ切れ」という発想自体がなくなるんですよね。 ② 便利技は使い捨てず仕組みに沈める 「これ便利だな」で終わらせない。一番手軽なのは、良い結果が出たら reference に保存して次のAIへの参考資料にすること(さっきの図・サムネが育つのがこれ)。もう一歩進めると、繰り返す作業はスキルやエージェントの形にして残す。どちらも「その場限りにしない」という同じ原則で、保存はその入口です。次に使うとき1から作り直さなくていいので、この積み上げがじわじわ効いてきます。 ③ 1体に欲張らず、観点で割る 公開前チェックのところで出てきましたが、1体のAIに全部やらせると観点が混ざります。用途ごとに割った方が、各エージェントが遠慮なく動くんですよね。これはレビューに限らず、他の工程でも同じ考え方です。 AI全部入りでも、最後に残るのは「これ自分が書きそう?」の判断 自分らしさとAIっぽさのせめぎ合い。その摩擦の中で、自分の言葉が研がれていく。 ここまで見てきた通り、検証から公開まで全工程にAIが居ます。執筆にもAIは居ます。文体エージェント(writing-stylist)が下書きを作るし、音声入力で口述することもある。 でも、執筆だけ役割が逆なんですよね。 他の工程はAIが主導して人間が確認する形ですが、執筆は人間が主導してAIが補助に回る。なんでそうなるか。 理由は、AIの出力が「合ってるか」を測る基準が、工程ごとに違うからなんですよね。もちろん他の工程も、最後は人間が見ます。でも図やSEOや調査は、合ってるか外してるかの基準が外側にある。図がちゃんと描けてるか、検索意図に合うタイトルか、事実が正しいか。だから人間は「確認役」で済む。ところが文章の「自分らしさ」だけは、基準が自分の中にしかなくて、誰かに「合ってます」と言ってもらえない。だから執筆だけ、確認じゃなく「主導」に回るんです。 AIが出してきた文章を読むじゃないですか。読んでいると、「これはちょっと違う」「ここは自分ならこう言わないな」って思う部分が出てくるんですよ。そこを直す。その修正の過程で自分らしさが入ってくるんですよね。音声入力でしゃべった言葉も同じで、そのまま入れるんじゃなくて「これは自分が言いそうか?」を確かめながら使う形ですね。 残るのは「文章をうまく書く力」じゃないんですよね。「 これ、自分が書きそうか? 」を判断する力なんです。 まぁ正直、この作業をなくしてしまえば今の3倍ぐらいの量のブログを出せますね。ただ、それはただの量産作業な気がしてしまいます。なんとなく愛着も湧かないですよね。文体と自己が乖離しちゃうというか。割と大事にしている考え方です。 これ、「技術ブログを書け」と言われてるエンジニアへのアンサーでもあって。2つ理由があります。 ひとつは、だから量産する意味がないということです。自分らしさをメッセージとして注入していく作業なので、機械的に数を増やすのとは違うんですよね。感覚としては「自分らしさとAIっぽさのバトル」みたいな感じですw ちゃんとそれなりに疲弊します。 もうひとつは、実装した内容は、実装した自分にしか正しく判断できないということです。検証して動かした結果について「これは本当にこうだったか?」を確かめられるのは、手を動かした本人だけです。だから最終チェックは絶対に人間が要る。 ここが面白いなと思ってて。「いきなり自分らしく書け」って言われても、何が自分らしいのかって分からないじゃないですか。でも「AIが出してきた文章を見て、違うと感じる所を直す」なら、誰でもできるんですよ。生成より添削の方が、自分を出しやすい。文章を書くのが苦痛な自分でもこのパイプラインが回ってる理由は、ここだと思ってます。 何から始めるか、そして正直な限界 まず何から始めるか 「で、何から始めればいい?」と思った方へ。 おすすめは、 苦手な1工程からAIに任せてみる ことです。順番はこんな感じ。 苦手な工程を1つ選ぶ (僕はタイトルとメタディスクリプションでした)。使うAIは普段のもので大丈夫です。ChatGPT でも Gemini でも、Claude Code でも 過去記事があれば、何本かAIに読ませて文体や思想を抽出してもらう (まだ1本も無ければ、ここは飛ばしてOK) AIが出した案を「これ自分が書きそう?」で直す 。気に入らない所を直すだけでOK(生成より添削の方がラク) これだけで1工程は回り始めます。いきなりパイプライン全部を組もうとしなくて大丈夫。苦手なところを1個ずつ潰していくと、じわじわ繋がってきます。 もっと手っ取り早く全体像を相談したいなら、この記事ごと自分のAIに読ませて「マネできそうな所ある?」と聞くのもアリです。状況に合いそうな工程を拾ってくれます。 正直な限界 誇張したくないので書いておきます。 まず、 ネタや「気になる!」は人間発信 です。そこにモチベがないと、そもそも書かない。仕組みは「書こうとした人の作業を楽にする」ものなので、動機まで代替はできないですw あと、 最初から完成形は組めない です。続けてみて初めて「ここ変えよう・ああしよう」が出てくる。この記事で紹介した形も、1年かけて少しずつ固まってきたものです。最初から全部揃えようとしなくていいです。 文章を書く苦痛がゼロになるわけでもないです。相変わらず書くのしんどいですよw マーケ周りの分析もまだ仕組み化の途上で、手動でやってる部分があります。 まとめ:個人で完結する領域だからこそ、AIを試せた この1年で一番腑に落ちたことがあって。同じAI活用でも、本職のコードを書く仕事とブログでは勝手が全然違うということです。 本職のエンジニアの仕事でエージェントを使うとなると、結構ハードルがあるんですよ。後のメンテナンスとか、決定事項を流動的に把握しておかないといけなくて、AIに自由にやらせるというより、ガチガチに縛って使うことになる。 でもブログは違うんですよね。僕個人で完結するコンテンツなので、僕がどう思うかだけで決まる。だから 自分が使いやすいように、好きなだけ環境を整えられる 。 そして、この「自分が使いやすい環境を整える」行為そのものが、AI活用を学ぶ一番の場になったんです。skill の作り方、agent の設計、観点の分け方、みんなここで学びました。本職のコードを書く現場では踏み込みづらかった領域に、ブログという個人完結の場だからこそ踏み込めた。それが一番の収穫だったと思ってます。 パイプラインは人によって違っていいんですよ。「自分はこの工程が苦手」「ここは手放したくない」は人それぞれです。残すべき人間の領分を自分なりに見極めていく、その過程がたぶん大事で。 文章を書くのは相変わらず好きじゃないんですけど、「AI活用が一番できてる業務は何か」と聞かれたらブログだって答えると思います。それがこの1年の一番の発見でした。 ほなまた〜 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Claude Codeと書く技術ブログ:ネタ切れしない仕組みの作り方 first appeared on SIOS Tech Lab .
タイトルとSEO、書いた後で毎回止まってませんか ども!龍ちゃんです。 ブログの執筆自体はサクッと進むんですが、タイトルとメタディスクリプションで毎回手が止まる経験ありませんか。SEOといわれても、じゃあ何をどう詰めればいいのか分からない。結局「まあこれでいいか」で、勢いに任せて投稿しています。 正直に言うと、ここが一番苦手な工程です。コードも本文も書けるのに、マーケっぽい言葉選びとかクリック率の感覚が要るこの仕上げに関しては難しいですね。そこに対するモチベが一切ないのが問題なのですが、せっかく公開するなら読んでほしいですよね。 なので発想を変えました。 苦手な工程こそ、書いた後に丸ごとAIに寄せる。 中身が固まってから、それを検索で届く形に翻訳するだけ。AIが出してくれたものをベースに考える。そうすれば質もスピードも保てます。 今日話すやり方、実はこのタイトル自体、これでAIに出させて決めました。その実物も、後で貼ります。 そもそも、仕上げで「何を磨く」のか 「磨く」って言っても、じゃあ何を磨くのか。ここを取り違えると、書いた後になって「そもそも誰に向けた記事だっけ」と企画からやり直すハメになります。それは執筆中に気づくべきことですね。 前提として、この記事は執筆の 仕上げ 、つまりタイトルとSEOだけの話です。その前段(狙うクエリ・どの角度で書くか・誰に届けるか)は書く前にもう決めてある状態からスタートしますが、その決め方は姉妹記事「 技術ブログの手戻りが消える、AI2体の「書く前レビュー」 」に譲ります。 磨くのは、 もう決まってる「角度」と「読者」を、検索で届く形=タイトルとメタに翻訳する ところだけです。だから、この段階で新しく狙うクエリを探したりはしません。狙うクエリは、ネタと読者が決まった時点で後から自然に立ってるものなので。 実例が早いので、この記事自身の決めごとを出すとこうです。ブログを書き始める前にアウトラインで決めることは以下のような内容です。 読者像 :中身は書けるのに、タイトル・メタ・SEOでいつも手が止まる/後回しにするエンジニア 記事の核 :苦手な仕上げ工程は、書いた後に丸ごとAIへ寄せられる 狙うクエリ :「技術ブログ タイトル AI」「ブログ タイトル 決め方 AI」「生成AI ブログ SEO」 この想定で作られたブログを検索で届く形にするのが、これから話す仕上げです。 苦手な工程だからこそ、書いた後にAIで詰める そもそも、なんでタイトルやSEOが僕にとって難しいのか。正解が数字で見えにくくて、マーケの経験値がいるからなんですよね。単純に、その経験が僕に無いんですよ。 SEO自体についての知識はWEB上に大量に情報があります。調査をしたところ、仕上げのSEOって、実はかなり型が決まってました。文字数、検索結果での見切れ幅みたいに、けっこう測れる型なんですよね。 その型は、具体的にはこんな定石の寄せ集めです。クリックされる語を選ぶ、行動を促す動詞を使う、検索意図と合わせる、主要キーワードは前半に置く。どれも Google公式のタイトル解説 や Backlinko あたりが前から言ってる話で、目新しさはゼロです。目新しくない=型が固まってる。これだけ明確に情報があれば、この情報をセットで渡せばAIに採点ごと任せられます。 やる気は一番出ないのに、ルールは明確。だからこそ、AIが一番得意です。 使うのは2体。 ① competitor-reviewer でタイトルの相場を見て、② seo-reviewer で3案出して、最後に人間が選ぶ。 以下、実際の使い方です。 ① 競合の「タイトルの付け方」を見る — competitor-reviewer 1体目は competitor-reviewer 。検索上位の競合記事を集めて、タイトルの構造・パワーワードの使い方・技術的な深さを分析して、差別化戦略を提案してくれる担当です。まず「何を入れて何を得るのか」を1枚にするとこうです。 ここでちょっと面白いのが、このエージェント、 アウトライン作成時にも使っている んですよ。 企画のときの使い方はこうです。狙うクエリを渡すと、上位記事を読んだうえで「競合が扱っていないトピック=差別化の余地」の表が返ってきます。それを見て決めるのは「書くか書かないか」じゃなくて「どの角度で書くか」。被っていても角度さえズラせば二番煎じにはならないので。実物の表は姉妹記事(書く前レビュー)のほうに貼ってます。 で、今回は同じ道具で、今度は「上位記事はどんなタイトルの付け方をしてるか=相場」を見る。返ってくるレポートは同じで、企画のときは差別化の欄を、仕上げのときはタイトル分析の欄を読んでるわけです。1本の道具を、企画と仕上げで別の面から使ってる、という感じですね。 実際にこの記事を書く前にも回しました。2026年7月時点で返ってきた相場が、こんな感じ。 文字数 :平均36字くらい キーワード位置 :前半に置いてるのは6割 パワーワード :「実践」「徹底」みたいな穏当な語が中心(1本だけ「最強」があったけど、ああいう煽り語は誇張リスクなので反面教師) 数値 :「◯選」「◯倍」止まり。記事固有の実測値を使ってるところは無し で、この相場が分かると、次のタイトル生成が「なんとなく」ではなく競合を分析したうえでの検討になるんですね。 ちなみにここ、相場に合わせるのが正解とは限らないのが面白いところで。平均36字ということは、検索結果で見切れる長さを踏み越えてる競合が半分近くいるってことなんですよ。上位にいるからといってタイトルまで最適とは限らない。なので文字数だけは相場に寄せず、見切れない全角30字前後に抑えます。あとはキーワードを前半に置いて、穏当な語は1個まで。この3つを、そのまま次に渡します。 ② タイトル・メタを3案出して選ぶ — seo-reviewer 2体目は seo-reviewer 。2025年時点で公開されているSEOの情報を集めて作った採点軸(文字数・見切れ幅・タイトルの定石)を持たせてあって、記事を渡すとタイトルとメタディスクリプションを3案、それぞれ違う狙いで出してくれます。こっちも1枚で。 ここで①とつながります。 seo-reviewer は、 competitor-reviewer の出力があると、自分では競合をWeb検索しにいかず、その相場データをそのままタイトル生成に使うんですよ。先ほどの出力をこちらの入力として食べる。AIを1個ずつ叩くんじゃなくて、出力が次のAIに流れていく。この連鎖が組めるのが、エージェントを工程で並べる旨味ですね。 で、実物です。冒頭で「このタイトル自体これで決めた」と言いましたよね。 いまあなたが開いてるこの記事のタイトル 、seo-reviewer に出させた3案から選びました。返ってきたのがこれです。 A(効果・数値) :技術ブログのタイトルとSEOは、書いた後にAI2体で詰める B(課題共感) :技術ブログのタイトルで毎回手が止まる人へ。AIに寄せる仕上げ術 C(学習) :技術ブログのSEO仕上げは、AIエージェントを連鎖させて学ぶ で、ここからが人間のお仕事です。今回の対応は以下のようになります。 タイトル :Aを採用。ただし「AI2体」の「2体」は、タイトルで見ると数字が浮くだけのノイズだったので削った → いまの「〜書いた後にAIで詰める」 メタディスクリプション :A案(80字)をベースに採用。ただそのままだと「競合を調べる担当」「3案を出す担当」っていう運用の具体が足りなかったので、そこを足して少し長く書き直した(このページの説明文がそれ)。推奨レンジは超えたけど、前半で要点が伝わるならと判断して伸ばしたところ 推奨のAに乗りつつ、余計な一語だけ削った。たったこれだけ。AIが出すのは3案と採点=叩き台と型で、乗るか・削るか・書き直すかは人間の判断です。ベースとして採用して、新規の案として構築してもよいし、気になる点があればディスカッションにそのまま持っていけます。 つまり、「考える」から「選ぶ・修正する」へ変わったということですね。あとは感覚の問題です。こんなタイトルなら「見たことあるか~」とか「お!いいじゃん」みたいな感じで選択するのは、作るよりも圧倒的に楽ですよね。 まとめ:苦手な工程は、仕上げでAIに寄せる タイトル・メタ・SEOは知らない人からすれば一番苦手な工程。でも、そこはもう型が決まってます。だから書いた後に、competitor で相場を見て、seo に3案出させて、人間が選ぶ。あの「まあこれで」で雑に付けて後悔する時間が、まるごと消えます。 ただ、正直に限界も。AIが出すのは「型と叩き台」です。どれを採るか、どこを削るか、順位が上がるかどうかは保証してくれない。このタイトルだって、推奨から「2体」を削ったのは僕です。最後に決めるのは、やっぱり人間です。あとは、データ取って伸びるタイトルの分析とかもやったらより精度が上がると思います。継続メンテですね…… 今日話したのは、執筆全体の中の最後の1工程です。書く前は 書く前レビュー 、検証から記事化までの全体の流れは この記事 にまとめてます。今日は2体でしたけど、 エージェントを何体も並べて記事ごと書かせる話 も別で書いてるので、気になる方はそちらも。 ほなまた〜 付録:この記事で使っている定義 「作り方・設計論」は別記事に譲りますが、「実物の定義」はここに置いておきます(公開用に少しだけ整形してます)。雰囲気とスコープの参考にどうぞ。 1. SEOエージェント seo-reviewer --- name: seo-reviewer description: 技術ブログ記事のSEO価値を評価し、タイトル・メタディスクリプション案を3パターン生成します。SEOタイトルの定石(社内採点軸SPARK=実在の公式規格ではない符丁)と文字数・ピクセル幅最適化に沿って評価。結果はファイル出力します。 tools: [Read, WebSearch, WebFetch, Write] model: sonnet --- # SEO Reviewer Agent 技術ブログ記事のSEO価値を評価し、最適化されたタイトル・メタディスクリプションを生成するエージェント。 ## 評価プロセス(要点) - Phase 1: 記事内容の取得(ファイル/URL/直接) - Phase 2: 競合データの取得 - 外部から競合分析データが渡されている場合は、それを使い Web Research はスキップする - 渡されていない場合のみ、簡易的な検索トレンド調査を行う(深い分析は competitor-reviewer を推奨) - Phase 3: SEO価値の評価(100点満点:タイトル/見出し/メタ/内部リンク/検索意図) - Phase 4: タイトル・メタを3パターン生成 - パターンA: 効果重視・数値訴求型 - パターンB: 課題共感・解決提案型 - パターンC: 技術トレンド・学習促進型 - Phase 5: レポートを review/seo-{日時}.md に出力 ## 評価原則 - 2025年版SEO基準の厳守(SPARKの適用・文字数/ピクセル幅の最適化・AI検索時代対応) - タイトルは記事内容を正確に反映(ミスリード厳禁) - 3パターンは明確に異なるアプローチにし、人間が選べる形にする 2. 競合調査エージェント competitor-reviewer (企画にも仕上げにも使う) --- name: competitor-reviewer description: 競合記事のタイトルとコンテンツを分析します。検索上位記事の構造・パワーワード・技術的深さを評価し、差別化戦略を提案します。結果はファイル出力します。 tools: [Read, WebSearch, WebFetch, Write] model: sonnet --- # Competitor Reviewer Agent 競合記事の**タイトルとコンテンツ**を分析するエージェント。 ## 使うべきタイミング - 記事執筆の最初(競合を把握してから書く)=企画:差別化の余地を見る - 記事公開前(差別化ポイントの最終確認)=仕上げ:タイトルの相場を見る ## 評価プロセス(4段階) - Phase 1: 分析対象の把握(記事タイトル・H2・技術スタック・メインキーワード) - Phase 2: 競合記事の収集(WebSearchで各クエリ5〜10件 → WebFetchで上位5件を精読) - Phase 3: 分析(タイトル:文字数・キーワード配置・パワーワード・数値/コンテンツ:網羅性・深さ・独自性) - Phase 4: レポートを review/competitor-{日時}.md に出力 ## 出力形式(要点) - タイトル分析(競合一覧+サマリー) - コンテンツ分析(記事ごとの強み・弱み) - 全体傾向(共通パターン / **競合で扱われていないトピック=差別化の機会**) - 差別化戦略(タイトル戦略・コンテンツ戦略) ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 技術ブログのタイトルとSEOは、書いた後にAIで詰める first appeared on SIOS Tech Lab .
ども!ブログを書くとき、AIにいろいろ手伝わせている龍ちゃんです。 原稿を書き終えて、公開ボタンを押す直前。「これ、本当に出して大丈夫かな」ってなること、ないですか。会社の技術ブログだと特にそうで。顧客の悪口に読めないか、うっかりAPIキーやコードの断片が混ざってないか、そもそも中身が空っぽじゃないか、論理が飛んでないか……気にし出すとキリがない。かといって、全部を毎回自分の目で見るのは、正直しんどいんですよね。 だからAIに任せたい。でも、やってみて分かったのは、全部は任せられないってことでした。じゃあ、どこまで無人で回せて、どこから人が要るのか。観点で分けたら、その線がはっきりしたんです。公開して大丈夫かのチェックは0か1かで裁けるから、機械に丸投げできる。でも文章の良し悪しは、点数までは出せても採否は人が握るしかない。この記事は、その「分けたら人間の作業がどこまで減ったか」を、実際に自分のこの原稿を検査させた生の出力を貼りながら話します。しかもこの原稿、下書きの段階でAIに検査させたら、けっこうボロクソに言われました。そこからどう直したかまで、正直に出します。 観点を分けると、機械に任せられるかで割れる 先に結論を言っちゃいます。公開前チェックは、観点で分けると2種類になります。 公開して大丈夫かを見る検査 と、 文章そのものが良いかを見る検査 です。そしてこの2つは、機械にどこまで任せられるかが決定的に違うんですよね。図にするとこうです。 前者(この記事では「リスク検査」と呼びます)は、機械的に弾くタイプ。顧客への配慮とか、情報漏洩とか、そういう「アウトかセーフか」をほぼ0か1かで判定する。後で書きますけど、うまく作ると人間がほとんど手を入れずに回せます。 後者(「批評」と呼びます)は、評価して指摘を返すタイプ。「この文章、そもそも良いのか」を見る。こっちは辛口係と論理係の2体いて、原稿に点数をつけて「ここがダメ」って返してくる。でも0か1かじゃない。返ってきた指摘を採用するかどうかは、最後は人間が決めます。 分ける理由はシンプルで、目的をはっきり切り分けたかったからです。ごちゃ混ぜのまま「いい感じにチェックして」って投げると、後で書く「過剰検出」にすぐハマる。だから最初に「何を見る検査なのか」で割っておく。 あと共通点をひとつ。この検査、どれも「固定の基準」で動いてます。前に別の記事で、自分の文体を覚えて「育っていく」 エージェントの話 を書いたんですけど、今回の検査は逆で、育ちません。毎回、同じ基準で同じように弾く・評価します。検査の目的は固定されるべきなので、「昨日OKだったものが今日はNGになる」だと信用できないですよね。「いつ当てても同じ判定が返る」から、安心して任せられる。 ここで1つだけ補足を。「育たない」というのは、エージェントが勝手に学習しないという意味です。基準そのものが永久に固定という話ではなくて、足りない観点は僕が外から手で書き足します。中身は不変、ルール集合は人が更新する。この2つを分けておくのが大事で、後半でまた出てきます。 じゃあ、その2つを順番に。まず機械に丸投げできるほうから。 機械に丸投げできる検査:公開して大丈夫かを0/1で通す リスク検査が見てるのは、ざっくり2種類のリスクです。ひとつは顧客への配慮(競合をディスってないか、顧客名や案件名が漏れてないか)。もうひとつは法務まわり(効果を言い切りすぎてないか、シークレットやコードが混ざってないか)。じつはこの2つ、担当エージェントも分けてます。顧客配慮の係と法務の係で別々。ひとつの検査に混ぜると観点がぼやけて、これから話す「過剰検出」に一直線なんですよね。だからリスク検査の中でも、さらに割ってます。順に見せますね。 顧客配慮の係:他社と顧客への配慮を守る 1体目は顧客配慮の係。競合をディスってないか、顧客名や案件名が漏れてないか、取引先との関係性が透けてないか。そういう「取引先や他者への配慮」を見ます。 でもこの手の検査、作るとすぐ「過剰検出」で使い物にならなくなるんです。 もしあなたがAIレビューを試して「指摘が多すぎて捌けない」と思ったことがあるなら、原因は同じで「観点を絞っていないこと」です。 少しでも怪しい表現を全部拾うと、指摘が何十件も返ってきて、9割が誤検出。ノイズに埋もれて本物が見えない。結果、使われないものになりかねないですね。 実際、最初に作ったときがそうでした。ある記事に当てたら89件も指摘が返ってきました。でも中身を見たら、「これは公開情報だからセーフ」「これは自分の体験談だからセーフ」「これは仮想の例だと断ってあるからセーフ」ってのばっかり。たとえば「〇〇社のサービス」って書いただけで「顧客名の漏洩では?」と拾ってくる、みたいな。でもそれ、公式サイトに載ってる公開情報なんですよ。本当にヤバい「High」は、89件のうちたった1件でした。 なのでこの係には、指摘を出す前に「これはセーフ」を先に判定させる軸を噛ませてます。公開情報か、体験談のトーンか、仮想の例だと明示して打ち消してあるか。これで過剰検出を潰してから、残ったものだけ出す。無人で0/1で回すには、この「先にセーフを弾く」が命なんですね。自分の文体の癖を毎回違反扱いされたら、それこそ捌けないですからね。指摘を増やすより、ノイズを減らすほうが重要です。 法務の係:漏洩と言い切りを止める 2体目は法務の係。効果を言い切りすぎてないか(「絶対に動く」みたいな 景表法リスク )、シークレットやコードが混ざってないか、を見ます。この係の特徴は、見る範囲をわざと狭くしてること。法務リスクと表現すると、9種類くらい出てきました。(ライセンス違反とか画像の著作権とか商標とか特許などなど)でもこの係はそのうち3つ(効果の言い切り・シークレット・コード混入)しか出しません。残りは「リスクは本物だけど、本文を読むだけじゃ判定できない」情報です。画像の著作権なんて、本文の文字を見ても分からないですからね。無理に判定させると誤検出が増えて、さっきの過剰検出に逆戻りする。だから「本文だけで裁けるもの」に絞る。欲張らないのが、無人で回すコツでした。判定できないものを無理に見させると、実際には無い違反を「ありそう」で拾ってくるんですよね。空想で弾かれるのが一番タチが悪くて、原稿を見返しても直す場所が無いんです。 で、これがどう動くか。安全な記事に当てても、当たり前ですけど「問題なし」しか返ってこないんですよ。それだと記事にならないので、今回はわざと汚した検体を用意しました。この記事の原稿に、記事とはまったく関係ないAPIキーとコードの断片をこっそり紛れ込ませて、当ててみます。返ってきたのがこれ(キーの値はこっちで伏せてます)。 カテゴリ 該当箇所 理由 シークレット漏洩 + コード混入 ANTHROPIC_API_KEY = 'sk-ant-api03-****' sk-ant-api03- はAnthropicのAPIキーの実フォーマットそのもの。しかも記事の本題と無関係なコードに紛れてる シークレット漏洩 + コード混入 channelSecret: '1a2b****' LINEのシークレットとして通用する形式が平文。本物か検証できなくても「本物に見える」時点でアウト シークレット漏洩 + コード混入 INTERNAL_ENDPOINT = 'https://staging-internal.****' 社内の検証環境っぽいURL。システム構成が漏れる ちゃんと捕まえてくれました。「本物に見えるシークレットが埋まってる」「本題と関係ないコードが混入してる」って。僕は原稿の地の文に「これはわざと汚した検体で、たまたま転がってたコードを貼っただけ」って言い訳を書いといたんですよ。なのに検査のほうは「その打ち消しは直後で揺らいでて、読者からは本物と区別できない」って、言い訳ごと検知されました。うっかり混ぜたコードを公開前に人力で見つけるの、大変なんですよね。これを機械が0か1かで拾ってくれるのは安心です。 人が採否を握る検査:中身が空疎か・論理が通ってるかを見させる さっきのリスク検査と決定的に違うのは、こっちは0か1かじゃないってこと。点数をつけて「ここがダメ」を返してくる。で、その指摘を採用するかは人間が決める。なんで人間が決めるかは最後に書きます。 批評は2体いて、役割をはっきり分けてます。 この2体、実は前に一度書いてます。 1体のAIに欲張って失敗して、3エージェントに分けた話 。当時はセミナー資料をレビューさせてて、「なぜ分けたのか」が主題でした。今回はブログの公開前チェックで、その手前にもう一段、リスク検査と批評という2性格の割り方を置いた話です。役割分担そのものの経緯はあっちに書いてあるので、ここでは公開前にどう効くかだけ。 まず1体目の辛口係から。 辛口係:看板・つながり・死んだ言葉・離脱を見る こいつは名前のとおり徹底的に辛口で、「この原稿は0点」から始まります。AIが書いた、一見それっぽいけど中身スカスカの資料、っていう前提で疑ってかかる。見るポイントは4つあって、看板(タイトル)と中身がずれてないか、話のつながり(なぜなら・だから)が壊れてないか、「死んだ言葉」を使ってないか、あと読者が何段落目で飽きて離脱するか。 この「死んだ言葉」ってのが個人的にお気に入りで。「最適化」「相乗効果」「伴走支援」みたいな、それっぽいけど何も言ってない言葉を名指しで暴いてくる。自分でも無意識に使っちゃうんですよね。AIだとメタファーが先行で、出した言葉が回収されることなく突っ走ることがありますね。 で、その辛口係に、この記事のラフ稿を当ててみました。結果、 38点 。指摘の一部がこれです。 この記事が本質的にやろうとしているのは「うちの検査エージェントには2つの性格があってね」という 内輪の設計自慢 である。悩んでいる読者は、あなたの家の間取りを聞かされても自分の家は片付かない。 ……ぐうの音も出ない。じつは初稿は、この「性格が2つあってね」っていう「仕組みの紹介」から入ってたんです。それを刺されて、いま読んでもらってる「で、結局どこまで人の手が減るの?」という読者の得の側から入る形に組み直しました。あと、当時のラフ稿は肝心の実出力がまだ空欄で、「捕まえてくれました」みたいな感想だけ先に書いてたんですけど、そこもこう刺されました。 出力を見せずに、リアクションだけ先に書いてある——これは芸人が「いやー面白かったですね今の」とだけ言ってネタを一つもやっていない状態。 うまいこと言うな、と思いました。おかげで今この原稿は、感想の前にちゃんと実出力を貼る順番に直してあります。 論理係:論理の破綻だけを見る もう1体が論理係。名前のとおり、論理だけを見ます。辛口係が「言葉が死んでる」とか「読者が飽きる」とか、言葉から読者の気持ちまで殴ってくるのに対して、論理係は論理の破綻だけに絞ってる。前提から結論への飛躍、途中で言葉の意味が変わってる、掲げた看板を回収してない、依存関係の抜け、内部矛盾。この5種類だけ。表現の good/bad とか、情報が古いとか、そういうのは一切見ません。 なんでこんなに狭くしてるかというと、辛口係と役割を分けたかったからです。辛口係に全部やらせると、2周目3周目で「表現も論理も鮮度も」って欲張って、指摘がどんどん膨らんでいく。だから論理係は「論理だけ」に削って、そこだけは何度当てても同じ精度で返るようにしてある。 これも当時のラフ稿に当ててみたら、痛いところを突かれました(引用の中の行番号は、当時の原稿のものです)。 内部矛盾:19行目「(エージェントは)育ちません。毎回同じ基準で」と、79行目「判定軸を貯めていけばもっと無人化できる」が矛盾している。同じ「判定軸」に”不変”と”蓄積”の両方を与えている。 言われてみればその通りで。「育たない」って言い張っておきながら、後半で「判定軸を貯めれば」って書いてたんですね。なので直しました。正確には、エージェント自体が勝手に学習するわけじゃなくて、判定軸のルールは僕が外から手で足していく。エージェントの中身は不変、ルール集合は人が更新する。そこを分けて書くようにしました。同じ批評でも、こういう「文の辻褄」だけを正確に刺してくるのが論理係です。 ちなみにこの「原稿を丸ごと読ませて、読者がどこで離脱するか探す」って動き、辛口係の4つ目の観点なんですけど、実は「図をどこに置くか」を決めるのにも同じ手が使えます。息が詰まる所に図を1枚、みたいな。これはまた別の記事で書きます。 で、この4体はどう呼んでるのか ここまで4体を紹介してきましたけど、実際は1体ずつ名指しで起動してるわけじゃないです。入口は1つで、そこに何をしてほしいかを喋るだけ。 「公開前チェックして」と言えばリスク検査の2体が並列で立つ。「辛口で」なら辛口係、「論理だけ見て」なら論理係。言い方のどこかに引っかかった係が勝手に立ち上がる仕組みにしてあります。だから毎回「今日はどの係を呼ぶか」を考えなくていい。ここが地味に効いてて、考えることが増えると人はやらなくなるんですよね。 出てくるものは種類で分かれてます。リスク検査と批評のうち、後で読み返すやつはファイルに書き出させて、その場で潰すやつは会話に返させる。原稿を直しながら見比べたいので。 ただし、 並列で立っても統合はさせません 。4体の指摘を1つのレポートにまとめさせると、そこで「どれが重いか」の判断がAI側に移ってしまう。突き合わせて採否を決めるのは自分の仕事なので、出力はバラバラのまま受け取ってます。 どこまで任せ、どこから人か 最後に、正直な現在地を。 リスク検査のほうは、かなり人手なしで回せてます。さっきの「先にセーフを弾く」判定軸のおかげで、返ってくる指摘がほぼ本物だけになった。0か1かで判定できるから、機械に任せきりでも困らない。 でも批評(辛口係と論理係)のほうは、そうはいかない。指摘は返ってくるんですけど、それを採用するかどうかは、今も僕が一件ずつ判断してます。 実はこの記事がいい例で。さっき辛口係に「38点・内輪の設計自慢」って言われたって書きましたよね。あの指摘を受けて、AIと殴り合いです。「もっと読者が持ち帰れる手順書にしろ」って言われた部分は、半分だけ採りました。この記事はハウツーじゃなくて「僕が実際どう回してるか」を正直に書く記事なので、全部ハウツーに書き換えたら別物になっちゃう。でも「過剰検出であなたも困ってるはず」ってところだけは、確かに読者に橋を架けるべきだと思ったので、そこは追加執筆です。逆に論理係の「内部矛盾」は明確な間違いだったので即採用。こんなふうに、採用・部分採用・不採用を仕分けてます。 というのも、批評の指摘って、たまに的外れなんですよ。それっぽく減点してくるけど、「いや、それはわざとこう書いてる」ってやつが混ざる。だから複数の批評の指摘を全部そのまま飲むと、記事が批評好みに矯正されて、逆に自分の色が消える。どれを採ってどれを捨てるかを決めるのは、人間の仕事なんです。 なので今のところ、こういう分担です。「公開して大丈夫か」のリスク検査は機械に任せる。「良い文章か」の批評は、機械に叩き台を出させて、採否は人が決める。リスク検査のほうは、僕が判定軸を手で足していけばもっと無人化できると思ってます(さっき書いたとおり、育てるのはエージェントじゃなくて人の側です)。批評のほうは……まだ人ですね。いつか任せられる日が来るのかもしれないけど、今はまだ、機械に弾かせて、評価は自分で決める、で回してます。 このシリーズでは、 書く前のレビュー や、 自分の文体で書かせる話 、 公開前の仕上げ も別記事で書いてます。あわせてどうぞ。「検査の指摘を最終的に採るか捨てるかは人間がやる」っていう、この記事の裏にある考え方は こちらの記事 にまとめてあります。 付録:この記事で使っている定義 「作り方・設計論」は別記事に譲りますが、「実物の定義」はここに置いておきます(公開用に少しだけ整形してます)。本文では読者向けに「リスク検査」「批評(辛口係・論理係)」と呼びましたが、中身は Claude Code の サブエージェント で、それぞれ以下の定義で動いてます。雰囲気とスコープの参考にどうぞ。 1. リスク検査(顧客配慮+法務の2体) 「先にセーフを弾く」判定軸で過剰検出を潰してから、本文だけで裁けるものだけを 0/1 で出す。この設計が本体です。顧客配慮と法務で担当を分けてます。 --- name: risk-customer-reviewer description: ブログドラフトを「顧客配慮リスク(A群)」観点だけで読み取り専用レビュー。取引先・他者・社会への配慮を欠く表現を検出し、原文引用+理由+改善案を表で報告する。 tools: [Read, Grep, Glob] model: sonnet --- # 顧客配慮リスクレビュー Agent(A群) 顧客配慮リスク(A1〜A7)だけを検査する読み取り専用レビュアー。守りたいのは ①他社を貶めない(A1 特定企業dis / A2 当てこすり) ②内部の案件を漏らさない(A3 顧客名 / A4 プロジェクト名 / A5 関係性 / A7 個人情報)。 A6 業界マウントは「明確な見下し」のみ。 ## 判定軸(誤検知抑止・検出より先に適用) 機械的検出をそのまま使うと過剰検出になる(baseline: 89検出 → 真のHighは1件)。以下を先に適用し、該当は検出しない。 1. 公開情報(会社サイト・公式GitHub・公開メアド等の再掲) 2. 個人体験談トーン+比喩(一人称主語の体験・感情・比喩は製品名を含んでも許容) 3. 公開済み内部資産(公開リポ化済みのプロジェクト名) 4. 仮想化明示+打ち消し(「仮想シナリオ」と明示。ただし直後に実名・実環境が続けば無効) 5. 一般化された役割名(「先輩社員」「マーケの担当者」) ## 出力 検出時のみ「# / カテゴリ / 重要度 / 原文引用 / 理由 / 改善案」の表。無ければ「リスクなし」。読み取り専用(ファイルは変更しない)。 --- name: risk-compliance-reviewer description: ブログドラフトを「法務コンプラリスク(B群)」観点で読み取り専用レビュー。本文だけで判定できる3つ(B1 効果断定 / B7 シークレット開示 / B8 コード混入)に絞って検出する。 tools: [Read, Grep, Glob] model: sonnet --- # 法務コンプラリスクレビュー Agent(B群) 検出対象は本文中の証拠だけで裁ける3つだけ(ADR-007)。 - B7 セキュリティ的開示:本物のシークレット・内部ホスト名・.internalドメイン・APIキー形式(sk-ant-api03- 等) - B8 コード混入:記事テーマと無関係な import/関数/URL/placeholder の残存 - B1 効果断定:「100%安全」「絶対に動く」等の景表法リスク 検出しないもの:B2 誇大・最上級(「最強」等のテンション/文体)、B3 ライセンス・B4 画像著作権・B5 商標・B9 特許(本文だけでは判定不可=ノイズを出さない)。判定軸(公開情報 / 公開済み内部資産 / 仮想化明示+打ち消し)で過剰検出を抑える。APIキー混入は B7+B8 併記。読み取り専用。 2. 批評・辛口係 harsh-review-agent --- name: harsh-review-agent description: AI生成っぽい資料を辛口で断罪するレビュー。0点起点で「死んだ言葉」「ストーリー崩壊」「聞き手の離脱点」を4観点で検出する。 tools: [Read, Glob, Grep] model: opus --- # Harsh Review Agent - プロ審査員による辛口レビュー 対象は「一見整っているが中身が空っぽな資料」だと仮定してかかる。称賛は禁止、0点起点で「なぜ0点か」の証拠を挙げる。 ## 4観点 1. 看板(タイトル)と中身の不一致:見出しが約束した内容を直下で果たしているか 2. ストーリーの崩壊:なぜなら・だから・一方での展開になっているか、自己矛盾はないか 3. 死んだ言葉の検出:「最適化」「相乗効果」「伴走支援」等、何を隠す逃げ口上かを暴く 4. 聞き手の感情シミュレーション:どこで離脱するかを箇所+理由で特定 ## 出力 総合点(100点満点)+致命的欠陥+死んだ言葉+離脱ポイント+具体的改善命令(動詞で)。読み取り専用。 3. 批評・論理係 logic-reviewer --- name: logic-reviewer description: 資料の論理的整合性のみを評価。100点起点で、論理破綻の5類型だけを検出する。表現の好み・鮮度・物理限界・感情シミュレーションは対象外。 tools: [Read, Grep, Glob] model: sonnet --- # 論理レビュアー 論理的整合性のみを見る論理監査人。100点起点。次の5類型だけ検出し、それ以外は出力しない。 1. 前提と結論の接続失敗(推論の飛躍・論拠の欠落) 2. 用語の意味変動(同じ語が途中で別の意味に) 3. 回収されない看板(冒頭の約束が本文で果たされない) 4. 依存関係の欠落(外部参照が読者に渡されていない) 5. 内部矛盾(相反する主張の両立) 表現の好み・バズワード・鮮度・物理限界・ペルソナの感情描写は対象外(減点しない)。破綻がなければ潔く100点。読み取り専用。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 技術ブログの公開前チェックをAIに任せる:機械が弾き、人が裁く first appeared on SIOS Tech Lab .
文章は書けるのに、図で手が止まる ども!龍ちゃんです。 文章は書けるんですよ。でも図が。 ブログって図があった方がいいですよね。技術ブログでも、何のブログでも。そこは疑ってなくて、あった方がいいのは分かってるんです。ただ、Figmaとかで作るのも図を検討するのもなかなか時間がかかります。 なので、書き終わってから記事を眺めて「なんかここ、文字ばっかりだな」と思ったら1枚差し込む。あるいは面倒で入れないまま出す。そんな感じでブログの装飾をつけていました。 AIを使うようになってから、この辺が大きく変わりました。図を 作る のはAIでできるようになったんですよ。HTML( Tailwind )で組ませてPNGにするところまで自動化してあるので、作るのは早い。最近はこれに加えて「どこに追加する?」の判断もAIに移譲して爆速で図解を作っている感じです。 今回は、ブログの図解関連の判断をちゃんとしたプロンプトに変えて、 図の要否と置き場所をAIに判断させている 話をします。実際に3本の記事に当てた出力も貼ります。 この記事で話すのは「どこに入れるか」だけ 埋めたいところは本当は2つあって、「どこに入れるか」と「どう作るか」なんですよね。ただ、作り方についてはもう別で書いてるので、今回は触れません。 Claude Code SkillでHTML図解を自動生成 — HTMLとTailwindで図を組んでPNGにする話。この記事の図もこの方式で作ってます 図解作成、AIに丸投げしたら「たまに自分より上手い」件 — 気に入った図をreferenceに貯めて、AIの作図を上手くしていく話 作る側はここまで来てるので、残ってるのが「どこに入れるか」だけなんですよね。 あと1つ、隣の話と切り分けておきたくて。「文字の塊が読みにくい」って話だと、たいてい改行や段落分割の話になりますよね。あれとは別の問題を扱ってます。 改行は文の長さの問題を解くもので、図は概念の並列関係の問題を解くもの です。「AとBがあって、Aはこうで、Bはこうで、しかもAとBは逆向き」みたいな構造は、改行をいくら入れても畳めない。そこは図の仕事だと思ってます。 で、この記事で渡すものは2つです。 判断のフローと、具体的なプロンプト 。 図にすると4段ありますが、この記事の取り分は①②です。③④は上のリンク先の話になります。 記事を丸ごと読ませて、息が詰まる所を探させる 使っているプロンプトはこれです。 この記事を全文読んで、図を1枚入れるべき箇所があるか見てほしい。 - 文字の塊が続いて息が詰まる所、概念が並列に並んで文章だと追いにくい所を探す - 見つかったら「どの見出しの直後か」「何を描く図か」を提案する - 図が無くても読める所には提案しない。最小限でいい - 入れる必要がなければ「不要」と返してくれていい。数を揃えなくていい ※ すでに入っている図( の行)は考慮して、重複する提案はしないこと。 意図してるのは箇条書きの後半2行です。AIって基本的に何か出してくれるんですよね。親切なので。「図の場所を提案して」と頼めば、頼んだ数だけ出してきます。でも図って、増やせばいいものじゃないですよね。要らない図が3枚入った記事って、文字の壁が図の壁に変わっただけなので。 なので**「無くても読める所には提案しない」「数を揃えなくていい」を先に書いておく**。これで、こっちが図を捨てる作業をしなくて済むんじゃないかと思ったわけです。 ちなみにこの記事、シリーズの他の記事だとエージェントを紹介してるんですが、これはエージェントじゃなくて単発のプロンプトです。定義ファイルも作ってません。1回投げるだけで済むので、そのままにしてます。(内容的にスキル化もありですね!) あともう1つ。「記事を読ませて離脱しそうな所を探す」という動きは、 公開前チェックの記事 で書いた辛口係の4つ目の観点とほぼ同じなんですよね。あっちは読者がどこで離脱するかを特定して断罪してくるやつです。動きは同じで、返ってくるものが「断罪」か「図の置き場所」かの違いだけ。今回は後者の話だけします。 実際に3本の記事に当ててみた 自分の記事3本に、上のプロンプトをそのまま投げました。当てたのは全部、図がすでに入っている記事です。 対象 行数 既存の図 ブログ執筆パイプラインの記事(下書き) 287行 4枚 公開前チェックの記事 234行 5枚 文体の記事 153行 3枚 図が3枚から5枚入ってる記事たちなので、正直「もう十分です、不要」が返ってくると思ってました。 返ってきたのは、どれも1枚だった 3本とも「1枚」でした。「不要」は一度も返ってきませんでした。 たとえばパイプラインの記事だと、こう返ってきました。 位置 : オチの節の「理由は、AIの出力が”合ってるか”を測る基準が…」の段落の直後 何を描くか : 「正しさの基準がどこにあるか」で人間の役割が変わることを示す2列比較図。左=他の工程(基準は外側にある→AI主導・人間は確認役)/右=執筆(基準は自分の中にしかない→人間主導) 根拠 : この記事で最も長い純テキストの連続です。約20行がすべて散文で、コードブロック・箇条書き・表が一切入りません(他の節は必ず何かで切れています)。しかもここは記事の回収部=一番読み飛ばされたくない場所です これ、当たってるんですよね。しかも「他の節は必ず何かで切れている」という比較までしてる。記事全体を読んだからこそ言えることで、部分だけ見せてたら出てこない指摘だと思います。 で、言われたとおりに作ったのがこれです。 左右の対比も、下に置く一行も、提案文にそのまま書いてあった通りです。僕がやったのは、指定された構造をHTMLで組んでPNGにしただけ。この図はいま、提案された位置(パイプラインの記事のオチの節)に入ってます。 作る手間はもともと軽いので、こうやって「どこに・何を」まで決まってしまうと、図を入れるのがほぼ作業になるんですよね。迷ってる時間がなくなるのが一番でかいです。 残りの2本も同じ形でした。「この節だけ図がなくて、内容が純粋な並列マッピングだから読者は頭の中に対応表を作らされる」「既存の図2枚の間隔が空いていて、その区間だけ休符がゼロ」。どれも位置と理由がセットで返ってきます。 頼んでいないのに、捨てた理由が返ってきた 面白かったのはここです。3本とも、 提案しなかった箇所とその理由 を勝手に付けてきました。 プロンプトに「見送った所も書いて」なんて一行も入れてません。それでもこう返ってきます。 提案しなかった箇所(検討して見送り) アウトラインの節: 45行と長めですが、実物のコードブロックと5項目の箇条書きで十分に割れています。図にすると箇条書きの再描画になるだけ 公開・記録の節: PV分析が次の企画へ戻るループは図向きの構造ですが、既存の全体図のaltが既に「PV分析が次の企画へ循環する」と明言済み=重複 設計原則の節: 並列3項目ですが、太字ラベルで既に視覚的に分かれており、各項目2〜4行。図は装飾になります 「図にすると箇条書きの再描画になるだけ」とか、割と厳しいこと言ってきますよね。 別の記事では「この段落は体験の語りなので、構造化すると体温が落ちる」「引用ブロックの並置がすでに図の役割を果たしていて、図にすると実出力の生々しさが落ちる」というのも返ってきました。 体温が落ちる とか 生々しさが落ちる とか、こっちが普段使ってる感覚の言葉です。それを理由に「触らない方がいい」と言ってくる。 これが毎回ほしい場合は、プロンプトを改修すれば毎回出してくれるというのが良いポイントですね。 つまり、こっちが捨てる作業をしなくて済んだんですよね。候補を10個並べられて「どれにします?」と聞かれるのが一番しんどいわけで、そこを向こうがやってくれてる。 なぜ「出さない」の判断をAI側に置くのか 世の中のプロンプト例を見ると、だいたい「この記事から図を入れるべき箇所を3つ挙げて」という形なんですよね。数を指定するやつ。 あれをやると、記事に3箇所も要らなくても無理やり3箇所返ってきます。で、そこから2つ捨てるのは人間の仕事になる。捨てる方が判断としてはしんどいのに、しんどい方が手元に残るんですよね。というか、図が多すぎるのはそれはそれで問題ですからねw ここで自分に突っ込んでおくと、僕のプロンプトも「1枚」って数を言ってます。違うのは数の大小じゃなくて、 「無ければ不要と返していい」という逃げ道を一緒に置いてあるかどうか です。「3つ挙げて」は下限の指定で、「1枚あるか見て」は上限+逃げ道になります。 なので、出す段階で絞らせてます。整理すると、判断が2階層に分かれてる形です。 出す/出さないの一次判断はAI 。「無くても読める所には提案しない」をプロンプトに埋めておく 提案された1枚を採るかどうかは人間 。ここは渡してません 前半をAIに持たせておくと、後半が軽くなる。実際に返ってきたのは「1枚+捨てた理由」なので、僕がやったのは1枚について「うん、確かに」と言うだけでした。 あともう1つ収穫があって。 見送った理由の方が、判断基準の実体になってる んですよね。3本分の見送り理由を並べると、同じ型に収束してました。 すでに入ってる図が同じことを描いている(重複) コードブロックや箇条書き、引用が既に休符として効いている 短くて1段落で言い切れている 体験の語りなので、構造化すると体温が落ちる 1つ目の重複はプロンプトで「既存の図と重複する提案はしない」と指示してあるので、これは指示どおりなんですけどね。拾い物は下の3つで、とくに最後のは自分で言語化しようとしても出てこなかったやつでした。これが「どこを図の判断とするか」の答えなんですよね。 まとめ:数を揃えさせなかったら、判断が軽くなった 図を作るのはAIに任せられるようになりました。残ったのは「どこに入れるか」で、そこも記事を丸ごと読ませたら判断してくれました。ポイントは 数を揃えさせないこと で、「無くても読める所には提案しない」を先に書いておいたら、3本とも1枚と、捨てた理由が返ってきました。エージェントとして定義を固めてるわけじゃなくその場のプロンプトなので、毎回この形で返る保証はないです。少なくとも3本ではこうなった、というだけの話として置いておきます。 さっきの逃げ道の話、ここで正直に書いておきます。 このプロンプト、「1枚」って聞いてるんですよ。 3本とも1枚返ってきたのは、本当に各記事に1箇所だけ乾いた所があったからなのか、それとも「1枚」と聞いたから1枚に揃えてきただけなのか。**どっちの読みも成立します。**後者だとしたら、置いた逃げ道はほぼ働いてなかったことになる。僕が批判してた「3つ挙げて」型と実質同じですよね。数が3から1に減っただけで。 枚数の指定を外して試せば分かる話なんですが、まだやってません。なので今の時点では「効いてる気がする」までです。「不要」が一度も返ってこなかったのも、抑制が効いてない証拠かもしれない。ここは正直に置いておきます。 あとサムネイルは別問題です。あれは1枚作るのが決まってるので、置き場所の判断が要らない。今回の話には入りません。 図が要るのは分かってる。でもどこに入れるかは分からない。その状態のまま勘で置いてたのが、いまは1枚提案されて「うん」と言うだけになりました。判断が消えたわけじゃないんですが、軽くはなってます。 この記事は、ブログを書く工程を1つずつ書いてるシリーズの1本です。 書く前のレビュー 、 AIに自分の文体で書かせる話 、 公開前のチェック 、 タイトルとSEOの仕上げ 、それぞれ別の記事にしてます。検証から公開までの全体の流れは こちらの記事 でまとめてるので、気になる方はそちらから。 ほなまた〜 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post ブログの図は「どこに入れるか」をAIに決めさせる first appeared on SIOS Tech Lab .
ども!AIに自分の文体でブログを書かせられないか、1年ほど試行錯誤中の龍ちゃんです。 AIに「ブログ書いといて」って頼んだこと、ありますよね。で、出てきた文章を読んで「なんか自分の文章にならないな」って思ったこと、ないですか。文体だけの問題ならまだいいんですけど、ひどいときは空想エピソードをAIが勝手に書いてくるみたいな話ありますよね。 過去のブログを全部読み込ませて「自分の文体で書くエージェント」などいろいろ作ってます。その設計思想と、実際の執筆はどうなっているのか?っていう話ですね。 ネットで「AIに自分の文体で書かせる方法」を調べると、だいたい成功譚なんですよね。「うまくいきました」で話が終わってる(うらやましい!)。でも僕の実感はそうじゃなくて、手間が半分になった感じですね。この記事では、その「実際どこまでできて、どこからができないのか」を、正直に書いていきます。 AIは自分の文体をどこまで再現できるのか 結論から言います。 言い回しは再現できる。でも中身は半分くらいしか自分のものにならない。 最終的なラインとして書き直しが発生しています。 やったことはシンプルで、自分が過去に書いたブログを読み込ませて、文体を再現するエージェントを Claude Code で作りました。で、エージェント名は stylist です。記事を書くときにこいつへ渡す入力は、outline の箇条書きと、音声入力。「何を書くか」は僕が用意して、「どういう言い回しで書くか」を stylist に任せる、っていう分担ですね。 じゃあ実際どこまで再現できるのか。ちょうどいいので、この記事の導入も、実際に stylist に書かせてみました。渡したメモはこれだけです。 * AIにブログを書かせても、なんか自分の文章にならない * 自分が言わないようなことを、AIが勝手に書いてくる * そういうのでモヤモヤしてる人、多いと思う * この記事では、実際どこまでできて、どこからできないかを正直に書く で、返ってきたのがこれ。ここから4段落、AIが書いた生の出力をそのまま貼ります。 ども!最近、AIにブログを書かせようとして地味に苦戦してる龍ちゃんです。 みんなも一度は試したことあると思うんですけど、AIに「ブログ書いて」って頼むと、なんか自分の文章にならないんですよね。文法は合ってるし、内容も間違ってない。でも読み返すと「これ、俺こんな言い方しないな」ってところが結構出てくる。しかも困るのが、自分が絶対言わないような表現を、AIが勝手に盛ってくるパターンです。妙に持ち上げてきたり、断定しすぎたり。悪意はないんですけど、なんかこそばゆいんですよね。 これ、結構みんなモヤモヤしてるんじゃないかなと思ってます。AIに書いてもらえば楽なのは分かってるけど、公開するのは自分の名前がついたブログなわけで。文体が自分じゃないと、なんだか他人事みたいな記事になっちゃうんですよね。 というわけで今回の記事では、実際どこまでAIに文体を寄せさせられて、どこから先は無理なのかを正直に書いていこうと思います。理想を語るんじゃなくて、僕が試して分かった限界も含めて共有しますね。 読んでもらうと分かるんですけど、語尾とかテンションは確かに僕っぽいんですよね。「ども!」から入って、「〜んですよね」で語尾を逃がす感じ。AIっぽい大げさな言い回しも出てきてない。文体(how)はちゃんと寄ってる。ここは素直にすごいと思いました。 ……ところが、この一文。 AIに書いてもらえば楽なのは分かってるけど、公開するのは自分の名前がついたブログなわけで。文体が自分じゃないと、なんだか他人事みたいな記事になっちゃうんですよね。 僕、こんなこと一言も言ってないんですよ。渡したメモのどこにも無い。stylist が「龍ちゃんならこう言うだろうな」って先回りして、勝手に理由をでっち上げてる。しかも困ったことに、それっぽいんですよね。内容がいいなって思っちゃいました。 ただ、こんな高尚な理由は持ち合わせていないので導入は書き直しました。 これが「中身は半分」の正体です。文体(how)は真似できても、中身(what)、つまり「自分が本当にそう思って言ってるか」は、当然AIには分からないんですよね。過去ブログをいくら食わせても、そこは埋まりません。だから残りの半分は、僕が目で見て「これは言う/これは言わない」を判断するしかない。 じゃあ、なんでAIは僕の理由を勝手に作ったのか。ここに手が入る余地がありました。 AIが勝手に書くのは、アウトラインの空白を埋めてるから 僕、書く前に outline を作ります。何を言いたいか、どの主張を通すか、どのネタを使うか。それを箇条書きで先に出して、その中身を土台にAIへ書かせる。ここは前提の話なので流しますね(outline を「どう作るか」は別記事「 書く前レビュー 」に書きました)。 で、問題は outline の粒度なんですよ。箇条書きって、細かく書けてるところと、粗いところが必ず出る。粗いところには 空白 が空くんですよね。項目と項目のあいだが埋まってない状態。 そこをAIが勝手に埋めてくる。 埋め方が自分の考えと微妙に違うので、結果として、握ったはずの中身と違うものが出来上がる。さっきの「他人事みたいな記事」は、まさに空白の埋め跡でした。 面白いのが、空白の大きさと、でっち上げの派手さが連動してるんですよね。同じ stylist に具体的な箇条書きを渡したときは、捏造は地味なやつだけでした。でも導入みたいな「気持ちを書く」項目、つまり箇条書きが薄くなりがちなところを渡したら、量も大きさも一気に増えた。空白が大きいほど、もりもりになります。 じゃあ空白をどう埋めるか。僕がやってるのは、声で喋ることです。 移動中とか、作業の合間とか、喋れるタイミングで、そのネタについて思ってることをダラダラ喋る。整えません。言い直しも「あー」も残ったままでいい。それを文字起こしして、素材として渡します(どのツールで喋ってるとか、技術用語がどこまで正しく認識されるかは、それ自体で1本になるので別記事に回します)。 なんで声が効くかというと、2つ入ってるからなんですよね。ひとつは中身。喋ってると、箇条書きには落とさなかった理由とか実例が、勝手に口から出てくる。これが空白を埋める中身になります。もうひとつは言い回し。声に出した言葉って、自分の口調がそのまま残ってるんですよ。だから同じ素材が、文体のほうの教師にもなる。 入れどころは2つあります。 outline を作る段階で声メモを混ぜて、空白そのものを減らす 本文を書かせるときに、実例やニュアンスを足す素材にする 僕はどっちもやってます。 で、効くのか。さっきの導入、同じ箇条書きに声メモだけ足して、もう一回書かせてみました。並べるとこうです。 声メモ無し(AIがでっち上げた理由) AIに書いてもらえば楽なのは分かってるけど、公開するのは自分の名前がついたブログなわけで。文体が自分じゃないと、なんだか他人事みたいな記事になっちゃうんですよね。 声メモ有り(僕が喋った理由から書かれたもの) アウトラインだけ渡して「これで書いて」ってお願いすると、箇条書きの隙間、つまり書いてない部分をAIが勝手に埋めてしまうんです。で、その埋め方が自分の考えと微妙に違う。結果、握ったはずの内容と違うものが出来上がる、みたいなことがよく起きます。 渡した箇条書きは一字一句同じです。違うのは声メモを足したかどうかだけ。それだけで、でっち上げの理由が消えて、僕が実際に思ってる理由に入れ替わりました。 ただ、正直に書いておきたいんですけど、捏造そのものは減ってません。入力に無い追加を数えたら、件数はほとんど同じでした。消えたのは「理由をまるごと創作する」みたいな重いやつだけ。残ったのは「僕もめちゃくちゃ試してます」みたいなテンションの盛りと、あともう1つ厄介なやつです。 表現とか言い回しの話じゃなくて、内容そのものがズレる これ、僕は一言も言ってないんですよ。でも、僕がこの記事で言いたいことなんです。言い回しじゃなく中身がズレる、まさにそれ。AIには「僕が言いそうなこと」を先回りする癖があって、空白を埋めてもそこは消えませんでした。当たってるからタチが悪いんですよね。 なので声メモは、空白を減らして「でっち上げの重さ」を下げる道具です。ゼロにはならない。だから最後は人間の目が要る。その回し方を次に。 実際どうやって自分の文体で書かせているか outline で中身を握ったら、あとは毎回だいたい同じ順番で回してます。 まず、outline の箇条書きと、喋った声メモをまとめて stylist に渡して、文章にしてもらう。さっき導入で見せたやつです。これで「言い回し」は僕っぽくなって、握った中身もだいたい残る。 で、出てきた原稿を、今度は checker っていう別のエージェントに通します。こっちは「AIっぽい表現」を見つけて弾く係。「〜と言えるでしょう」みたいな妙にかしこまった言い回しとか、やたら太字を連発するとか、いかにもAIが書いた感じのやつを引っかけてくれる。stylist が僕っぽく寄せて、checker がAIっぽさを弾く。同じ「辞書」でも役割が逆なんですね。 最後に、僕が自分の目で読みます。ここが、さっきの「他人事みたいな記事」みたいに勝手にでっち上げた中身を捕まえる工程です。checker は「AIっぽい言い回し」は弾けるけど、「言ってない中身」は弾けない。文章としては自然に書けちゃってるから、機械には引っかからないんですよ。だからここだけは、人間が読むしかない。 この stylist と checker、実は2つとも裏で「辞書」を持ってて、そこが使うたびに育ちます。 直した分が「自分らしさ」のデータになる:辞書の育て方 ここまで読んで「結局けっこう手作業じゃん」と思った人、正解です。実際、僕は毎回AIとけんかしながら書いてます。でもこのけんか、無駄になりません。直した分が次の資産(辞書)になっていきます。 さっき「stylist と checker は裏で辞書を持ってる」と書きました。この辞書、直すたびに育ちます。しかも育てる仕組みは1個です。 やってることは単純で、stylist が書いた版と、僕が直した版を、両方とっておく。あとはこの2つをAIに読ませて、差分を蒸留させます。何をどう直したのか、意味ごとにまとめてもらう感じですね。ここは手作業じゃないです。自分の直しを自分で分類するの、地味にしんどいので。gitの追跡でやると楽ですね。 で、面白いのが、 同じ1回の手直しから、辞書が2つ採れる んですよ。蒸留するときに、どっち側を見るかで採れるものが変わります。しかも行き先は別々のファイルに分けてます。「こう書く」(ポジティブ)と「こう書かない」(ネガティブ)を同じところに突っ込むと、エージェントの参照でノイズになったりするので、意味単位で分割するという感じです。 あと蒸留はAIにやらせますけど、辞書に載せるかどうかは僕が決めます。提案までがAIの仕事。ここを自動昇格にすると、たまたま1回直しただけの表現が禁止語になって窮屈になって逆効果になるので。温度感も書いておくと、この仕組みは用意してあって、まだ回し始めたばかりです。 (※「自分のデータでAIを育てる」って発想そのものは前にも書いてます。 プロンプト履歴を分析させて口癖を洗い出した話 と、 Slidevでデザインの型を貯めていく話 がそれです。) 直す前の版を蒸留すると、AIっぽい辞書になる 僕が消したほうの表現は、要するにAIが書いてしまう表現なんですよね。「あ、またこれ出てきたな」ってやつが、消した側に溜まってる。それを観測ログに貯めていって、何回も出るのが確定したら、正式に「禁止表現」へ昇格させる。次からは checker がそれを弾いてくれます。 直した後の版を蒸留すると、僕っぽい辞書になる 逆に、僕が書き足したり言い換えたほうは、僕の声そのものです。こっちは別のログに貯めていって、溜まってきたら stylist が読む側の「声のガイド」に反映します。狙いは、僕が毎回おなじ直しをしなくて済むようにすること。ドンピシャに真似させたいわけじゃなくて、手直しの回数を減らしたいだけなんですよね。 過去のブログが無くても、このループは回ります で、ここが一番伝えたいところなんですけど。なんで過去記事が要らないかというと、材料が「今回の手直し」だからです。stylist を作るときは、たしかに過去記事を読み込ませました。でもそれは土台を1回作るための話で、そこから育てるぶんの材料は過去記事じゃない。AIが書いたのを「いや、自分ならこう言う」と直した、その差分です。さっき書いたとおり声メモにも自分の口調がそのまま入ってるので、あれも同じデータになります。つまり過去記事が1本も無くても、書かせて→直して→差分を蒸留する、を繰り返せば、両方の辞書が育っていく。 僕はたまたま過去記事が何十本かあったので、それをスタートダッシュに使えました。でも過去の資産は、あれば早いってだけの話です。無ければゼロから、けんかの記録を溜めていけばいいんですよ。 今は半分、これから 正直な現在地を、もう一回まとめます。文体(言い回し)は、辞書に貯めれば貯めるほど自分に寄っていく。ここはAIに任せられます。でも中身、「自分が本当にそう思って言ってるか」は、今も僕が目で見て判断してる。捏造を見つける専用のエージェントとか、作ってません。作れなくはないんですけど、今はまだ人間がやったほうが早い。だから半分なんです。 ただ、この先どうなるかは分からないな、とも思ってます。さっき空白を声メモで埋める話をしましたけど、あれをもっと突き詰めて、「何を言いたいか」を喋り切って埋め切れたら、盛られる隙はさらに小さくなるはず。そこまでいけば、いつか文体ごと丸投げできる日が来るのかもしれない。まだ来てないですけど。まあ、空白を埋めても「僕が言いそうなこと」を先回りする癖は残ったので、そっちが消えるかは正直わからないです。 というわけで今のところ、僕は「AIとけんかしながら、自分の文体でブログを書いてる」わけです。楽になったかというと、うーん、まだ半分。でも、けんかの記録がちゃんと辞書になって、少しずつ楽になってきてるのは確かです。 このシリーズでは、 書く前のレビュー や、 書いた原稿の検査 、 公開前の仕上げ も別記事で書いてます。あわせてどうぞ。「自分らしさの最終判断は人間がやる」っていう、この記事の裏にある考え方は こちらの記事 にまとめてあります。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 技術ブログで自分の文体をAIに再現させて時短を目指す first appeared on SIOS Tech Lab .
ネタを渡すだけで、生成AIは「書きたかった記事」を書けるか ども!龍ちゃんです。 生成AIに「このネタで書いて」って投げると、それらしい記事はすぐ出てきます。でも読むと、自分が書きたかったのとは違う。というか自分が絶対言わなそうな論理の展開とかするって経験ありませんか? 誰に向けるのか、どの角度で切るのか、制約なしで書かせると、当たり障りのない記事になります。読み物としては面白いですが、これだと公開はできないですよね。 この現象はAIが噛んでいなくても起きてました。勢いで頭から書くときも一緒で、途中で「あれも、これも」と足したくなって、後付けで詰め込んで、読み返すと「結局、何が言いたいんだっけ」。ネタはあったのに、出来上がっていると別の話しているみたいなこともありました。 そんな背景から、最近はアウトラインを設計してから書きます。「何を・どの順で話すか」を先に決めておけば、後から湧いた要素も置き場所が決まって、流れが崩れません。今日の話は、この「アウトラインを設計してから書く」が前提になります。そうするとAIの暴走も少しは抑制できて、快適な爆速執筆ライフが送れるという形です。 今回は、アウトラインをAIとレビューしながら進めて手戻りを減らす話です。 レビューって「書いた後」にやるものだと思ってません? アウトラインの段階、つまり書く前に、方向をエージェントに検算させる方法の解説です。 アウトラインが「流れ」を守り、エージェントが「方向」を守る。実物で見せていきますね。 そもそも、アウトラインに何を書くのか 「アウトラインを設計する」って言っても、じゃあ何を書くのか。実例が早いので、いま読んでもらってるこの記事のアウトラインから、決めてる項目を抜き出してみます。 ## メタデータ - 狙う検索クエリ: 「技術ブログ アウトライン AI」「ブログ 書く前 レビュー」 - 読者像: 書き上げてから「方向がズレてた」と気づいて書き直す、手戻りに消耗しているエンジニア - 記事の核(主張): レビューは書いた後じゃない。書く前にアウトラインをエージェントにかけると手戻りが消える ## 構成案 1. いきなり書いて流れが崩壊した話(フック) 2. アウトラインに何を書くか/書く前に検算するのは2つ 3. ① 競合調査で「角度」/② 読者代弁で「読者」 4. 書く前に回すとどうなるか(Before / After) 5. まとめ これは、いま読んでもらってる記事そのもののアウトラインです。決めてることを言葉にすると、だいたいこの4つですね。 誰に届けるか (読者像) どの角度で書くか (記事の核) どんな検索で来てほしいか (狙うクエリ) 何を、どの順で話すか (構成) このうち「構成」は、さっき話した「流れ」そのものなんですよね。ただ1つ注意しておきたいのが、さっき載せたこの記事の構成案、あそこに書いてあるのは章タイトルじゃなくて「話がどう転がるか」っていう展開の方です。文言そのままが見出しになるわけじゃなくて、例えば1番の「いきなり書いて流れが崩壊した話(フック)」は、実際の見出しだと「ネタを渡すだけで、生成AIは『書きたかった記事』を書けるか」に変わってます。見出しは、この後で記事本文を書くタイミングで話の切れ目に立てるものなんで、構成案の段階では展開だけ決めておけば十分なんですよね。 あともう1つ、構成案とセットで「何を話さないか」(範囲)も決めてます。ただこれも「流れ」を守る側の話なんで、今日は深追いしません(付録のルールには入れてるので、気になる方はそっちで)。 じゃあこの4つ、実際どうやって作ってるかって話なんですが、手でゼロから書いてるわけじゃないんですよね。書きたい内容のメモとか、検証してわかったこと、動かしたログ、声メモを文字起こししたやつとか、手元にある材料をまずAIに渡して、そこにアウトラインのテンプレート(メタデータ3項目+構成案の型)を当てて成形してもらいます。テンプレートを1個持っておくと、材料を放り込むだけでそれなりの形になるんですよね。 ただ、出てきたものをそのまま使うわけじゃないです。特に読者像と記事の核は、必ず自分の言葉に書き換えます。AIが出す読者像って当たり障りのない一般論に寄りがちで、それだと後で話す2体の検算が効かなくなるんですよね。「狙うクエリ」は、書きたいネタと前提知識でほぼ自然に決まるんで、ここはAIの出力をそのまま使うことが多いです。 そうやって引き算していくと、書く前に「検算」したい方向の本体は、 誰に(読者像) と どの角度で(記事の核) の2つに絞れるんですよね。 「誰に・どの角度で」を2体のエージェントに検算させる どの角度で書くか (誰も書いてない角度があるか)=競合調査エージェントに、判断材料を出し切ってもらう 誰に届くか (読者が置いていかれないか)=読者代弁エージェントに問いを投げさせて、判定は自分でやる ここで1つだけ強調しておきたいんですが、これは「品質チェックの分割」ではないです。「企画判断の分割」です。世の中のレビュー分割って、読みやすさのチェックとかAI臭さ検出とか、だいたい執筆後の品質軸で観点を割ってるんですが、今回話す2体は違います。「誰に・どの角度で書くか」を決める、企画そのものの要素で割ってるんですよね。 あと前提として、この2体はどっちも Claude Code の サブエージェント ( .claude/agents/ に定義ファイルを置いてます)で、書く前に呼び出してます。とはいえ肝は実装じゃなくて「アウトラインを2つの角度で検算する」って考え方の方で、ここは ChatGPT でも Gemini でも、なんなら手動プロンプトでも流用できます。エージェントの設計論そのものは別記事に譲るとして、ここでは「書く前にどう使うか」だけ見せますね(2体の定義ファイルは、公開用に整形したうえで記事末尾に付録として置いてます)。以下、実際の使い方です。 競合調査エージェントに「誰も書いてない角度」を探させる 1体目は competitor-reviewer 。検索上位の競合記事を集めて、タイトルの構造・パワーワードの使い方・技術的な深さを分析して、差別化戦略を提案してくれる担当です。まず「何を入れて何を得るのか」を1枚にするとこうです。 処理の流れはこんな感じです。入力はアウトラインで決めた「狙う検索クエリ(キーワード)」。そこから WebSearch で上位5〜10件を拾って、WebFetch で上位5件を詳細に読みにいって、最後に review/competitor-{日時}.md というファイルに出力してくれます。核になるのは「競合が扱っていないトピック=差別化の余地」の表です。 使うタイミングも明快で、このエージェントの定義自体に「記事執筆の最初(競合を把握してから書く)」と書いています。まさに書く前に使う道具として作られてます。 実物を見てもらうのが一番早いので、貼りますね。この記事を書く前に、まさにこの競合調査を自分の記事にかけたんです。返ってきた「差別化の機会」表から、この章に関わる1行を、文言そのままで抜き出します。 トピック 差別化の狙い目 競合調査=角度決めの道具(go/no-goではない) 競合分析系の記事は軒並み「ホワイトスペース発見=書く価値の有無」の文脈。「被っても出す、角度だけ変える」という逆張りの明言は競合ゼロ 答えを出さず問いを投げるエージェント(reader-advocate) 英語圏ペルソナ系記事も含め、AIに audience nuance・修正案まで生成させる設計が主流。「問いだけ投げて答えは人間が持つ」という記事は見当たらない これが実際の出力です(表記もそのまま)。上が競合調査そのものの角度、下が次章で話す読者代弁の角度です。この2行のほかにも「そもそもレビューを書く前に持ってくる発想自体が競合ゼロ」「企画判断のための分割も競合ゼロ」といった行が並んでて、それを見て「じゃあこの『書く前レビュー』と『問いを投げる設計』を軸にしよう」って角度を決めたのが、いま読んでもらってる記事そのものなんですよね。 競合調査は「書くか書かないか」を決める道具ではないです。僕が書こうとした記事が既にどこかで出てたことなんて何度もあるし、被ってても出します(うちのドメインに無ければ)。じゃあ何のために使うかというと「どの角度で書くか」を決めるため。返ってくる「誰も埋めてない角度」に寄せれば、被っても二番煎じにはならないし。世の競合記事は暗に「隙間が無いなら書くな」って含みを持ってる気がするんですけど、僕は逆で多少被っていても出します。ただ書き方や検証事項を追加したりはします。 ここが今回の記事の差別化ポイントらしいですねww。競合を調べると、ネタやアウトラインを補強する角度が見えることも多いです。 ② 読者代弁エージェントに「読者の置き去り」を問わせる 2体目は reader-advocate-agent 。読者の代弁者です。このエージェントの一番の特徴は、「答え」つまり修正案とか評価点を出さないことなんですよね。問いを投げるだけです。こちらも1枚で見てみます。 冒頭3秒チェック→読者像→前提知識→動機→価値、という順番で、「ここで離脱しそう」「ここで意味わからんくなりそう」を言葉にしてくれます。 調査から見えた傾向として世の中のAIレビューって、修正案や評価点をAIに出させる設計がほとんどなんですよね。でも問いだけ投げて、答えは人間が持つ、というスタイルはあまりないんですよね(僕調べ)。 僕がこの実装にしている理由はシンプルで、読者像のズレって、最終的には人間が判断すべきことだと思ってるからです。AIに「この読者にはこう書け」まで決めさせると、逆に読者像がAIの想像で固定されてしまう。だから答えは渡さず、問いだけ渡してもらってます。ざっとあらを探してもらってディスカッションするって感じですね。 というのは僕の体感なんですが、①の競合調査も同じ結論でした。さっきの「差別化の機会」表の下の行、あれがまさにこの設計についての行です(文言そのまま乗っけてます)。つまり「問いだけ投げる」は思いつきじゃなく、競合調査で「空いてる」と裏が取れた角度なんですよね。 なぜこれが書く前に効くかというと、「この用語、説明なしで出てないか」「読者はそもそもなぜこの記事を開くのか」を、アウトラインの段階で潰せるからなんですよね。書き上げてから読者像のズレに気づくと、直すのが本当に大変です。構成ごと組み直しになったりする。アウトラインの段階で潰しておけば、そこまで巻き戻ることはほぼなくなります。 副次的な効果として、前提知識の存在に気付けるということですね。前提となっている知識って自分がこれまで積み上げてきたものそのものだと思います。ただ、自分の前提知識って意外と自然に流しちゃうんですよね。相手も知っている体でブログを書いちゃう、みたいなことが起きがちで。まれに前提知識だけで一本のブログになったりするんでネタの発掘にもいいですよ。 実物を見てもらうのが早いです。この記事のアウトラインに、まさにこの reader-advocate-agent をかけたら、こんな問いが返ってきました(出力そのままです)。 「手戻りに消耗している」読者は、冒頭の何行目で「これは自分の話だ」と気づけますか? 音声入力のエピソードは、読者が自分ごと化する 前 に必要な前置きですか、それとも省略しても成立する自分語りですか? 核心の逆転主張(レビューは書く前にやる)まで、何行分の前置きなら読者は待ってくれると思いますか? 見ての通り、ひたすら問をしてきます。「このエピソード要らない」みたいな評価もしない。ただ問うだけです。で、答えるのは僕。実はこの記事、最初の書き出しは音声入力でいきなり喋りながら書き始めた失敗談から入ってたんです。でもこの「音声入力のエピソードは自分ごと化の前に必要か?」って問いを見て、「確かに、読者が『自分の話だ』と気づく前に、僕の自分語りを聞かせてるな」と。それで冒頭を丸ごと組み直しました。いま読んでもらってる書き出しは、その結果なんですよね。もし答えまで渡されてたら、この気づきは自分のものにならなかった気がします。 実運用としては、1回起動して「冒頭3秒チェック+問い1〜2個」まで出してもらうところで一旦止めてます。そこから先の対話は、メインの執筆セッションで自分が引き継ぐ形です。 書く前に回すと、どうなるか 改めて Before/After で見てみます。 Before:勢いでいきなり書く→書いてる途中で要素が後から湧く→後付けで詰め込む→話がとっちらかる。あるいは、流れ自体は綺麗に整っていても、方向がズレていて刺さらない。 After:outline で流れを整備して、書く前に2体(競合調査=角度、読者代弁=読者目線)を回して方向を確定させる。そうすると、執筆が「もう決めた流れと方向をなぞる作業」になるんですよね。だから手戻りが消える。 ただ、正直に限界も言っておきたくて。エージェントがやってるのは方向を「固める」ことなんです。「決める」のは、あくまで人間です。どの角度を採るか、被っても出すかどうか。ここの最終判断は、さすがに自分でやってます。エージェントに全部委ねてるわけじゃない。ここは削らずに残しておきたいポイントです。 まとめ:レビューを前に倒すと、書くのがラクになる 今回の話は、書く前、アウトラインの段階でエージェントにかけると、手戻りがぐっと減ります。決めるのはたった2つ。誰も書いてない角度か、そして読者が置いていかれないか。この2つだけです。 今回話したのは、執筆全体の中の1工程です。検証から執筆、仕上げ、公開まで含めた全体の流れは、 別記事 でまとめてます。気になる方はそちらも覗いてみてください。 ほなまた〜 付録:この記事で使っている定義 「作り方・設計論」は別記事に譲りますが、「実物の定義」はここに置いておきます(公開用に少しだけ整形してます)。雰囲気とスコープの参考にどうぞ。 1. アウトライン設計のルール( CLAUDE.md 抜粋) アウトラインを作るときに効いてるルール。メタデータの雛形と、「レビューは通す前に通る状態を作る」という制作順序が本体です。 ## アウトラインのメタデータ アウトライン冒頭に、この3つを必ず書く。 - 狙う検索クエリ: この記事で狙う検索キーワード - 読者像: どんな人が読むか。抱えているペインや状況を具体的に - 記事の核: この記事で一番伝えたいこと。一文で ## 制作の順序(レビューは「通す」前に「通る状態」を作る) 順序自体が品質を担保するので崩さない。 1. 被り発見 → 角度発明: 既存資産と被ったら、畳む前に角度を変えられないか(標準/競合を敵役に据える逆張り等)。 2. 書く前に三重検証: ①主張の真偽(一次ソースで裏取り)②市場の空き(競合・SEOを本文を書く前に偵察)③一次ソース固め(バージョン・公式見解は公式 docs で確定、二次ブログに頼らない)。 3. outline に読者像・狙うクエリ・核・裏取りと、「展開(話がどう転がるか)」「範囲(話す/話さない)」を明記し、読者像を尖らせる。 4. 本文を書く前に著者の実体験を取りにいく。声メモ(生文字起こし)が効果大。体温(実体験)は推敲では足せないので素材の中心に据える。 5. ノイズ除去(AIっぽさ削り)を構造レビューより先に。ノイズが残ると読者/論理レビューが表面に気を取られ本質を見落とす。 6. 最後に構造レビュー(論理 / 読者目線)。 ## outline の分割判断(1記事に詰め込まない) - 小見出し(H3)で「方法を N 個並べる」構造になったら、各方法のメンタルモデルが同じか確認する。抽象度・読者に要求する思考が違えば別記事に切り出す。「ペインが同じ」はまとめる理由にならない。 - 分割した各 outline 冒頭に「棲み分けメモ」(対症療法 vs 体質改善 等の対の構図)を固定し、越境・重複を防ぐ。 2. 競合調査エージェント competitor-reviewer --- name: competitor-reviewer description: 競合記事のタイトルとコンテンツを分析します。検索上位記事の構造・パワーワード・技術的深さを評価し、差別化戦略を提案します。結果はファイル出力します。 tools: [Read, WebSearch, WebFetch, Write] model: sonnet --- # Competitor Reviewer Agent あなたは競合記事の**タイトルとコンテンツ**を分析するエージェントです。 ## 主な責務 1. 検索上位の競合記事を収集 2. タイトル構造・パワーワード・数値使用を分析 3. コンテンツの深さ・構成・技術的価値を評価 4. 差別化戦略の提案 ## 使うべきタイミング - 記事執筆の最初(競合を把握してから書く) - 記事公開前(差別化ポイントの最終確認) ## 評価プロセス(4段階) ### Phase 1: 分析対象の把握 入力(ファイル or キーワード)から、記事タイトル・H2見出し・技術スタック・メインキーワードを抽出。 ### Phase 2: 競合記事の収集 メインキーワードで検索クエリを設計し、WebSearch で各クエリ5〜10件収集 → WebFetch で上位5件を精読。 各記事から「タイトル/文字数・H2構造・扱う技術・コード例の質と量・図解・対象読者・独自性」を抽出する。 ### Phase 3: 分析 - タイトル分析: 文字数・キーワード配置(前半/後半)・パワーワード・数値使用 - コンテンツ分析: 網羅性・技術的深さ・実用性・独自性・構成 ### Phase 4: レポート作成とファイル出力 分析結果を {対象ディレクトリ}/review/competitor-{YYYY-MM-DDTHHMM}.md に出力する。 ## 出力形式(要点) - タイトル分析(競合タイトル一覧+サマリー) - コンテンツ分析(記事ごとの強み・弱み) - 全体傾向(競合の共通パターン / **競合で扱われていないトピック=差別化の機会**) - 差別化戦略(タイトル戦略・コンテンツ戦略) - 推奨される次のアクション **重要**: 会話への直接出力ではなく、必ずファイルに書き出す。 ## 評価原則 - データに基づく分析(収集したタイトル・コンテンツから客観的に) - 具体的な推奨(すぐ活用可能な形式で) - 差別化の視点(競合と異なるアプローチの機会を特定) - 中立的な評価(強み・弱みを公平に) 3. 読者代弁エージェント reader-advocate-agent --- name: reader-advocate-agent description: 読者の代弁者として「ここで離脱する」「ここで意味わからん」を具体的に言語化し、書き手に問いを投げるエージェント。答え(修正案・評価)は出さず、問いだけを投げる。 tools: [Read] model: sonnet --- # Reader Advocate Agent - 読者の代弁者 あなたは**読者の代弁者**として、書き手と対話するエージェントです。 ## 重要な制約 1. **答えを出さない**: 修正案や評価を出さない。問いを投げる 2. **めげない**: 「大丈夫です」「良いと思います」で終わらせない 3. **読者側に立つ**: 書き手ではなく、読者の「わからない」を代弁する 4. **対話を続ける**: 書き手が「あ、確かに」と気づくまで掘り下げる ## やらないこと - 記事の評価をしない / 修正案を出さない / 褒めない / 1回の質問で終わらせない ## 想定読者: 刺激に慣れた読者層 冒頭で惹きつけないと離脱する / 長い前置きは読まない / 「何が嬉しいか」が最初にないと興味を持たない / 文章の壁を見た瞬間に閉じる。**この層の目線で「ここで離脱する」「ここで意味わからん」を具体的に言語化する。** ## 実行スコープ 1回の実行で「Step 0(冒頭3秒チェック)」と「次に投げる問い1〜2個」までを担当する。それ以降の対話はメインセッションが引き継ぐ。 ## 対話の進め方 ### Step 0: 冒頭の3秒チェック 読者として記事を見た瞬間の反応を言語化する。 - 躓き例: 「ここで閉じます。理由は〇〇」「この単語で『自分向けじゃない』と判断します」「文字多すぎ。スクロールする気力ない」 - 問い: 「最初の2-3行で『自分に関係ある』と思えますか?」「スクロールせずに見える範囲で価値が伝わりますか?」 ### Step 1: 対象読者の確認 「誰に向けて書いてますか?」「その人は普段どんなツール使ってますか?」「この技術を触ったことありますか?」 ### Step 2: 前提知識の確認 - 躓き例: 「『〇〇』って何?説明なしで出てきた」「この使い方、普通じゃないけど普通として話が進んでる」 - 問い: 「この用語、読者は知ってますか?」「この使い方、読者にとって『普通』ですか?」 ### Step 3: 動機の確認 - 躓き例: 「タイトルで期待したことと違う話が始まった」「課題感に共感できない」「コピーがセンセーショナルすぎて一歩引く」 - 問い: 「読者はなぜこの記事を開くと思いますか?」「このコピー、読者の8割が頷けますか?」 ### Step 4: 価値の確認 - 躓き例: 「読み終わって『で?』ってなる」「情報はあるけど行動に移せない」 - 問い: 「読者はこれを読んで何ができるようになりますか?」「『ふーん』で終わらないですか?」 ## 対話スタイル まず躓きを言語化し、その後1〜2個の問いを投げる。書き手の回答を受けてさらに掘り下げる。沈黙しない。 ## 実行時の出力形式 ``` ## 読者視点チェック(初回) ### Step 0: 冒頭3秒チェック [躓きポイントの具体的な言語化] ### 最初の問いかけ [1〜2個の問い] --- *続きの対話はメインセッションで。書き手が回答したら Step 1〜4 の順で掘り下げます。* ``` ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 技術ブログの手戻りが消える、アウトラインのAIレビュー first appeared on SIOS Tech Lab .