スマートフォンがスリープしてもチャットを維持する
Mythexのエージェントは、1つのリクエストに対して数分間作業し続けることがあります。その様子をスマートフォンで見ている場合、その間にロックされたり、アプリを切り替えたり、ネットワークが変わったりするかもしれません。それで問題が2つ起きていました。1つ目は、チャットが「ページが再び表示された」というすべての信号を、接続が切れた証拠とみなし、「接続中…」の表示のままループしてしまうことでした。2つ目は、新しいチャットの最初の返信の途中でスマホがスリープしてしまい、手動で再読み込みするまで、その返信の最初の数ブロックしか戻ってこないことでした。どちらも修正済みです。
チャットが実行中のターンを追う仕組み
エージェントが作業している間、チャットはサーバーとのストリームを開いたままにしています。各ステップ(思考、ツール呼び出し、返信の一部)は、イベントとして届きます。ストリームが切れると、チャットは再接続し、サーバーは直近120秒分を保持したバッファから、最近のイベントを再送します。ターンが終わると、チャットはサーバーに保存された履歴も要求し、表示していたものと保存されたものを照合します。この最後のステップで、ストリームが取りこぼしたものを拾います。
何も起きていないときに、接続が生きているか死んでいるかを判断するため、サーバーは15秒ごとに小さな「ping」を送ります。
バグ1:起きた瞬間に、正常なストリームを切断していた
ブラウザは、ページが再び表示されたときにそれを通知します。タブを切り替えたとき、画面のロックを解除したときなどです。私たちはこの通知を、接続を確認するきっかけとして使っていました。実際には、ストリームを中断して再接続していました。非表示の間に死んだと見なしていたからです。
スマートフォンや組み込みブラウザでは、この通知はストリームが切れることよりはるかに頻繁に発生します。タブを切り替えるたび、ロックを解除するたびに、場合によっては組み込みブラウザでは数秒ごとに発生します。その一つひとつが、正常に動いているストリームを中断させていました。チャットは再接続し、再生を受け取り、また中断され、結果として「接続中…」の表示で止まってしまいました。そして、エージェントがまだ作業中なのに、ターンが終わったと表示していました。
修正:ストリームが話しているかどうかで判断する
チャットは今、プロジェクトごとのストリームが最後に何かを受け取った時刻を記録します。イベントも15秒ごとのpingも、どちらも含みます。「再び表示された」という信号は、25秒間沈黙していたストリームだけを中断します。これはping間隔と遅いネットワークの遅延を足した時間より長く、生きているストリームが該当することはありません。テストでは、pingが生きていることの証拠として数えられること、直前にpingを受け取ったストリームは死んだものとは扱われないことを確認しています。
この件から得た原則は、ページが表示されているかどうかは、ページについて何かを語るだけで、ネットワーク接続については何も語らないということです。接続に直接確認してください。
バグ2:スマホが起きたあとに、返信が途中までしか表示されない
こちらは、iOSシミュレーターで毎回再現できました。
- 新しいチャットを開き、時間のかかるリクエストを送ります。
- ビルドの間にスマホをスリープさせます。
- ターンが終わり、2分間の再生バッファの期限が切れてから戻ってきます。
するとチャットには、返信の最初の数ブロックだけが完了済みとして表示され、ページを再読み込みするまでそのままでした。
原因
新しいチャットには、最初のメッセージが作成するまで、スレッド(保存された会話)がありません。スレッドが作られるのは送信関数の内部で、その時点では、送信処理が既に照合ステップを取り込んでいて、その照合ステップはまだスレッドがないと認識していました。そのため、新しいチャットの最初のターンが終わったとき、照合はスレッドがないことを見て、何も要求せずに静かに戻っていました。
通常は、誰も気づきませんでした。ストリームがターン全体を配信していたからです。しかし、ターンの間にスマホがスリープし、120秒の再生バッファが切れてから戻ってきた場合、ストリームから欠けたイベントを取得できませんでした。そして、その隙間を埋めるはずの照合は、一度も実行されていなかったのです。
修正
- 送信処理が、そのターンがどのスレッドに属するかを照合に伝えます。 そのため、新しいチャットの最初のターンも、ほかのターンと同じように照合されます。答えが返ってくるまでに別のチャットを開いていた場合は、その結果は間違った場所に表示されず、破棄されます。
- 失敗した照合は、もう一度試します。 悪い接続でスマホが起きると、ターンが終わっていてもそれを取得できないことがあります。その失敗は記憶され、タブが再び表示されたとき、またはネットワークが戻ったときに、チャットは再度要求します。
- 「作業時間」を正直に表示します。 各返信には、エージェントが作業した時間が表示されます。タブがサーバーのターン終了メッセージを見ていなかった場合、以前はその時間を、送信ボタンを押してからスマホが起きるまで、タブ自身の時計で埋めていました。その時間には、離れていた時間も含まれていました。今では、その数字は推定値としてマークされ、履歴が届いたら保存された実際の所要時間に置き換えられます。
この件から学んだこと
- 実際に人が使うデバイスで、使われ方どおりにテストしてください。 2つのバグとも、「正常に動くストリーム」と「ポケットの中のスマホ」の隙間に潜んでいました。
- 一度も実行されないフォールバックは、フォールバックではありません。 照合ステップは、まさにこのケースを拾うために存在していましたが、静かに飛ばされていました。
- 回復処理も再試行してください。 スマホが起きた瞬間は、接続の状態が最も悪いことがよくあります。
- 推測を事実のように見せないでください。 間違った時計からの所要時間は、何も表示しないよりも悪いです。ラベルを付けるか、置き換えてください。
同じ作業で、ターンの実行中に表示されるものもいくつか整理しました。シェルの手順は、cdではなく、実際に作業をしているコマンドでラベル付けされるようになりました。チャット内のスクリーンショットは、ページの左上を保持し、列に収まります。新しいプロジェクトは、再読み込みなしで生成された名前を受け取るようになりました。
スマートフォン向けに自分のアプリを作る場合は、モバイル対応のウェブアプリの作り方とネイティブアプリとプログレッシブウェブアプリの違いで基本を解説しています。チャットそのものについては、チャットのドキュメントをご覧ください。