ToWow / エンジニアリングノート

成功の確定は識別情報の照合後に

一つの項目が課題を取り違えたのに、バッチ全体はすでに成功と記録されていた。今回直したのは確定の順序だった。

一括評価の一項目で、返された case_id が gold-pair-078 から gold-pair-079 に変わっていた。タスク番号は元の課題を指していた。障害記録から再構成した識別フィールドは次のとおりだ。

task_id:      gold-pair-078::gold-pair-078-candidate-127
candidate_id: gold-pair-078-candidate-127
case_id:      gold-pair-079

この返却は 20 候補を含むバッチの一部だった。JSON は解析でき、構造検査も通った。その後の項目別検査で誤りを見つけたが、バッチの受領記録にはすでに status: ok と書かれていた。元の障害記録とコードの所在

結果は正式な評価の完了には至らなかったが、復旧上の落とし穴を残した。古いファイルの「成功」が後の失敗記録と矛盾していた。成功の受領記録だけを読む復旧プログラムなら、このバッチを再び受け入れてしまう。

修正箇所は明確だった。全項目の識別情報を照合してから成功にする。 リクエストがどの課題に属するかは呼び出し側が知っている事実であり、プログラムが照合すべきで、モデルの自己保証に任せるものではない。

バッチ全体を検収してから成功を書く

型と構造だけを定める Schema では、異なる二つの文字列がどちらも合法になりうる。動的 Schema で識別値を絞ることもできる。本システムはさらに、返却後に実リクエストと直接照合してバッチを検収する。

現在の実装は、最上位の case_id、タスクの網羅、項目構造、識別情報を順に調べる。網羅性は数と集合の両方を比べ、欠落、重複、追加タスクを検出する。その後 task_id、case_id、candidate_id、候補型を照合する。

以下は中心的な順序の説明用略記だ。実際の実装には段階別 Schema、受領記録、再試行処理もある。

expected = {task["task_id"]: task for task in requests}
if len(expected) != len(requests):
    raise InvalidBatch("请求自身包含重复任务")

ids = [item["task_id"] for item in response["items"]]
if len(ids) != len(expected) or set(ids) != set(expected):
    raise InvalidBatch("任务缺失、重复或新增")

for item in response["items"]:
    task = expected[item["task_id"]]
    for key in ("task_id", "case_id", "candidate_id"):
        if item[key] != task[key]:
            raise InvalidBatch(f"身份字段不一致:{key}")

commit_success(response)

番号で関連付けるので、返却順は変わってよい。冗長なフィールドも照合することで、同じ対象が後段のモジュールによって別の課題に見える事態を防ぐ。あるモジュールは task_id で 078 を見つけ、別のものは case_id で 079 に分類する、といった食い違いだ。

プログラムは 079 を勝手に 078 へ直さなかった。食い違いだけでは、モデルが番号を写し間違えたのか、別の課題を理解したのか判断できない。全体を失敗とし、生の返却を残すことで、両方の可能性を調べる余地を守った。

一括検収には費用がある。正常そうな残り 19 項目も再利用できない。代わりに確定境界が単純になる。一部受け入れを支援するなら、各項目の独立検証、欠落補完、重複確定の扱いを別に定義する必要があり、それは別の復旧設計だ。

古い成功ファイルも再検査する

新規呼び出しを直しても過去の受領記録は残る。そこで取り込みコードも各項目を再検査するようにした。過去の失敗ログと一致する識別エラーがあれば、バッチ全体を診断資料として保ち、どの項目も再開用チェックポイントへ入れない。

すでに発生した呼び出し費用は引き続き計上する。再起動で実行ディレクトリを変えても、それまでの試行は消せない。消すと、最後に報告する「利用可能な結果一件あたりの費用」が、最終成功分だけになる。

バッチの大きさは総費用で選ぶべきだ。大きいバッチは共通入力の費用を薄めるが、全体再試行は一項目の誤りの影響を拡大する。後続の設計では、再試行を使い切った指定バッチだけに決定的な分割を認め、単項目の失敗でも実行を終えられるようにした。分割は範囲を限った復旧手段であり、無限再試行の理由にはしない。

回帰テストでは最初の返却項目の識別情報を意図的に壊し、三つを調べる。その試行に失敗の受領記録を書くこと、成功チェックポイントのパスが空であること、その後の正しい返却で初めて成功を書くことだ。今回の障害が通り抜けた隙間そのものを測っている。

バッチ処理の完了計数が増えるのは、返却と依頼が一致し、全体の検収が終わり、成功の確定が行われた後であるべきだ。

Next step

If your team is putting agents into real work and wants to talk about Flowness or enterprise AI deployment, email us your industry and the problem you want to solve. hi@towow.ai