HITLとは何か — 人間の確認をエージェントの設計に組み込む
HITL(Human-in-the-Loop、ヒューマン・イン・ザ・ループ)とは、AIエージェントの処理の途中に人間の判断を挟み、要所で一度止めて確認を取る設計を指す。結論から書くと、HITLで難しいのは「止めること」ではなく「再開すること」である。人間の返事をどの選択として受け取るかを機械的に決められなければ、止めた処理は再開できない。そこで、質問と選択肢を会話ごとの状態として保存し、番号だけの返事を決定論的に解決する形に落ち着いた。本記事は、自前で運用しているエージェントに組み込んだ設計の記録であり、完成品ではなく模索中の記録である。
AIエージェントに確認を挟む理由 — 可逆かどうかで止める場所を決める
そもそも、なぜ自動で進めずに確認を挟むのか。理由は、判断の重みが一様ではないからである。
エージェントに任せた作業のなかには、間違えてもすぐ戻せるものと、戻しにくいものがある。後者の代表は、外部への送信、記録の削除、本番環境への反映である。これらは進める前に一度止めて人に聞く。逆に、読み取りや下書きの生成のように、やり直しが一操作で済むものは、いちいち止めない。
この線引きは、可逆かどうかで決めている。戻せるものは任せ、戻せないものだけ確認する。止める回数を増やせば安全側にはなるが、そのぶん作業の流れが切れる。確認の仕組みは、安全のための装置であると同時に、流れを止めないための装置でもある。止めすぎないことが、確認を機能させる条件になる。
自動化の話題では、どこまで任せるかが注目されやすい。国内のAI活用の議論も、まず導入の可否に焦点が当たりがちである。だが実際に効くのは、この地道な線引きのほうだ。派手な自動化より、どこで止めるかの設計が、運用の安定に寄与する。GeminiをiRacingのコーチ役に使った実験でも、任せる範囲を先に決めたほうが結果は安定した。
確認は「止める」より「再開する」のが難しい
エージェントに確認を求めるだけなら、難しいことはない。処理の途中で質問を出して、応答が返るまで止めればよい。
難しいのは、その先である。人間の返事は自然文で返ってくる。機械の側は、それを「どの選択が選ばれたのか」に落とさなければ、処理を再開できない。ここを曖昧にしたまま進めると、確認を出した意味がなくなる。
だから、確認の形式そのものを設計の対象にした。人間に自由に書かせて、その文章の解釈だけに頼る方向ではなく、選択肢を提示して番号で答えてもらう方向を軸にした。解釈の曖昧さを、そもそも減らすという考え方である。
HITLの選択肢は番号で答え、会話ごとの状態として保存する
確認を出すときは、質問と一緒に選択肢を番号付きで並べる。人間はその番号で答える。番号は記号なので、機械的に扱いやすい。
選択肢を出して答えやすい形で尋ねるというのは、人と人とのやり取りでも同じである。相手が答えやすい問いの立て方については、別の記事で整理した。
ただし、番号には弱点がある。単体では意味を持たない。何番目の何を指すかは、直前に提示した選択肢があって初めて決まる。つまり番号は、文脈に依存する記号である。この依存関係を、仕組みの側が覚えていられるかどうかが、その後の安定性を決める。
この点を軽く見ていた時期があった。番号だけの返事が意味を持たないまま流れ、機械の側は残っていない情報を推測するしかない状態だった。推測が当たれば動く。外れれば、同じ確認がもう一度返る。不安定に見えていたのは、この推測への依存である。
方針は単純である。確認を出した時点で、質問文と選択肢を会話ごとの状態として保存する。返事の入り口でその状態を読み出し、番号が来たら、保存した選択肢の何番目かを機械的に確定する。
保存する単位は、会話ごとに閉じている。保存先は会話の識別子ごとの独立したファイルにした。同じ会話の中で確認が繰り返されても、最新の選択肢だけが残るように上書きする。会話をまたいで混ざらないので、別の会話の番号が誤って結びつく余地が生まれない。
上書きにしたことには理由がある。ターン単位で厳密に残す形も考えたが、解釈すべき番号は常に直近の確認に対するものだから、会話を単位にして最新で上書きするほうが、状態の意味と一致する。厳密さを足すほど、古い状態が残って誤結びつきの余地が増える。この用途では、上書きが正しいと考えている。
有効期限も付けた。一定時間を過ぎた選択肢は無効として扱う。会話が中断したまま翌日になり、昨日の番号が今日の質問に結びつく事故を避けるためである。回答を解決したら、その状態は消す。一度使った選択肢を残すと、古い番号が新しい質問に誤って結びつく。
後から同じ確認が続いているのか、別の確認に置き換わったのかを判別できるように、作成時刻は初回のものを保ち、更新時刻だけを進める形にした。この一手間で、状態の履歴が読めるようになる。
HITLの番号解釈を厳密にする — 機械で決めるものと人間に委ねるもの
番号の解釈は、素朴に書くと事故が起きる。数字を探して当てはめるだけでは足りない。
たとえば「1. この内容で登録して」のような長い返事がある。これは選択肢そのものを引用した返事であり、番号だけの返事とは扱いが違う。私自身、推奨案を番号付きで丸ごと引用して返したことがある。それを「1番を選んだ」と機械的に解釈されると、引用の意図が消える。
そこで、番号だけの短い返事に限定する規則にした。全角の数字は半角へ正規化して扱い、丸囲みの数字にも対応する。文字数が一定を超える返事は、番号だけの回答とはみなさない。長文は文脈から読むべきもので、機械的な解決には向かない。
番号が選択肢の範囲を外れている場合も解決しない。三択の確認に「5」と返ってきたら、それは選択ではなく別の意図である。存在しない番号を無理に当てはめると、別の選択肢を選んだことにしてしまう。
番号が解決できた場合は、確定した選択として扱い、同じ内容を再確認しない。解決できなかった場合は、直前の確認事項として提示し、文脈からの判断を委ねる。この二段に分けた。
要は、機械的に決められるものは機械的に決め、決められないものは人間の判断に委ねる。境界をはっきりさせたことが、この設計の本体である。曖昧な中間を作らないことが、確認の往復を減らす最短の道だった。
解決できたことを文脈に書き添えるのも大事である。確定済みであることと、再確認せずに進めることを明示しないと、せっかく解決しても同じ問いを返す余地が残る。解決できなかった場合も、直前の確認事項として提示する。何も注入しなければ、選択肢の存在自体が失われたままになる。存在しないより、存在して解釈に委ねるほうが安全である。
失敗が見えないことが最大の難所 — HITLが静かに壊れる理由
この仕組みで厄介だったのは、壊れ方が静かなことである。番号が解釈できなければ、同じ質問が返るだけである。エラーは出ない。ログにも異常は残らない。動作としては正常に見える。
つまり、気づく手段が「人間が違和感を持つ」しかない。確認のやり取りは日常的すぎて、監視の対象にも設計の対象にもなりにくい。だが、その一点が安定しないだけで、作業の流れは何度も止まる。地味な部分ほど、状態を明示して持たせる価値がある。
状態に依存する仕組みを書いたら、その状態がどこに、どの粒度で、いつまで残るのかを明示する。動いているように見えるものほど、状態が本当に残っているかを確かめる。今回の設計でやったのは、突き詰めればこの明示である。
HITLの自由文は捨てない — 決定論と文脈解釈の二段構え
ここは誤解を招きやすいので、実装に即して書いておく。よく「自由文の解釈は採らなかった」と要約されるが、いまの実装はそこまで割り切っていない。
決定論的に確定するのは、番号だけの短い返事に限られる。それ以外の返事、たとえば番号を使わない自然な文章や、選択肢の範囲を外れた番号は、機械的な解決の対象から外れる。ただし、対象から外れることと、捨てられることは違う。
解決できなかった場合、仕組みは直前の確認事項と選択肢をそのまま文脈に載せ、モデルの解釈に委ねる。つまり、そこには自由文を自然言語で読む余地が残っている。番号で確定できるものは決定論的に決め、確定できないものは文脈解釈に回す。この二段構えが実装の姿である。
だから、捨てたのは自由文の解釈そのものではない。すべての返事を最初から自由文の解釈だけで処理する設計を、軸から外したのである。軸を番号に置いたうえで、軸から外れた入力を文脈解釈へ逃がす。柔軟さを消すのではなく、柔軟さの置き場所を変えた、というほうが正確である。
この段の分け方には弱点もある。文脈解釈に回った返事は、結局モデルの推測に依存する。だが、選択肢が必ず文脈に載るようになったぶん、推測の材料は前より確実に増えている。当たる確率は上がるが、外れる可能性は残る。ゼロにはできない。そこをゼロだと言い張らないことが、この設計では大事だと考えている。
HITLの実装と検証 — 保存・取得・消去と「通らないこと」の確認
実装は、状態を扱う既存の層に三つの操作を足す形にした。保存、取得、消去である。保存は上書き可能にし、取得は会話の識別子で引く。消去は解決した時点で行う。これ以上細かい操作は要らなかった。状態の意味に対して操作が多すぎると、どれをいつ呼ぶかが曖昧になる。
解釈の部分は、ループの入り口で一度だけ走らせる。毎ターン呼ばれる場所なので、重い処理は置けない。保存された選択肢を読み、番号が解決できればその旨を文脈へ差し込み、できなければ直前の確認として提示する。ここは判定と注入だけで完結させた。
状態を持つ側と、それを使う側を分けたのも意識した点である。保存の形式を知っているのは状態の層だけで、ループの側は「直近の選択肢があるか」しか見ない。形式を変えても、使う側に波及しない。小さな分離だが、あとから保存先を変えるときの手戻りが減る。
直したあとは、番号だけの返事で確認を再開できることを確かめた。短文の番号、全角の数字、丸囲みの数字を順に試し、いずれも同じ選択に解決されることを確認した。あわせて、長い返事が誤って番号として解決されないことも確かめた。
通ることより、通らないことを確かめるほうが重要である。解決してほしくない入力が解決されないことを見ておかないと、あとで思わぬ結びつきが起きる。選択肢の範囲外の番号、有効期限を過ぎた状態、解決済みの状態。これらが正しく効かないことを、通る場合と同じだけ確かめた。
壊れ方が静かな仕組みでは、検証も静かにする必要がある。エラーが出ないからといって、正しく動いているとは限らない。だから、確認を再開できたかどうかを、毎回の作業で意識して見るようにしている。動いているように見えることが、この種の仕組みではいちばん当てにならない。
反映は会話を止めずに行う
この改修は、会話を止めずに反映できる形で入れた。確認の仕組みそのものを直す作業で会話を切るのは本末転倒である。ファイルの更新が読み直され、新しい会話から新しい動作になる。進行中の会話はそのまま走り切る。
止める仕組みを直すときこそ、止めないで直せる形にしておく。これは確認の仕組みに限らず、運用中の道具を触るときの原則にしている。
採用しなかった設計案と、HITL設計の現在の限界
検討したうえで、いまの形に落ち着かなかった案が二つある。
一つは、番号に寄せず、最初から最後まで自由文の解釈だけで処理する案である。柔軟に見えるが、解釈の正しさを保証できない。誤解釈は静かに起きるので、気づきにくい。軸を番号に置いたのは、解釈に頼る範囲を狭めるためである。ただし、先に書いたとおり、自由文の解釈を切り捨てたわけではない。番号で決められない部分は、いまも文脈解釈に委ねている。
もう一つは、選択肢を保存せず、要約の中に必ず残すよう指示で縛る案である。指示は破られうるし、破られたことに気づきにくい。破られているかを外から観測する手段もない。状態として持つほうが、無ければ動かないという意味で確実である。指示ではなく状態で解く、というのがこの設計の一貫した方針になった。
正直に書くと、これはまだ完成していない。番号だけの返事には強いが、番号を使わない自然な返事は、決定論的な解決の対象外である。ただし、その場合も直前の選択肢を文脈に載せるので、モデルが解釈する余地は残っている。切り捨てているのは機械的な確定であって、自由文そのものではない。
選択肢の保存先は一時的な領域で、永続化はしていない。会話が続いている間だけ正しければよいと判断している。再起動をまたいで残す必要が出たら、その時点で保存先を変えればよい。直したのは推測に頼る範囲を狭めたことであって、推測を消したわけではない。だが、選択肢が必ず文脈に載るようになったので、推測の材料は確実に増えた。材料が増えれば、同じ推測でも当たる確率は上がる。
これから詰めたいのは、確認の頻度の設計である。確認を増やせば安全になるが、作業の流れは止まる。どこで聞き、どこで聞かないかは、仕組みではなく運用の設計であり、まだ経験が足りない。自然文の返事をどこまで決定論の側へ引き寄せるかも、同じく未解決である。
HITL実装で踏みやすい落とし穴
- 選択肢を文脈に残さないこと。番号が意味を失い、推測に依存する。
- 選択肢の範囲外の番号を無理に当てはめること。別の選択を選んだことになる。
- 解決した後に状態を残すこと。古い番号が新しい確認に結びつく。
- 有効期限を付けないこと。中断した会話の確認が後から効く。
- 解決済みであることを文脈に書かないこと。同じ確認がもう一度返る。
- 自由文を「一切読まない」と誤って設計すること。文脈解釈の余地を自分で潰すことになる。
- 壊れ方が静かなことを見落とすこと。エラーが出ないので、人間の違和感しか検知手段がない。
まとめ — 自前エージェントにHITLを組み込むときの要点
- HITL(Human-in-the-Loop)とは、エージェントの処理の途中に人間の確認を挟む設計である。
- 確認を挟む仕組みは、止めることより再開することが難しい。
- 選択肢は提示した時点で状態として保存し、番号を決定論的に解決する。
- 決定論の対象は番号だけの短い返事に限定し、それ以外は直前の選択肢を文脈に載せて解釈に委ねる。
- 自由文の解釈を捨てたのではなく、あらゆる返事を自由文だけで処理する形を軸から外した。
- 解決した状態は消し、有効期限を付ける。古い確認が後から効かないようにする。
- 機械で決められるものと、人間に委ねるものの境界をはっきりさせる。
- 失敗が見えない壊れ方こそ、状態を明示する形で直す。
- 反映は会話を止めずに行う。
確認のやり取りは、日常的すぎて設計の対象になりにくい。だが、その一点が安定しないだけで、作業の流れは何度も止まる。地味な部分ほど、状態を明示して持たせる価値がある。これはまだ模索の途中であり、確認の頻度と、自由文をどこまで決定論の側へ引き寄せるかは、次回以降に詰めたい。