Antigravity Agent を本番に投入したあとに最初に直面するのは「すべての出力を人がレビューする運用は持続しない」という現実です。私自身、ある業務自動化のエージェントを運用しはじめて2週間目に、レビュー待ちのキューが300件を超えて、結局そのキューを消化するために週末を潰すという本末転倒な状況を経験しました。
かといって、レビューを完全に外してしまうと、たまに混ざる「自信満々で間違っているケース」が下流の業務やデータベースを汚染して、後から戻す方がよほど高くつきます。ここでよく語られる解は「Human-in-the-loop(HITL)」ですが、HITL は「人を入れる」とだけ書いてあって、誰のどの出力をいつ人に渡すかという肝心の設計は読者に委ねられている、という意見を私は持っています。
この記事で扱うのは、その「いつ人に渡すか」を機械的に判断するための 自信スコア(confidence score) の設計です。出力ごとに 0〜1 のスコアを付け、閾値で「自動承認 / 人間レビュー / 自動拒否」の3層に振り分ける graduated approval(段階的承認)パターンを、Antigravity Agent と Python での実装例まで踏み込んで解説します。
なぜ「全レビュー」も「全自動」も失敗するのか
エージェント運用には3つのモードがあります。それぞれの失敗パターンを先に押さえておきます。
モード1: 全レビュー(フルチェック)
すべての出力を人が見てから次の処理に進めます。最も安全に見えますが、運用 N 日目には必ず破綻します。エージェントが1日100件処理するなら、1件30秒の確認でも50分。10件しか処理しない用途なら成立しますが、それなら最初からエージェント化する旨味が薄いです。
モード2: 全自動(ノーチェック)
「LLM が出した結果を信じる」運用です。短期的には回りますが、必ず「自信を持って間違える」ケースが混ざります。Anthropic や Google の論文でも繰り返し指摘されているとおり、LLM の自信度(モデルが内部で計算する尤度)と、実際の正答率の相関は弱いです。「これは確実です」と語尾を強めるハルシネーションは止められません。
モード3: 段階的承認(graduated approval)
出力ごとにスコアを付け、閾値で振り分けます。
- スコア >= 0.85: auto-approve(人を介さず次工程へ)
- 0.55 <= スコア < 0.85: human-review(キューに送る)
- スコア < 0.55: auto-reject + retry(破棄して別プロンプトで再試行)
このモードは、運用工数を 70% 以上削減しながら、品質劣化を「人がレビューする上位 30〜45%」に集中させる設計です。私はこの方式に切り替えてから、レビューキューが安定して 1 日 30〜50 件で頭打ちになり、自分の開発時間を取り戻せるようになりました。
自信スコアとは何か — 既存の指標との違い
「自信スコア」と聞くと、モデルが返す logprobs を思い浮かべる方が多いと思います。しかし logprobs はテキスト生成の尤度であって、出力内容の正しさを測る指標ではありません。「確信を持って嘘をつく」のが LLM の典型的な失敗様式なので、logprobs 単体ではエスカレーション判断に使えません。
私が運用で使っている自信スコアは、次の3つの独立した観点を組み合わせた合成スコアです。
- Self-evaluation スコア: 同じモデル、または別モデルに「あなたのこの出力はどれくらい信頼できますか? 0-100で答えてください」と問い直した値
- Verifiability スコア: 出力がスキーマ検証・型チェック・ドメインルールに通るか
- Historical similarity スコア: 過去の類似タスクで、エージェントが返した出力が人間レビューで承認された割合
3つを独立に計算して重み付けで合成するのがポイントです。1つの指標に依存すると、その指標を回避するように LLM が「最適化」してしまうことがあるためです。Goodhart の法則(「測定指標が目標になると、それは良い指標でなくなる」)は LLM 運用でも容赦なく発動します。
自信スコアを取得する3つのアプローチ
それぞれの計算方法を実装レベルで見ていきます。Python と Antigravity Agent の SDK を想定します。
アプローチ1: モデル自身に評価させる(self-evaluation)
最もシンプルですが、プロンプト設計を間違えると過大評価が発生します。コツは「具体的な失敗シナリオを並べて、そのいずれかに該当するなら低い点をつけてください」と質問することです。
# 自信スコア計算: モデルに自己評価させる方式
import anthropic
from typing import Literal
client = anthropic.Anthropic()
SELF_EVAL_PROMPT = """以下は、AI エージェントが生成した出力です。
あなたはこの出力を厳しく評価する査読者です。
出力:
{output}
元のタスク:
{task}
次の各項目について、該当すれば該当する数だけ減点してください(初期値: 100):
- 事実誤認・存在しない情報を含む: -40
- 元のタスクに直接答えていない: -25
- 不要な前置き・冗長な説明: -10
- スキーマ違反・形式エラー: -30
- 前提を勝手に追加している: -15
最終スコアを 0-100 の整数 1 つだけで返してください。それ以外の文字は禁止。
"""
def self_evaluation_score(task: str, output: str) -> float:
msg = client.messages.create(
model="claude-haiku-4-5-20251001", # 評価には軽量モデルで十分
max_tokens=10,
messages=[{
"role": "user",
"content": SELF_EVAL_PROMPT.format(task=task, output=output),
}],
)
raw = msg.content[0].text.strip()
try:
score = int(raw)
except ValueError:
# パース失敗は最低スコアとして扱う(保守的)
return 0.0
return max(0.0, min(1.0, score / 100.0))
# 使用例
score = self_evaluation_score(
task="ユーザーの請求書PDFから、金額・税率・支払期限を JSON で抽出して",
output='{"amount": 12000, "tax_rate": 0.1, "due_date": "2026-06-15"}',
)
# 期待出力: 0.85〜0.95(情報抽出として妥当な内容のため)
注意点として、評価に同じモデルを使うと「自分の間違いに気づけない」現象が起きます。生成は claude-sonnet-4-6、評価は claude-haiku-4-5-20251001 のように別モデルに分けるか、最低でも別系統のプロバイダ(Gemini や GPT)に評価させるのが安全です。
アプローチ2: 出力の検証可能性で測る(schema + rule-based verification)
LLM の出力を Pydantic などで型チェックする運用は一般化していますが、これを「合否」ではなく「スコア」として扱う発想が重要です。
# 自信スコア計算: スキーマ + ルールベースで測る方式
from pydantic import BaseModel, Field, ValidationError
from datetime import date
class InvoiceExtraction(BaseModel):
amount: float = Field(gt=0, description="請求金額(正の数)")
tax_rate: float = Field(ge=0, le=1, description="税率 0〜1")
due_date: date = Field(description="支払期限")
def verifiability_score(output_dict: dict, expected_amount_range: tuple = None) -> float:
"""
スキーマ検証とドメインルールでスコアを算出。
完全パスで 1.0、一部違反で 0.5、構造破壊で 0.0。
"""
score = 1.0
# Layer 1: スキーマ検証(重め)
try:
parsed = InvoiceExtraction(**output_dict)
except ValidationError:
return 0.0 # スキーマ違反は致命的
# Layer 2: ドメインルール検証(軽め)
if expected_amount_range:
lo, hi = expected_amount_range
if not (lo <= parsed.amount <= hi):
score -= 0.3 # 想定範囲外は要注意
if parsed.due_date < date.today():
score -= 0.4 # 過去日付は明らかにおかしい
if parsed.tax_rate not in (0.0, 0.08, 0.1):
score -= 0.2 # 日本の標準税率以外は要注意
return max(0.0, score)
# 使用例
data = {"amount": 12000, "tax_rate": 0.1, "due_date": "2026-06-15"}
v_score = verifiability_score(data, expected_amount_range=(1000, 1_000_000))
# 期待出力: 1.0(全ルール通過)
このアプローチの強みは決定論的に動くことです。LLM の機嫌に左右されず、CI 環境でも安定して再現できます。一方で、検証可能なドメインに限られます(自由記述の文章は検証しづらい)。
アプローチ3: 過去の類似タスクの成功率で測る
最も価値が高いのに、実装している人をあまり見ないのがこれです。エージェントの出力を全件ログに残し、人間レビューで「承認 / 拒否」のラベルを付けておきます。新しい出力が来たとき、ログから類似タスクを検索し、その類似タスクの承認率をスコアとします。
# 自信スコア計算: 過去ログの類似タスク承認率を返す
import numpy as np
from typing import List, Tuple
class HistoricalScorer:
"""
過去の (タスク埋め込み, 承認フラグ) のログを保持し、
新規タスクとの類似度上位 K 件の承認率を返す。
"""
def __init__(self, top_k: int = 10):
self.embeddings: List[np.ndarray] = []
self.approvals: List[bool] = []
self.top_k = top_k
def add_record(self, task_embedding: np.ndarray, approved: bool) -> None:
self.embeddings.append(task_embedding)
self.approvals.append(approved)
def historical_score(self, new_task_embedding: np.ndarray) -> float:
if len(self.embeddings) < self.top_k:
# 学習データが少ないうちは中立スコア
return 0.5
sims = [
np.dot(new_task_embedding, e) /
(np.linalg.norm(new_task_embedding) * np.linalg.norm(e) + 1e-9)
for e in self.embeddings
]
top_indices = np.argsort(sims)[-self.top_k:]
approval_rate = np.mean([self.approvals[i] for i in top_indices])
return float(approval_rate)
# 使用例
scorer = HistoricalScorer(top_k=10)
# 起動時に過去 1000 件のログから埋め込みを再生成して投入する想定
# scorer.add_record(emb, approved=True) を 1000 回呼ぶ
new_emb = np.random.rand(1024) # 実際は埋め込みモデルで生成
h_score = scorer.historical_score(new_emb)
# 期待出力: 学習データが揃っていれば 0.7〜0.95 程度(タスク種別による)
このスコアの真価は、エージェントが「特定タイプのタスクで弱い」ことを自動検知できる点にあります。たとえば「契約書の特殊条項」だけ承認率が 30% を切っているとわかれば、その種類だけプロンプトを差し替える、別モデルに振る、といった対策が打てます。
3つのスコアを合成する
3つのスコアは性質が異なるため、単純平均ではなく重み付き合成が向きます。私が運用で使っている重みは以下です。
# 合成自信スコア
def composite_confidence(
self_eval: float,
verifiability: float,
historical: float,
weights: tuple = (0.3, 0.4, 0.3),
) -> float:
"""
重み付き合成スコア。
verifiability に最も重みを置くのは、決定論的で改ざん耐性があるため。
"""
w1, w2, w3 = weights
composite = w1 * self_eval + w2 * verifiability + w3 * historical
# ただし、verifiability が 0 のときは強制的に低スコアに丸める
if verifiability == 0.0:
composite = min(composite, 0.2)
return composite
# 使用例
final_score = composite_confidence(
self_eval=0.9,
verifiability=1.0,
historical=0.85,
)
# 期待出力: 0.3*0.9 + 0.4*1.0 + 0.3*0.85 = 0.925
verifiability == 0 のときに強制的に丸めるロジックは、運用初期に痛い目を見て追加しました。スキーマ違反なのに self-eval が 0.95 を返したケースで、合成スコアが 0.7 を超えてしまい、不正データが auto-approve に流れた事故があったためです。
閾値の設計と振り分けロジック
スコアを 3 層に振り分けるロジックは単純ですが、閾値の選び方が運用の質を決めます。
# 段階的承認の振り分け
from enum import Enum
class Decision(Enum):
AUTO_APPROVE = "auto_approve"
HUMAN_REVIEW = "human_review"
AUTO_REJECT = "auto_reject"
def decide(
confidence: float,
upper_threshold: float = 0.85,
lower_threshold: float = 0.55,
) -> Decision:
if confidence >= upper_threshold:
return Decision.AUTO_APPROVE
if confidence >= lower_threshold:
return Decision.HUMAN_REVIEW
return Decision.AUTO_REJECT
# 使用例
for c in [0.92, 0.70, 0.40]:
print(c, decide(c))
# 期待出力:
# 0.92 Decision.AUTO_APPROVE
# 0.70 Decision.HUMAN_REVIEW
# 0.40 Decision.AUTO_REJECT
私が初期値として推奨する閾値は upper=0.85 / lower=0.55 ですが、これはあくまで出発点で、運用ログを見て調整する前提です。次のセクションでキャリブレーション手順を扱います。
閾値はどう決めるか — ROC 曲線によるキャリブレーション
閾値を「勘」で決めると後で必ず後悔します。最低 200 件、できれば 500 件のログが溜まったら、ROC 曲線を引いてください。
# 閾値キャリブレーション: ROC 曲線から最適閾値を求める
import numpy as np
def calibrate_upper_threshold(
scores: list, true_labels: list, target_precision: float = 0.95
) -> float:
"""
auto-approve に流すスコアの上限を、目標精度から逆算する。
true_labels: 1 = 後で人が見て正解だった, 0 = 不正解だった
target_precision: auto-approve した中での正解率目標
"""
pairs = sorted(zip(scores, true_labels), key=lambda x: -x[0])
cum_correct = 0
cum_total = 0
for s, label in pairs:
cum_total += 1
cum_correct += label
precision = cum_correct / cum_total
if precision < target_precision and cum_total >= 20:
# 直前のスコアが上限
return s + 0.01
return 0.5 # データ不足
# 使用例(実データ想定)
historical_scores = [0.95, 0.90, 0.88, 0.85, 0.80, 0.70, 0.55, 0.40]
historical_labels = [1, 1, 1, 1, 0, 0, 0, 0]
upper = calibrate_upper_threshold(
historical_scores, historical_labels, target_precision=0.95
)
# 期待出力: 0.86 前後(精度 95% を維持できる最低スコア)
実務上のポイントとして、target_precision を 0.99 のように上げすぎると auto-approve に回る件数が激減して、結局すべてが human-review に流れて意味がなくなります。ビジネス影響度から逆算して、 0.95〜0.97 あたりに設定するのが現実的です。
下限閾値(auto-reject の閾値)は逆方向で、「どこまで低いスコアなら諦めて再試行すべきか」を決めます。私の運用では、再試行のコスト(API 課金 + 時間)と、人間レビューに渡すコスト(人件費)を比較して、人件費の方が高いケース(多くの個人開発・小規模運用)では下限を 0.4〜0.5 と低めに、API 単価が高いケース(GPT-4 系を多用する)では 0.6〜0.7 と高めに設定しています。
Antigravity Agent と組み合わせた本番パイプライン
ここまでの構成要素を、Antigravity Agent から呼ばれる Python サービスに統合した例を示します。
# Antigravity Agent から呼ばれる graduated approval サービス
import json
import logging
from dataclasses import dataclass
logger = logging.getLogger("graduated-approval")
@dataclass
class ApprovalResult:
decision: Decision
confidence: float
output: dict
reason: str
def graduated_approval_pipeline(
task: str,
raw_output: str,
expected_amount_range: tuple = None,
) -> ApprovalResult:
"""
Antigravity Agent の出力を受け取り、振り分け結果を返すメインパイプライン。
"""
# Step 1: パースを試みる(パース不能 = 即拒否)
try:
output_dict = json.loads(raw_output)
except json.JSONDecodeError:
return ApprovalResult(
Decision.AUTO_REJECT, 0.0, {}, "JSON パース失敗"
)
# Step 2: 3 つのスコアを並行算出
s_eval = self_evaluation_score(task, raw_output)
v_score = verifiability_score(output_dict, expected_amount_range)
# 過去ログスコアは事前に初期化済みの scorer を使用する想定
# h_score = scorer.historical_score(embed_task(task))
h_score = 0.7 # サンプル値
# Step 3: 合成スコア
confidence = composite_confidence(s_eval, v_score, h_score)
# Step 4: 振り分け
decision = decide(confidence)
# Step 5: 監査ログに必ず残す(後でキャリブレーションに使う)
logger.info(json.dumps({
"task": task[:200],
"self_eval": s_eval,
"verifiability": v_score,
"historical": h_score,
"confidence": confidence,
"decision": decision.value,
}))
return ApprovalResult(
decision=decision,
confidence=confidence,
output=output_dict,
reason=f"s={s_eval:.2f} v={v_score:.2f} h={h_score:.2f}",
)
# 使用例: Antigravity Agent の Walkthrough から呼ばれる想定
result = graduated_approval_pipeline(
task="請求書PDFから金額・税率・支払期限を抽出して",
raw_output='{"amount": 12000, "tax_rate": 0.1, "due_date": "2026-06-15"}',
expected_amount_range=(1000, 1_000_000),
)
print(result.decision, result.confidence)
# 期待出力: Decision.AUTO_APPROVE 0.91 (前後)
このサービスを Antigravity Agent から HTTP で叩く形にしておくと、エージェントは「自分のタスクが終わったら API に投げる、結果が auto_approve なら次のステップに進む、human_review なら待機」というシンプルなフローで動かせます。Antigravity の Walkthrough ステップとして組み込めば、Manager Surface 上で承認待ち状態が可視化されます。
よくある落とし穴
私が運用しながら踏んだ落とし穴を3つ共有します。これを知っておくだけで初期段階の事故が大きく減ります。
第1に、self-evaluation の過信問題 です。生成と評価を同じモデルで行うと、評価が甘くなります。最低でも別系統のモデル(Claude で生成したなら Gemini や GPT-4 で評価)に振るか、社内ルールベースの検証を併用してください。コストが気になるなら評価は Haiku 4.5 のような軽量モデルで十分です。
第2に、スコア分布のドリフト です。本番運用 1〜2 ヶ月で、入力データの傾向が変わるとスコア分布が偏ります。たとえば月初は請求書処理が多くてスコアが高めに偏り、月末は契約書処理が増えてスコアが低めに偏る、といった変動が起きます。週次で分布を可視化(ヒストグラム + 中央値)し、急変したら原因を調査するルーティンを組み込んでください。antigravity-prompt-versioning-ab-testing-production-guide で扱った A/B テスト基盤と組み合わせるのが効果的です。
第3に、「常に human_review」になるバグ です。閾値を厳しく設定しすぎると、ほぼ全件が human_review に流れて、結局フルレビュー運用に逆戻りします。月次で「auto_approve 率 / human_review 率 / auto_reject 率」を集計し、auto_approve 率が 50% を切っていたら閾値を見直してください。
運用ログから閾値を継続的に改善する仕組み
閾値は一度決めて終わりではありません。継続的にキャリブレーションする運用ループを組み込んでおくと、エージェントの「成長」をデータで追えるようになります。
具体的には、人間レビュー結果(承認/拒否)をすべて構造化ログとして保存し、週次バッチで以下を再計算します。
target_precision = 0.95 を保てる upper_threshold の最新値
- 直近 N 件の confidence スコア分布のヒストグラム
- 「自信が高いのに人間が拒否した」ケースの一覧(プロンプト改善の宝庫)
このうち最後の「自信が高いのに拒否されたケース」が最も価値があります。これは self-evaluation のプロンプトが見落としている失敗パターンを示しているので、SELF_EVAL_PROMPT に減点項目を追加するヒントになります。私の経験では、運用 3 ヶ月で減点項目が 5 → 12 まで増えましたが、それに伴って「自信が高いのに拒否」のレートが 8% → 2% まで下がりました。
エージェント観測の基盤については Antigravity Agent のトレース観測設計 で扱った OpenTelemetry の構成と相性が良いので、合わせて参照してください。検証ループ自体の3パターンは Antigravity Agent の検証ループ3パターン に整理してあります。
graduated approval が向かない3つのケース
便利なパターンですが、万能ではありません。私自身がこの設計を持ち込んで後悔した領域が、少なくとも3つあります。
1つ目は、取り返しのつかない高リスクな操作 です。1件の誤りが数十万円の損失や顧客との信頼喪失、あるいは規制上の問題に直結する処理では、「自信が高いから自動承認」という発想そのものが危険です。送金、契約の締結、対外的な公表——こうしたアクションは、エージェントがどれだけ高いスコアを返しても、常に人間レビュー必須の層に固定すべきだと考えています。この場合の自信スコアは「承認をスキップする道具」ではなく、「人間のレビュー待ち行列に優先順位を付ける道具」として使います。
2つ目は、検証可能なシグナルが存在しない仕事 です。出力が正しかったかどうかを後から確認する手段がない(スキーマも、ドメインルールも、誤りを拾う下流システムもない)場合、historical similarity スコアに意味のあるラベルが付かず、キャリブレーションのループ全体が回らなくなります。純粋な創作、ブレインストーミング、探索的なリサーチはここに当たりがちです。こうした用途では、別のレビュー方式——たとえば「10件に1件をランダムに抜き取って確認する」——の方が向いています。
3つ目は、処理件数が極端に少ない場合 です。1日5〜10件しか処理しないなら、スコアリング基盤を作る労力が、削減できる工数を上回ります。素直に全件レビューしてください。私の経験では、graduated approval が複雑さに見合うのは、おおむね1日50件を超えたあたりからです。
これらを正直に見分けることも、運用設計の一部だと感じています。善意のエンジニア(過去の私を含めて)が、創作的な仕事にこのパターンを当てはめようとして、ラベルが本質的に主観的なせいでキャリブレーションが収束しない、という失敗をよく見かけます。
損益分岐は1日何件か — 簡単なコスト試算
導入の前に、自分の状況で graduated approval が割に合うかを見積もっておく価値があります。以下は私自身の本番エージェントの数字を、一般化できる形に直したものです。
仮に、エージェントが1日500件の出力を処理するとします。graduated approval がなければ、1件あたり平均30秒のレビューで、1日250分——約4時間の集中レビューが毎日発生します。多くのチームはこれを維持できず、現実には「ざっと目を通すだけ」のスポットチェックに縮退し、肝心の問題を見落とします。
graduated approval を入れ、キャリブレーション後の典型的な分布をおおよそ auto-approve 60% / human-review 30% / auto-reject 10% とすると、人間レビューが必要なのは1日150件。同じ30秒なら75分です。auto-reject は再試行キューに回るだけなので、人間の時間はほぼ消費しません。
追加で発生するのは self-evaluation 呼び出しの API コストです。Haiku 4.5 のような軽量モデルなら1回あたり1セント未満。1日500回でも、追加 API コストは1日3〜5ドル、月にして100ドル程度で、取り戻せる時間に比べればはるかに小さい額です。
私自身の帳簿では、損益分岐はおおよそ1日80件あたりにあります。それ以下なら、エンジニアリングと API のオーバーヘッドが見合いません。1日200件を超えると、削減効果ははっきりと見えてきます。基盤を作る前に、自分の数字で正直にモデル化しておくことをおすすめします。
実運用のキャリブレーション記録 — 3ヶ月の数字
抽象論だけでは伝わりにくいので、ある請求書・問い合わせ処理エージェントを3ヶ月運用したときの、私自身のキャリブレーション記録を時系列で共有します。数字は一般化のために丸めていますが、変化の形はそのままです。
1ヶ月目(log-only 運用)。 振り分けはまだ行わず、全件を人間レビューしながら self-evaluation スコアと承認/拒否ラベルだけを記録しました。溜まったログは約 1,400 件。この時点でスコア分布を引くと、0.9 以上に大きな山があり、0.6 付近にもう一つ小さな山がありました。後者がほぼすべて「契約条項の解釈」タスクだったことが、historical スコアを入れる前から見えてきました。
2ヶ月目(振り分け開始)。 upper=0.85 / lower=0.55 で振り分けを始めたところ、初週の分布は auto-approve 52% / human-review 41% / auto-reject 7% でした。auto-approve 率が想定より低かったので、calibrate_upper_threshold を target_precision=0.95 で回すと、最適な upper は 0.83 と出ました。0.85 から 0.83 へわずかに下げただけで、auto-approve 率は 52% から 63% に上がり、それでも auto-approve 内の正答率は 95.4% を維持できました。たった 0.02 の差がこれだけ効くというのが、勘で閾値を決めてはいけない理由です。
3ヶ月目(プロンプト改善ループ)。 「自信が高いのに人間が拒否した」ケースを毎週洗い出し、SELF_EVAL_PROMPT の減点項目を足していきました。減点項目は 5 から 12 に増え、それに伴って「高スコアなのに拒否」のレートは 8% から 2% に下がりました。同時に、契約条項タスクだけを別プロンプト+上位モデルに振り分けたところ、そのカテゴリの承認率は 31% から 78% に改善しました。
この3ヶ月で一番効いたのは、派手なアルゴリズムではなく、「拒否されたケースを毎週ただ眺める」という地味な習慣でした。エージェントの弱点は、抽象的に考えても見えてきません。実際に拒否されたログの中に、いつも具体的な形で現れます。
緊急停止スイッチを最初から組み込む
最後に、運用を始めてから「最初から入れておけばよかった」と最も強く感じた仕組みを紹介します。バッチ全体を一括で human-review に戻す 緊急停止スイッチ(kill switch) です。
モデルをアップグレードした直後、ドメインで異例の出来事が起きたとき、ある種別の判断について外部から問い合わせが来たとき——こうした場面では、スコアが高かろうと、いったんすべてを人間の目に戻したくなります。再デプロイせずにスイッチ一つでそれができるかどうかは、必要になって初めて、その価値の大きさがわかります。
# 緊急停止スイッチ付きの振り分け
import os
def decide_with_kill_switch(
confidence: float,
upper_threshold: float = 0.85,
lower_threshold: float = 0.55,
force_human: bool = False,
) -> Decision:
# 外部フラグ(環境変数 / KV / フィーチャーフラグ)で一括上書き
kill_switch = force_human or os.environ.get("APPROVAL_FORCE_HUMAN") == "1"
if kill_switch:
# auto-reject だけは破棄してよい(再試行に回るだけ)
if confidence < lower_threshold:
return Decision.AUTO_REJECT
# それ以外は確信度に関わらず人間へ
return Decision.HUMAN_REVIEW
return decide(confidence, upper_threshold, lower_threshold)
# 使用例: モデル切替直後の1日だけ全件を人間に戻す
os.environ["APPROVAL_FORCE_HUMAN"] = "1"
print(decide_with_kill_switch(0.97)) # Decision.HUMAN_REVIEW
print(decide_with_kill_switch(0.30)) # Decision.AUTO_REJECT
ポイントは、停止フラグをコードではなく外部(環境変数・KV・フィーチャーフラグ)に置くことです。コードに埋め込むと、緊急時に再デプロイが必要になり、肝心の「今すぐ止めたい」に間に合いません。私はこのフラグを、自動投稿パイプラインを大きく変更する日には必ず手動で立てて、半日ほど全件を自分の目で確認してから解除するようにしています。
まず最初の一歩
長い記事になりましたが、明日からの一歩は次の1つだけです。
現在運用中のエージェントの出力に、self-evaluation スコアだけを付けてログに記録します。 振り分け処理はまだ入れず、ただ記録するだけです。1〜2週間溜めたログを眺めると、「このスコアが 0.7 を下回っているケースは確かに怪しい」「0.9 を超えているケースはほぼ正しい」という肌感覚が掴めます。閾値設計はそのあとで構いません。
人間が判断する力を、最も判断が必要な場面に集中させる。これは私自身、自分のアプリ事業をひとりで運用してきた中で何度も学び直してきた原則です。エージェントに任せられる判断と、人間が責任を持つべき判断の境界を、データで定義していく取り組みは、これからの個人開発にとっても重要な設計力になっていくのだと考えています。