ChatGPT Proのディープリサーチが空振りした日|送信は成功、プロンプトは空だった

開発book-knowledge-base

ChatGPT Proのディープリサーチが空振りした日 | 送信は成功、プロンプトは空だった

自炊してOCRにかけた本が352冊、チャンクにすると47,216件たまっている。この山からオリジナルのコンテンツを作る、という企画をトラックA・B・Cに分けて動かしていて、今日はそのうちのC——市場側をディープリサーチで調べる——を回した。

ChatGPT Pro のディープリサーチを Chrome 経由で叩く。ログイン済みの Chrome に接続して、タブを開いて、ブリッジを注入して、Deep Research のトグルを入れて、1,856文字のプロンプトを送る。ここまでは滑らかに進んだ。「生成が始まっています。待つ間に蔵書DBを調べます」という報告まで返ってきた。

うまく走っているに違いない、と思っていた。

計画カードの一行目で手が止まった

画面に出ていたディープリサーチの計画カードを見て、指が止まった。

一行目が「元リクエスト全文と目的を確認する」。何を調べるのか、どのデータベースに当たるのか、一つも書いていない。1,856文字のプロンプトを読んだあとに立てる計画には見えない。

「これ本当に入ってますか?プロンプト」と聞いて調べさせたら、その通りだった。ChatGPT 側に届いていたユーザーメッセージは15文字。「Deep Research」というチップだけで、本文が丸ごと落ちていた。

原因は document.execCommand('insertText') が複数行テキストを飲み込めていなかったこと。改行をまたいだ瞬間に本文が消える。厄介なのは、送信処理のほうが成功として sentChars: 1856 を返してくることで、実行ログだけ見ていると成功したようにしか読めない。空のリクエストに対して、ChatGPT は律儀に「元リクエストを確認する」という汎用計画を立てて走り出していた。

自分でリサーチを止めて、入れ直しを頼んだ。

貼り付け方式に切り替えたら、今度は別の場所が欠けた

insertText を捨てて、paste イベントでテキストを流し込む方式に変えてもらった。送信前に文字数を検証する手順も足させた。

一度で入りはしなかった。

  • 貼り付けると 1行目だけ欠ける。ダミー行を先頭に付けて貼り直し、全セクションが揃ったことを確認
  • テキストは入ったのに、今度は Deep Research のチップが消えていた。テキストを保ったままモードを入れ直し
  • チップと本文の両方が揃った状態で送信。1,891文字

今度は計画カードの中身が変わった。調べる対象と手順が具体的に並んでいる。同じ画面の同じカードでも、空振りのときとは別物になる。ここで初めて「入った」と判断した。

自動操作は「送りました」としか言わない。届いたかどうかは、送った側とは別の窓——この場合は相手の画面に出た計画カード——で確かめないと分からない。今回はカードの一行目が救ってくれた。

走っている11分の使い道

ディープリサーチは11分走り、51件を引用し、380件を検索した。その間ずっと待っているのももったいないので、蔵書DBのテーマ別集計を並行で走らせておいた。冊数とチャンク数を出せば、どのテーマなら手元の素材だけで語れるかが見える。冊数は一般語を拾って上振れするので、深さの指標にはチャンク数を見る。

ついでに「まだ誰もやっていない」と思っていた仮説テーマを自分でも検索させたら、そのうち2つは数分で崩れた。すでに講座が存在していた。手元で回せる裏取りは、リサーチの完了を待つ理由にならない。

ただしディープリサーチ本体の結果については、途中で方針を切り替えた。11分と380件の検索をかけたものをもう一度自分で裏取りするのは二度手間になる。裏取り済みとして扱い、次の判断に進むことにした。

レポートを取り出す

完了したレポートは、画面には出ているのに DOM から読めなかった。クロスオリジンの iframe の中に入っている。API から生テキストを取る経路も試させたが、結局いちばん確実だったのは ChatGPT 自身のエクスポート機能で、マークダウンとして書き出したら 35,417 バイト・164行が丸ごと落ちてきた。

これを一切加工せず、非公開の置き場にそのまま保存させた。要約は後からいくらでも作れるが、生出力は消えたら戻らない。

