分析編では、Android WebView上のChatGPTを実測し、data-message-id、stopボタン、Copy/Share、無変化タイマーのどれも単独では回答完了を保証できないことを確認しました。
そこで今回は、調査結果をもとに「何を組み合わせれば完成候補として扱えるか」を絞り込みます。
最終的に採用した考え方
現在のロイド環境で、最小の完成判定候補として採用した条件は次の組み合わせです。
同一 data-message-id
+ stop-button = false
+ 同じ回答コンテナ内に finished-action が存在
+ 本文内容が 1200ms 変化しない
重要なのは、どれか1個ではなく複数の証拠が同時に揃ったあと、さらに本文の安定を確認することです。
1. 回答IDで対象を固定する
まず、監視対象を最新の実回答IDに固定します。
data-message-id は「完成したか」ではなく、どの回答を追っているのかを識別するために使います。
これがないと、過去回答のボタン再描画などを現在の回答の状態と取り違える可能性があります。
2. stop-button=falseを生成終了側の証拠にする
生成途中の長文では、本文が1秒以上止まってから再び伸びることがありました。
そのため、本文が静かだからといってすぐ完成候補には入れません。
stop-button=false を確認して、少なくとも生成中の停止区間を完成と誤認しにくくします。
3. finished-actionを同じ回答に紐付ける
CopyやShareなどの操作UIも使います。ただしページ全体にあるボタンを数えるのではなく、現在追っている回答と同じコンテナにあるものだけを対象にします。
このfinished-actionも単独では早く出ることがありましたが、生成終了側の追加証拠としては有効でした。
4. 最後に本文そのものが安定したかを見る
ここが今回の重要ポイントです。
文字数だけではなく、前回取得した本文文字列と今回の本文文字列を比較します。
if (currentText !== previousText) {
previousText = currentText;
// 安定待ちをやり直す
}
実測では、同じ len=3386 が連続しているのに本文内容が変化した場面がありました。文字数比較だけでは、この書き換えを見逃します。
完成候補タイマーは「本文が変わったら最初から」
stopが消え、finished-actionも確認できた時点で、初めて完成候補のタイマーを開始します。
ただし、その後に本文が1文字でも変われば古いタイマーを破棄し、最新本文から1200msを数え直します。
stop=false + actionあり
↓
candidate-start
↓
本文変化
↓
候補タイマーをリセット
↓
candidate-startやり直し
↓
1200ms本文不変
↓
completion-candidate
短い回答で起きたこと
ある試験では、stop=false とfinished-action確認後に完成候補を開始しましたが、約1.0秒後に本文が再変化しました。
1200ms待つ方式だったため、その変化を拾って古い候補を捨て、タイマーをやり直すことができました。
約7900文字の長文でも確認
さらに約7900文字の長文で試験しました。
生成途中には約1.88秒、本文が変化しない区間がありました。しかしその時点ではstopボタンがまだ生成中側だったため、完成候補タイマーには入りませんでした。
最終段階で stop=false、同じ回答のCopy/Share、最新本文が揃ったところから候補を開始。1200ms安定後に completion-candidate を出し、その後約42秒の観測範囲で本文変化は再発しませんでした。
つまり今回のログには、単純な無変化タイマーなら実際に誤判定し得た場面が存在し、新しい複合条件はそこを回避できていました。
なぜ「確定」ではなく「現在の採用条件」なのか
ここは注意が必要です。
ChatGPTのDOM構造や data-testid は、公開APIとして固定された契約ではありません。UI変更で変わる可能性があります。
そのため、この方式は「ChatGPT一般に永久保証された完成判定」ではなく、現在のWebView環境で実測して採用した判定条件です。
将来DOMが変われば、同じ考え方で観測し直す必要があります。
今回いちばん学んだこと
今回の開発で、プロの仕事と素人実装の違いをかなり具体的に見ることができました。
差は、単に複雑なコードを書けることではありません。
- 一時的に正しく見える状態を、そのまま確定とみなさない
- 1個のシグナルを信用しすぎない
- 途中停止や書き換えなどの例外を想定する
- 対象をIDで正しく対応付ける
- 実測ログで仮説を検証する
そして自分たちの進め方も、途中から既知事例確認 → 仮説 → 最小検証 → ログ確認 → 記録 → 次へ変わりました。
慎重なのは開発が遅いからではなく、最短で正しい場所へ到達するためでもあります。
次の段階
回答完了判定の調査は、ここで大きな山を越えました。
次はテスト用ログと本番判定を整理し、この完成通知を音声処理側へ安全につなげる段階へ進みます。
開発はクラフトだ。



コメントを残す