その日のあらすじ
テストが全部通ったのに、品質を見るはずの意味の判定が一度も走っていなかった、という事故を踏みました。研究予稿という自然言語の長文を、テストが通るまで書き直す開発ループで書いていたときのことです。査読役の認証が切れると意味の判定はまるごと飛ばされ、通ったように見えるテストが安全の証拠にならなくなっていたのです。
テストは、文脈を見ずに判定できる決定論的チェックと、CLI 経由の LLM を査読役として呼ぶ意味の判定の二層からなり、判定は合格と不合格に、査読を回せなかったことを表すエラーを加えた三値をとります。
この仕組みは、研究予稿と研究運営のリポジトリの運用を始めて約 1 週間後に入れ、そのあと二週間以上かけて、検査の追加、二本目の予稿への横展開、査読役の認証の復旧とモデルの更新を続けました。
対象読者と持ち帰れるもの
この記事は、テストの合否を見ながら文章を書き直す開発ループを、コードだけでなく自分の執筆にも持ち込みたい開発者と書き手に向けた記事です。持ち帰れるものは三つあります。一つ目は、文脈を見ずに強制すると誤判定する検査は外す、という切り分けの基準です。二つ目は、査読を回せなかった状態を品質の合否に混ぜない、三値の設計です。三つ目は、断定を一律に落とすと本文が留保ばかりになって主張が立たなくなった失敗と、その直し方です。素材は、研究予稿と研究運営のリポジトリで、この仕組みを二週間以上動かした運用の記録です。
コードの開発ループを長文の執筆へ持ち込む
コードを書くときは、書いたそばからテストが合否を返してくれます。この開発ループを、論文や仕様書や提案書のような自然言語の長文にも持ち込めるか、というのが今回の主題です。対象にしたのは研究予稿です。テストの定義を YAML に置き、pytest を土台に組んだテストランナーがパラメータ化で 1 項目を 1 テストに展開する仕組みで、テストが通るまで書き直す運用が回るようになってきました。
最初に断っておくと、このテストは執筆を代行する道具ではありません。僕が予稿へ実際に入れてきた指摘を一つずつルールに落として検査にしているだけで、機械が再現できるのはその過去の指摘までで、何を書くかの判断は相変わらず書き手の仕事です。
文脈なしで強制すると誤判定する検査を外す
外した検査と残した検査を分ける軸は一本です。文脈を見ずに単発で強制すると正当な用法まで誤判定してしまう検査を外し、文字数や出現回数のように検査する値へ範囲を持たせれば誤判定しない検査を残しました。当初は、一文の長さを強制する、段落数を厳密に強制する、文脈なしで単発の禁止語を弾く、といった硬すぎる強制型の検査ばかり入れていたのですが、文脈があってはじめて意味が通る書き方まで誤判定して書き方の自由を奪うので、一度作ってからランナーの外へ出し、古い実装として削除しました。
この切り分けの土台に置いたのは、論文の評価を決定論的チェックだけで担えるとは考えない、意味と論理の判定を重視する、という方針です。空白や組版用の命令を除いた文字数の範囲、指定した語が入っているか入っていないか、正規表現の一致と出現回数、といった文脈を見ずに決められる項目だけを、ランナー内のローカル関数で実装した決定論的チェックへ寄せ、意味と論理と章の構成の整合は、CLI 経由の LLM を査読役として呼ぶ層に任せました。速くて安い判定は決定論的チェックへ、文脈の要る判定は査読役へ寄せ、両方を一つのランナーに同居させています。
エラーは品質の判定から切り離す
査読役には、与えた基準だけで合否を判定し、違反箇所を漏れなく全件列挙するよう固定の指示を与え、応答は判定と該当箇所と理由の構造化データへ強制しました。一件ずつ小出しに指摘されて直しが長引くのを防ぐためです。応答に組版用のバックスラッシュが混ざると構造化した出力の解析に失敗するため、補正してから解析をやり直す処理も足しています。
三値の三つ目のエラーは、認証切れやタイムアウトや応答の解析失敗で査読役をそもそも回せなかった状態を指します。これを本文の品質判定から切り離し、不合格には混ぜません。不合格が増えたときに本文の問題なのか基盤の問題なのかを分けられないと、このループは目的を果たさないと考えました。
実際、査読役の認証が切れて全査読役がエラーとして省かれ、テストはすべて通ったように見えるのに意味の判定は一度も走っていない、という事故を踏みました。テストがすべて通っても、意味の判定が実際に走ったとは限らない、というのがこのとき明確になった教訓です。
断定は観察の範囲で通し、ぶれは多数決で抑える
判定の軸のなかで、断定の扱いに最も調整を重ねました。観察対象の規模を見ずに断定したら不合格、と一律に落としていた時期は、本文が留保の言い回しばかりになって主張が立たなくなりました。そこで、観察対象が何名で何件規模か、といった観察対象の規模を査読役の指示へ差し込み、その範囲を超えた一般化だけを不合格にして、範囲内に収めた理由つきの断定はそのまま通す形へ直しました。あわせて不合格のときは、言い回しの直しで済むのか、データ収集へ戻るべきなのかの区別まで、査読役に必ず返させています。
査読役のぶれを抑えるために、1 回目が合格ならそこで止め、合格以外のときだけさらに 2 回判定して、3 回分の多数決を取る実装にしました。ただ、これは速度を優先した折衷で、最大 3 試行で同じ予稿に同じ判定が返る再現性が実用に足るかは測っていません。1 回だけの査読役には判定のばらつきがあり、安定した合意には試行の反復が要るという指摘は見かけますが、必要な回数を裏づける一次文献はまだ確認できていません。速さと再現性の折り合いをつけた、という以上のことは今は言えないのです。
決定論的チェックは CI へ、査読役は手元のループへ
導入から実用に耐えるまでは実質二日で、そこからの二週間あまりは、論文の書き方の一次文献と自分のフィードバックに照らして少しずつ検査を足していきました。章単体の検査では章と章の間の見落としが残るため、全章を通して問いから主張までの筋が一本通っているかを見る、全体を通すテストも後から新設しています。
運用の分担も決めました。決定論的チェックだけを CI で毎回自動実行し、意味の判定を担う査読役は、手元で回す手動のループ専用にします。仕組みは一本の予稿で作り込み、別テーマの予稿へも同じループを横展開しました。
機械が差し出せたのは判断材料まで
ここまで動かして、機械が担えた範囲が見えてきました。機械にできたのは、僕が繰り返してきた指摘を判断材料として差し出すところまでです。テスト駆動で予稿の品質そのものが上がったかどうかは、不合格の件数の推移という一次データを取っていないので断定できません。動かして見えたのはそこまでで、その先を道具の効能として言い切るのは控えます。
機械に任せられる判定と、人が指摘し続けるしかない判定の境目は、この先も動いていくはずで、それがどこまで動くのかは、この運用を続けながら見極めていきたいと考えています。