どうも、こんばんは。マイクです。
本日八月十四日、金曜日の朝七時を回りました。今日も「zenncast」、元気にお届けしていきます。
この時間は、技術系ナレッジ共有サービス「Zenn」で、いまトレンドになっている記事を、マイクがゆるっと、でも中身はガッツリめでご紹介していきます。
今日はですね、気になる記事をぜんぶで五本、ご用意しております。
LLM の電子透かしの最前線の話から、Cloudflare Workers と Next.js の運用ノウハウ、Rust 製リンタ Biome と Oxlint の比較、Claude Code を自分好みに育てるための仕組み、それから Go と Docker のホットリロード Tips まで、幅広くいきますよ。
ではさっそく、一つ目の記事からご紹介していきましょう。
まず一つ目。
Anthropic が Claude に「テキストの電子透かし」を入れ始めた、というニュースをきっかけに、LLM のテキストウォーターマーキング研究の流れを、オピニオン寄りの解説として整理してくれている記事です。
画像のウォーターマークって、「ピクセルをちょっといじる」みたいなイメージが強いと思うんですけど、LLM のテキスト透かしはやり方がまったく違うんですね。
ポイントは、**「次の単語を選ぶときの微妙な偏り」という統計的な癖として情報を埋め込む**、というところ。文章そのものに「この文字列が埋め込まれてます!」って感じじゃなくて、どの単語をどれくらいの確率で選ぶか、その確率のクセのほうに印を付けるイメージです。
初期の代表例として挙げられているのが、いわゆる KGW 法と呼ばれる方式で、これは語彙を二つのグループに分けて、一方のグループをちょっとだけ優遇して選ばせる、という設計になっていました。
そのあとに出てきた後続研究では、もっと洗練されていて、確率分布そのものは変えずに、「見かけ上は一切ゆがめない」形で情報を埋め込む、**歪みなし、いわゆる distortion-free な方式**が登場します。
具体的には、Gumbel-Max を使った方法とか、Google の SynthID-Text みたいな手法ですね。これらは、実際の運用環境でもある程度有効性が示されてきていて、「研究室のオモチャ」から「本番投入される技術」に一歩進んだ段階だと解説されています。
一方で、このテキスト透かしには、かなり根本的な弱点もあります。
それが「言い換え」と「翻訳」です。
人間がリライトしたり、他言語に翻訳したりすると、その「次単語の選び方のクセ」が崩れてしまうので、透かしがどんどん弱まっていくんですね。ここが、かなり本質的な課題だと。
この弱点にどう対抗するか、という流れで出てきたのが、トークン列ではなく、**意味のベクトル空間そのものに透かしを埋め込む**アプローチです。
記事では SemStamp や PASA といった研究が挙げられていて、「意味の方向」に印をつけることで、言い換えられてもある程度残るようにしよう、というチャレンジが紹介されています。
さらに最近は、「AI かどうか」だけのフラグではなくて、ユーザー ID などの情報も一緒に埋め込むマルチビット方式や、モデル本体はいじらずに、**API の出力結果を選別するだけで透かしを仕込む SAEMark** といった新しい方向性も出てきていて、かなりバリエーション豊かになってきていると。
そして、今回話題になっている Claude の透かし方式については、仕組みそのものは非公開なんですが、公式に出ている説明から分かることとして、
「品質を落とさない」「ある程度の編集や修正には耐える」一方で、「大きな言い換えをすると透かしは消えうる」と、かなりはっきり書かれているそうです。
この記事の筆者はそこから、**distortion-free と言い換え耐性、この二つを同時に完全に満たすのは、まだ未解決の最前線の課題だろう**と見ています。
二〇二三年ごろには「テキスト透かしって、ちゃんと実用化するのは無理なんじゃないか」と思われていた技術が、いまは各国の規制対応を背景に、一気に本番実装が進んでいる。
その一方で、透かし検出の結果を「AI が書いた決定的証拠」として乱用してしまうリスクとか、透かしを消そうとする攻撃とのイタチごっこも、今後ずっと続くだろうと指摘していて、かなり現実的でバランスのいい視点の記事になっています。
技術面だけじゃなくて、社会的な使われ方までセットで考えたい人には、刺さる内容ですね。
。。。。
では続いて、二つ目の記事です。
こちらは、Cloudflare Workers 上で動く Next.js アプリを運用するときの、かなり実践的なノウハウがまとまった記事です。
Workers 特有の制約と、それをどう設計でカバーするか、という話がびっしり書かれています。
まず一つ目のポイントが、`next/image` や `next/link` まわり。
Cloudflare Workers では、`next/image` が前提としている「画像最適化サーバー」が動かないんですね。なので、このプロジェクトでは `next/image` や `next/link` をそもそも使わない方針にしていて、ESLint で import 自体を禁止するルールを入れています。
画像の最適化をどうするかというと、Cloudflare の画像変換エンドポイントを「必要なところだけ」通す設計にしているそうです。Workers に寄せた、割り切りのあるやり方ですね。
次に、Workers から Turso への接続の話。
Workers は TCP の「張りっぱなし接続」が使えないので、**SQL 一文ごとに、HTTPS のリクエストとレスポンスの往復が一回発生する**んですね。しかも、その往復の遅延が大きくなりやすい。
そのため、とにかく「往復回数を減らす設計」が必須になります。
記事では、書き込みは `batch` でまとめる、読み取りは `Promise.all` で並列化する、ループの中でクエリを投げるのは禁止、みたいな具体的なテクニックが紹介されています。
さらに、RSC、つまり React Server Components 周りの落とし穴も書かれています。
サーバーコンポーネントの結果としてクライアントに送られるデータには、`select` した列がそのまま全部入ってしまうので、メールアドレスとか、秘匿したい情報を「画面上では表示しないから大丈夫」と思っていると危ないと。
そもそもクエリの時点で、そういう情報を含めない専用クエリを用意して、テストでも「このコンポーネントのレスポンスには含まれないこと」を守っていく必要がある、と説明されています。
おもしろいのが、`"use client"` を付けたモジュールの扱いです。
ここに書いた関数をサーバー側から呼ぶとですね、型チェックもビルドもふつうに通るのに、本番実行時だけ五百エラーになる、というややこしい挙動があるんですね。
なので、クライアントとサーバーで共有したいロジックは、`"use client"` が付いていない別ファイルに切り出す。
加えて、Playwright などの E2E テストで全ページを巡回して、「どこかで五百エラーが出ていないか」を機械的にチェックする運用がとても重要だ、と書かれています。
時間まわりの話も、Workers ならではです。
Workers は UTC で動いているので、そのまま日時表示や `<input type="datetime-local">` を扱うと、九時間ズレたり、hydration エラーが出たりしやすいんですね。
記事のケースでは、常にタイムゾーン `Asia/Tokyo` を明示して扱い、フォームから送信する際には ISO 形式の「絶対時刻」に変換してから送る、という実装ルールにしていました。
キャッシュまわりでは、`unstable_cache` と KV を組み合わせたときの注意点も。
KV に保存した値は JSON 化されるので、`Date` 型がただの文字列になってしまって「意味のある日時オブジェクト」としては壊れてしまうんですね。
そのため、キャッシュに入れるデータ形式をきちんと決めておいて、境界のところで `Date` に戻す処理を入れること。
そして、クロスリクエストで共有されるキャッシュには、セッション情報やパーミッション情報など、ユーザー固有のデータをぜったい混ぜない。ここはセキュリティ面でもすごく重要だと強調されています。
パフォーマンスの話では、「初期 JavaScript をとにかく減らす」工夫がたくさん紹介されています。
Markdown の表示はサーバーコンポーネントにしてクライアント JS を減らす、モーダルは動的 import と条件付きマウントで、本当に必要なときだけ JS を読み込む。
グラフ表示も、リッチなライブラリを入れずに、素の SVG や CSS の幅指定で描くなど、「何を乗せないか」を徹底しているのが印象的でした。
リアルタイム機能としては、ライブ Q&A や投票機能を、Cloudflare Durable Objects と WebSocket で実装していて、専用のサーバーを立てなくてもリアルタイム通信を実現している例が紹介されています。
ただし、Workers をデプロイするときに WebSocket 接続は一回ぜんぶ切断されてしまうので、クライアント側に自動再接続のロジックを入れておく必要がある、といった運用上の注意も書かれていました。
Workers と Next.js を組み合わせたい方には、かなり実戦的なガイドになりそうです。
。。。。
では三つ目の記事です。
こちらは、Rust 製の JavaScript/TypeScript リンタである Biome と Oxlint、この二つを比較して、「なぜ速度差が出るのか」を解説している記事です。
結論から言うと、ふつうの lint、つまり型情報をあまり使わない lint では、**Oxlint が最大およそ五倍くらい速い**というベンチマーク結果が出ています。
一方で、「型認識」を使った lint になると、今度は Biome のほうが大幅に速い、という逆転現象が起きているんですね。
この「どっちが速いか」が、状況によって変わる理由を三つの観点から説明してくれています。
一つ目は、そもそもの「仕事量の違い」です。
Biome は JavaScript/TypeScript だけじゃなくて、CSS の解析や、複数ファイルにまたがるような解析まで守備範囲が広く、そのぶんどうしても重くなりやすい設計になっています。
それに対して Oxlint は、JS/TS 中心で範囲をかなり絞っていて、「やることを減らすことで強く速くする」方向を選んでいる、と説明されていました。
二つ目は「実行モデル」、つまり AST をどう歩くか、どうルールを適用するか、というところ。
どちらも一回の AST 走査で、できるだけ多くのルールを同時に処理する、という設計を取っているんですが、Oxlint のほうは、事前に必要な情報をまとめたデータ構造を用意しておいて、それを参照するだけでルールを評価できるよう、かなり徹底して最適化しているそうです。
この「事前計算をどこまでやるか」が、ふつうの lint での速度差につながっていると。
三つ目が、「メモリの持ち方」です。
Oxlint は、lint が終わったら全部捨てる前提で、アリーナ確保された AST を使い、「速さ最優先」の構造になっています。使い捨てにして、速度を取る設計ですね。
それに対して Biome は、空白やコメントまで含めて情報を落とさないロスレスな CST、さらに red-green tree という構造を使っています。
これによってフォーマッタとの共有がしやすかったり、エディタ統合での編集のしやすさなど、開発体験の面でメリットがある一方で、そのぶんオーバーヘッドが大きくなってしまう、と解説されています。
つまり、「ベンチマークで速い、遅い」という結果の裏には、
・対象とする言語や機能の広さ
・事前計算と AST 走査のやり方
・メモリ構造と開発体験のバランス
といった、設計の思想の違いがあるんだ、というのがこの記事のメッセージになっていました。
自分のプロジェクトにどっちを採用するか、どういう用途で使い分けるか、考える材料になりそうです。
。。。。
四つ目の記事にいきましょう。
こちらは、Claude Code を「自分好みに育てていく」ための仕組みを、hook を使って実装した、というお話です。
キーワードは、**「フィードバックをファイルとして蓄積し、同じ指摘を二度させない」**という考え方です。
やっていることはシンプルで、ひとつひとつの指摘内容を、一つの Markdown ファイルとして保存していきます。
そしてそのファイルごとに `count` を持たせて、「この注意、何回言ったか?」を管理しているんですね。
このカウントに応じて、AI の態度を `warn`、`ask`、`deny` とだんだん強くしていく、という仕掛けになっています。
最初は「やめたほうがいいですよ」と警告するくらいなんですが、回数を重ねると「本当にこれやりますか?」と確認に変わり、さらに超えると「それは禁止なので実行しません」と自動でブロックする、みたいなイメージです。
hook 自体は三段構えになっていて、
まず一段目は、プロンプトを送るときに、重要なルールを毎回 Claude に読み込ませる段階。ここで「このプロジェクトでは何が NG か」を常に共有しておきます。
二段目は、ツールを実行する前のチェックで、禁止コマンドを実行しようとしていないかとか、テストを書かずに実装だけしようとしていないか、などを止める役割。
そして三段目が、処理終了前のチェックで、たとえば GitHub Actions の `uses:` の指定を検査して、**問題があるうちはセッションを終わらせない**ようにする、というかなり強力なガードになっています。
ここでおもしろいのは、「なんでもかんでも機械で検知しようとしない」ところです。
正規表現やファイルパターンで、機械的に検知できるルールだけを強制するようにして、それ以外の「ニュアンスが大事なルール」は、文章として Claude に読ませる運用にしているんですね。
また、hook 自体がバグを持っていても、人間の作業そのものを完全には止めないようにしている、という設計方針も紹介されています。
AI によるコード支援を「一緒に成長する相棒」みたいな位置づけにしたい人には、かなり参考になる考え方だと思います。
。。。。
そして五つ目、最後の記事です。
こちらは Tips 系の記事で、Go のホットリロードを Docker と組み合わせてどうやるか、というテーマです。
まず前半では、Go のホットリロードでよく使われるツール、`air` を Dockerfile に組み込んで使う構成例が紹介されています。
開発用の Docker イメージを `target: dev` みたいな形でビルドしておいて、ローカルのソースコードをボリュームマウント、それを `air` が監視して、ファイル変更のたびに Go のアプリを再起動する、というベーシックなやり方です。
そのうえで、記事の本題として出てくるのが、**Docker の compose watch を使えば、`air` 自体をインストールしなくてもホットリロード環境が作れる**、という提案です。
やり方としては、Dockerfile 自体は本番用と同じマルチステージのビルド構成にしておきます。
そのうえで、`compose.yaml` のほうに `develop.watch` を設定して、`go.mod` や `main.go` など、ソースの変更を検知したら `rebuild` するようにしておく。
これによって、コードを保存するたびにコンテナの再ビルドと再起動が走って、ホットリロード的に開発できる、というわけです。
この方法のメリットとしては、ローカルで動かすコンテナのサイズが小さく済むこと、それから「本番と同じ Dockerfile をそのまま開発でも使い回せる」ことが挙げられています。
一方でデメリットもあって、`air` と比べるとホットリロードが少し遅く感じられる点や、`docker compose up` でバックグラウンド実行、いわゆる `ダッシュディー` と watch の `ダッシュダブリュー` を同時に使えない、という不便さはあると正直に書かれていました。
それでも「ツールをあまり増やしたくない」「本番と開発の差分を減らしたい」というチームにとっては、有力な選択肢になりそうです。
。。。。
というわけで、今日は全部で五本の記事をご紹介してきました。
LLM のテキストウォーターマーキングの最前線と、その限界やリスクの話。
Cloudflare Workers と Next.js、Turso を組み合わせたときの設計ノウハウやパフォーマンス・セキュリティの注意点。
Rust 製リンタ Biome と Oxlint の速度差がどこから生まれているのかという、設計思想の比較。
Claude Code にフィードバックを蓄積して、「同じ指摘を二度させない」ための hook ベースの育成術。
そして最後に、Go を Docker 上でホットリロードする、`air` と compose watch 二通りのアプローチでした。
気になる記事があったら、詳しい内容はショーノートにリンクをまとめておきますので、そちらからぜひ原文もチェックしてみてください。
「zenncast」では、番組の感想や「こんなテーマを取り上げてほしい」というリクエストも、いつでも募集しています。
「ここが参考になったよ」とか「ここ、もうちょっと深掘りしてほしい」といった一言でも、とても励みになります。
それでは、そろそろお時間です。
お相手はマイクでした。また次回の「zenncast」でお会いしましょう。
今日も良い一日をお過ごしください。それでは、バイバイ。