豆腐文字の正体は引用マーカーだった

保存したレポートを読み返そうとして、本文のあちこちに豆腐文字(□)が挟まっているのに気づいた。

正体は、ChatGPT がエクスポート時に埋め込む引用マーカーだった。U+E200 で開いて U+E201 で閉じるブロックで、中身は citeturn41search5 といった参照ID。私用領域の文字なのでフォントに字形がなく、豆腐で表示される。

対応は2本立てにした。原文は無改変のまま残し、読む用には除去した版を別に置く。除去後に残った U+F8F0 は、今回いじっていないページにも同数出ていたのでサイトのアイコンフォント由来と切り分けた。同じ「文字化け」でも出どころが違えば触ってはいけない。

リサーチを受けて、テーマと読者を詰める

出てきたレポートを踏まえて、コンテンツのテーマ候補を並べ直した。ここは自分の判断がいちばん入るところで、Claude Code には書き直しを何度もさせた。

陳腐化しにくさが最初の物差しになった。制度が動けば中身も動くテーマか、年が変わってもほとんど変わらないテーマか。それと、自分がその論点をどれだけ手の内に持っているか。ニーズがあっても自分が得意でない領域を主戦場に選ぶのは無理がある。

読者像の切り方は、指摘して組み直させた。当初の案は職業で読者を切っていた。だが、なぜその人が財務諸表を読んでいるのかを考えると、切るべき軸は職業ではなく用途だった。同じ職業でも、求めに応じて調べる場合と、助言のために読む場合では、必要な出口が別になる。用途を軸に読者・製品構成・目次を組み直させた。

書き直すたびに Codex にレビューを投げた。合計5周かかった。潰した指摘はこのあたり。

  • 複数の計画書でテーマ名が食い違っていた(片方に古い名前が残っていた)
  • スコア表に構造的な偏りがあり、比較の土俵が揃っていなかった
  • 章立てを直したのに、初回リリースの節だけ古い章構成のままだった
  • 見出しが「改訂版」のままで中身と食い違っていた

途中で Codex が Not logged in を返して止まり、ログインし直してから再開した。

手元にある本と、まだ持っていない本

最後に、テーマごとにどの書籍を組み合わせるかの一覧を作らせた。分け方は2つ。

  1. すでに蔵書DBに入っている本 — チャンク数で素材の厚みが分かる
  2. まだ持っていないロングセラー — 買って読み込む候補

2つめが今日の主眼だった。これから取り組むテーマで長く売れ続けている本があるなら、それを読んで「何がウケているのか」を自分で理解しないと話にならない。棚卸しをさせたら、今回のテーマのいくつかで蔵書が薄いことがはっきり出た。

Codex にテーマごとの書籍リサーチを出典URL付きで投げ、返ってきたものを一覧に統合させた。最初の1回は前のレビュー文脈を引きずって書籍リストを返さなかったので、新規セッションで投げ直している。外部リンクに target="_blank" が抜けていたのは、プロジェクトルール違反なのでその場で直させた。

今日の学び

  • 自動操作の「送信成功」は届いた証拠にならない。 送った文字数を返す実装は、本文が落ちていても成功を返す。相手側の画面に出た結果で検証する
  • execCommand('insertText') は複数行で落ちる。 貼り付けイベント方式に切り替える。ただし1行目が欠ける癖があるのでダミー行で吸収する
  • モードのトグルとテキスト入力は独立に壊れる。 片方を直すともう片方が外れることがあるので、送信直前に両方を見る
  • 生出力は無改変で残し、読む用は別に作る。 加工した版しかない状態にすると、後から検証できなくなる
  • 文字化けは出どころで切り分ける。 今回は引用マーカーとアイコンフォントの2種類が混ざっていた
  • 読者は職業ではなく用途で切る。 同じ職業でも、何のために読んでいるかで必要な出口が変わる

買う本を決めるところまでは進んだ。ただ、読んで「何がウケているのか」を掴むのはこれからで、そこが分かるまでは目次案もあくまで仮置きのままになる。