どうも、マイクです。おはようございます。
二千二十六年八月五日、水曜日の朝七時を回りました。今日も「zenncast」、張り切ってお届けしていきます。
この時間は、技術情報共有プラットフォーム Zenn から、いまホットなトレンド記事をピックアップして、内容をまるっとラジオで味わっていくコーナーです。
今日はお便りはお休みということで、その分じっくり記事を見ていきましょう。
きょう紹介する記事は、ぜんぶで五本です。AI時代の社内検索から、Rust のテスト高速化、PKBとエージェントの実践、iOS Safari のクセ強挙動、そして生成AIプロダクトのQAの話まで、かなり濃いラインナップになっています。
それじゃあ、一つ目からいきましょう。
。。。。
まず一つ目の記事。
これは、Cerebras という会社の「社内ナレッジ検索基盤」の事例を通して、AI時代の情報検索をどう実践していくか、かなり具体的に教えてくれる内容になっています。
おもしろいのが、ベクトルデータベース専用のプロダクトはあえて使わずに、「ポスグレ」、PostgreSQL を一枚だけ用意して、その一つのテーブルの中で全文検索とベクトル検索の両方をまとめてやってしまう、という設計なんですね。
これ、めちゃくちゃシンプルなんですけど、その仕組みで一日に一万五千件以上の質問に答えているというから、かなり実戦投入されています。
チャットデータの扱い方にも工夫があって、Slack のスレッドをそのままベクトル化するのではなくて、いったん LLM に通して、「エンジニアが検索しそうな質問」の形にしたり、要約や構造化データに変換してからベクトル化しています。
長い連投が続くスレッドの中でも、「ここ大事だよね」という発言だけを条件付きで別チャンクとして切り出したりして、チャット特有のノイズをなるべく減らしているんですね。
検索するときは、四つの指標を組み合わせて順位付けしています。
キーワード検索のスコア、ベクトル検索の近さ、レア単語をちゃんと評価するための重み付け、そして更新日。
この四つをバランスよく混ぜることで、「意味的に近いけど古い情報」とか、「キーワードは合ってるけど文脈がズレてる」みたいな結果をうまく調整しているイメージです。
さらに MCP 経由で公開しているツールも、`search_slack` みたいな素朴な検索インターフェースをポンと渡しておくだけで、「どのツールをどう組み合わせるか」はエージェント側に任せる、っていう方針をとっています。
人間側はあくまで「使える道具箱」を用意するところまでで、具体的なツールの選び方は AI エージェントに委ねる設計ですね。
一方で、この会社はデータを全部自前の Postgres に集約しているので、部署ごとの閲覧制限とか、かなり細かい権限管理を自分たちで設計しなきゃいけない、という難しさもあります。
用途別に AI を分割していこうとすると、どのエージェントがどのデータにアクセスできるのか、その権限設計がさらに複雑になっていくだろう、と筆者は指摘しています。
シンプルな基盤設計の裏側で、権限まわりはむしろちゃんと考えないと危ないよ、っていうバランス感覚がよくわかる記事でした。
。。。。
では二つ目の記事。
こちらは Rust のテストが裏側でどう動いているのかを解説しつつ、「テストバイナリの増やし方を間違えると、CI の時間とコストが一気に悪化するよ」という話をしている内容です。
Rust のテストでおなじみの `#[test]`、これがどういう仕掛けかというと、コンパイラ組み込みの属性マクロになっていて、テスト関数そのものはちゃんと残ります。
その上で、テスト名とかソースコードの行番号といったメタデータと、そのテスト関数を呼び出すクロージャをまとめた `const` を、テストごとに生やしていく形なんですね。
コンパイラの `rustc` は、それら全部を集めて、テスト用の `main` 関数を自動生成してくれます。
さらに `libtest` という標準のテストランナーをリンクして、一つのクレートにつき一個、「自己完結なテスト実行バイナリ」を作る、という仕組みです。
`cargo test` は、このテストバイナリを一つずつ、直列で起動していきます。
フィルタ文字列や引数をテストバイナリにそのまま渡して、終了コードだけチェックする、という超シンプルな動かし方です。
並列実行されるのは、一個のテストバイナリの内部だけ。
CPU のコア数まで、テスト関数をスレッドで同時に動かすことはできるんですが、テストバイナリ同士は基本的に並列になりません。
その結果、バイナリの数が増えれば増えるほど、「リンクにかかる時間」と、「直列で順番に実行される時間」がどんどん積み上がっていきます。
この仕組みがあるので、`tests` ディレクトリの直下にテストファイルをやたら大量に作ったり、ワークスペースのメンバークレートを細かく分けすぎたりすると、それぞれが独立したテストバイナリになってしまって、CI が極端に重くて高コストになる、という落とし穴にハマりやすいんですね。
じゃあどうするかというと、ポイントは「ビルドされて実行されるテストバイナリの数をまず減らそう」というところ。
具体的には、`tests` 直下に置くファイル、つまり一個一個のテストバイナリを減らして、サブモジュールにまとめていくことで、なるべく一つのバイナリの中にテストを集約してしまう。
Rust のテストを高速化したいなら、個々のテストコードをいじる前に、「バイナリの個数」を削る設計から見直すのが一番効くよ、というのがこの記事のメッセージです。
。。。。
三つ目の記事にいきましょう。
これは Tips 系の記事で、Obsidian を中心にしたパーソナルナレッジベース、いわゆる PKB を「人間と AI の共有メモリ」として動かすための、かなり具体的な手順がまとまっています。
おもしろいのが、「ゼロから自分流で組む」のではなくて、公開されているテンプレートを使って、「レベルゼロ点五くらい」から始めよう、という提案なんですね。
そうすることで、エージェントとの契約まわりとか、フォルダ構成みたいな、最初の土台づくりで消耗せずに済むよ、という考え方です。
まずレベルゼロの段階では、ChatGPT のアプリと Obsidian をインストールします。
そこに Claudian プラグインと Codex CLI を連携させて、テンプレートの Vault をクローン。
その上で、Daily ノートと Persona、つまり自分のプロフィール的なノートを整えることで、「人間と AI が同じ Vault を書き合う最小のループ」を作ります。
ここでのゴールは、とにかく「同じノート群に、お互いが書き込みできていること」を確認するところですね。
レベルワンでは、Hermes Agent を導入して、この Vault を Hermes のホームとして設定し、gateway を起動します。
Hermes の役割は、この段階ではあくまで「土管」。
Slack とかカレンダーみたいな外部サービスへの認証とデータ取り込みだけを担当して、情報を `Inbox` フォルダにひたすら集約していく役割です。
レベルツーに入ると、外部ツールとの接続を一つずつ具体的に進めていきます。
Google カレンダーや Google Tasks の予定やタスクを Daily ノートに流し込んだり、Gmail を読み取り専用でつないで、メールの要約を `Inbox/mail` に貯めたり。
Slack はユーザートークンを使って、「自分が見える会話だけ」を `Inbox/slack` に取り込んでいく。
このあたりは OAuth の設定も含めて、ステップバイステップで解説されています。
運用フェーズに入ると、毎日のループが三つのフェーズに分かれます。
朝は「daily-briefing」として、予定とタスクを Daily ノートに並べて一日の計画を立てる。
日中は Hermes 経由で、メールとか Slack の情報をどんどん集約していく「aggregate」。
そして夜には、その日の学びを Wiki やメインのデータベースに蒸留し直す「eod-distill」、エンド・オブ・デイのディスティルですね。
これを、Daily ノート一枚でくるっと回す日次ループとして運用していこう、という提案です。
うまくいかなくなりがちなポイントもちゃんと触れられていて、
認証エラーで外部サービスから情報が取れなくなったり、Hermes gateway の起動をうっかり忘れたり、`Inbox` にデータが貯まりすぎて処理しきれなくなったり。
複数の PC で同じ Vault を編集して競合が起こる、といったトラブルも挙げられています。
それから、「どこまで自動に任せるか」「どこからは自分で判断するか」という線引きも、悩みどころとして出てきます。
筆者が強調しているのは、テンプレートを使うことで、悩むべきポイントを「認証まわり」と「運用習慣づくり」にかなり絞り込める、ここが大きな利点だというところです。
構造やフォルダ設計に悩む時間を減らして、「人と AI の共同作業の運用」に意識を割けるようになる、そんな実践的な Tips が詰まった記事になっていました。
。。。。
四つ目の記事です。
これは iOS の Safari、つまり WebKit の上で Web アプリを作るときにハマりがちな、細かい挙動と回避策を、かなり大量に紹介している内容です。
フロントエンドやってる方は、あるあるが多そうですね。
まず一つ目のトピックが、`position: sticky` と `position: fixed` のヘッダ上に置いたボタンが、タップを背後の要素に誤配信してしまう問題。
これに対して、合成レイヤーの分離や z-index の調整、DOM の再配置、それから capture フェーズでのイベント再ディスパッチと、四種類の回避策を全部試しても直らなかった、という話が出てきます。
唯一効いたのは、「body のスクロールをやめて、内部コンテナだけをスクロールさせる」という方法だったんですが、これをやると、`window.scrollY` を使ったスクロール位置の復元がうまくいかなくなったり、iOS 独自の「時計をタップして最上部に戻る」操作が壊れてしまう。
そのため、この方法は泣く泣く採用を断念した、という経緯が語られています。
次に、`env(safe-area-inset-bottom)` が効かないと勘違いして、フォールバック値の指定や JavaScript での加算など、三世代くらいの回避策を積み上げてしまった話。
実際には、「変数自体は存在していてゼロに解決されるので、フォールバックが走らない」という仕様理解の不足と、CSS-in-JS がビルド時にスタイルを壊していたこと、この二つが原因でした。
ここから、「`viewport-fit=cover` を付けているかどうか」とか、「ビルド後の CSS と、スタイルの優先順位をちゃんと確認すること」が大事だよね、という教訓を得ています。
ユーザージェスチャーが必要な API、たとえば `navigator.share` や `window.open` は、「重い非同期処理をまたぐと権利が失効する」という厄介な仕様も紹介されています。
そのため、共有用の画像は事前に生成しておいて、クリックされたタイミングではなるべく同期的に API を呼ぶ設計にしたり。
iOS では、先に空タブを `window.open` して参照を保持しておいて、処理が終わったら `location.assign` で遷移する、といった OS ごとに遷移手法を分ける工夫もされています。
また、アプリがバックグラウンドから復帰するときに、「モーダルだけが消えて、`body` に付けていた `inert` とか `pointer-events: none`、`overflow: hidden` が残り続けて、画面全体が触れなくなる」という問題もあるそうです。
これに対しては、`visibilitychange` イベントのあと、一フレーム待ってから「ダイアログがないのにロック用の属性が残っていたら、すべて剥がす」というガードロジックを入れて対処しています。
View Transition API と `backdrop-filter: blur()` を組み合わせると、ちらつきや二重のぼかしが出る、といった問題もありました。
ここは、背景を画像と `filter: blur()` の組み合わせに変えてみたり、小さい画像に `translateZ(ゼロ)` をかけて、GPU の挙動を安定させるなどの工夫でしのいでいます。
さらに小ネタ的な情報もたくさん並んでいて、
`overscroll-behavior` は `html` 要素に付けるべきだとか、`scrollend` イベントは Safari 二十六点二以降で使えるので、それ以前はデバウンスで代用するとか、
`border-radius` と `overflow: hidden` の組み合わせは `isolation: isolate` で直せる、といったテクニックが紹介されています。
進行中のスクロール中に発火する `touchmove` は `cancelable: false` で、`preventDefault()` できないので、まずフラグを確認しよう、という話も出てきます。
他にも、`<video preload="auto">` が読み始めてくれないときに `load()` を明示的に呼ぶ話、`<a download>` での Data URL と Blob URL の挙動の違い。
ITP によるクッキーの七日制限と、それに対応するための HttpOnly クッキーとサーバー側発行への移行、
`screen.orientation` が Safari 十六点四以前では使えない、といった、「仕様と実装の細かいズレが、実務で結構な破壊力を持つ」パターンを具体例で説明しています。
記事全体を通してのメッセージは、「iOS WebKit 上で Web アプリを開発するときは、CSS や DOM 的には正しいコードでも、OS やレンダリング実装の都合で簡単に壊れることがある」ということ。
まずは自分の仕様理解と、ビルドされた結果の CSS や JavaScript を疑って、それでも残る問題は実機でしっかり観測しながら、「あえて直さない判断」と、その理由まで記録しておく姿勢が重要だ、と締めくくっています。
。。。。
それでは、五つ目、最後の記事です。
こちらは、生成AIを使ったプロダクトの品質保証、いわゆる QA をどう設計するか、という話です。
まず最初に、国が出している AI 事業者向けのガイドラインをもとにして、想定されるリスクをできるだけ洗い出していったそうです。
ただ、その全部を QA 担当が目視でチェックしようとすると、とにかく工数が膨らみますし、判定基準もあいまいになってしまう。
結果として、「これは丸かバツかではなくて、三角だけどどうする?」みたいな判断がたくさん溜まっていって、プロセスが滞ってしまった、と。
そこで発想を切り替えて、「AI に関するリスクを全部 QA が見る」のをやめて、「どのリスクを誰が担保するか」を改めて分け直す方針に転換します。
法務とかコンプライアンスが持つべき判断と、QA が「リリースしていいかどうか」に直結させるべき判断を、きちんと切り分けていったんですね。
QA の仕事の中身も、三つの層に分解して考えています。
一つ目が、機械的なチェック。NG ワードが含まれてないか、文字数は条件以内か、数値の整合性は取れているか、といった、ルールに当てはめれば一発で判定できるところ。
ここは、なるべく機械に任せて自動化していきます。
二つ目が、AI による意味レベルのチェック。
ハルシネーション、つまり事実じゃないことをそれっぽく言ってないか、不適切な推測や表現が紛れ込んでいないか、といった「文脈理解が必要なスクリーニング」は、生成AIに担当させます。
三つ目が、人による最終確認。
ここでは、すべてのケースを人が見るんだけれども、「どこを見るか」の観点をかなり絞り込むことで、負荷を現実的な範囲に抑える、という設計です。
決定的にルールで判定できる部分は機械に任せて、微妙なニュアンスの見極めだけに人の判断力を集中させるイメージですね。
一方で、「AI が AI を評価する」という仕組み自体にも揺らぎがあります。
同じケースでも、コンテキストの与え方や順番によって判定が変わることがありますし、誤判定が出るたびにプロンプトにルールを足していくと、あっという間に「バベルの塔」みたいな、誰も理解しきれない巨大プロンプトになってしまう。
この記事では、この問題もちゃんと指摘しています。
そのため、誤判定が起きたときには、単純に個別ルールを追加するだけじゃなくて、「そもそもこの判定って、機械・AI・人間のどれが担当すべきなんだっけ?」というところまで一歩引いて見直します。
そのうえで、プロセス全体の設計として、品質保証のフローを組み立て直していくこと。
ここが、生成AI時代の QA の核心になるんだ、というまとめになっています。
AI プロダクトの品質って「モデルの精度」だけではなくて、「誰がどのリスクをいつ見るか」というプロセス設計なんだよ、という視点がとても印象的でした。
。。。。
ということで、きょうの「zenncast」、五本の記事を駆け足でおさらいしてみましょう。
まずは、Cerebras 社の社内ナレッジ検索基盤の事例。
PostgreSQL 一枚に全文検索とベクトル検索をまとめて、一日一万五千件以上の質問に答えつつ、その裏で権限設計の難しさと向き合っている話。
二本目は、Rust のテスト実行の仕組みから見える、「テストバイナリの数が CI の時間とコストを直撃する」という話。
テスト高速化のカギは、ビルド・実行されるテストバイナリの数をまず減らすことだよ、という内容でした。
三本目は、Obsidian を中心にした PKB を、人と AI の共有メモリとして動かす実践手順。
テンプレートを使ってレベルゼロ点五から始めて、Hermes や外部サービス連携、Daily ノートのループで回していく話でした。
四本目は、iOS Safari と WebKit 上の Web アプリ開発での、クセ強挙動とその回避策の数々。
正しいコードでも OS やレンダラの事情で壊れるから、仕様理解とビルド結果の確認、そして「直さない判断」まで含めて記録する姿勢が大事だよ、というお話。
そして最後は、生成AIプロダクトの品質保証。
「AI のリスクを全部 QA が見る」のではなく、リスクごとに担当を分け直し、機械・AI・人の三層に仕事を分解して、プロセス全体を設計し直していくことが大事だ、という内容でした。
気になった記事があれば、詳しい内容はこの番組のショーノートにまとめておきますので、あとでぜひチェックしてみてください。
番組への感想や、「こんなテーマを深掘りしてほしい」といったリクエストも、いつでも募集中です。あなたの開発現場での悩みや、最近ハマった技術ネタなんかも、どしどし送ってください。
それでは、きょうはこのあたりでお別れです。
お相手はマイクでした。また次回の「zenncast」でお会いしましょう。
良い一日をお過ごしください。