0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AI を業務やプロダクトに組み込もうとすると、わりと早い段階で同じ壁にぶつかる。「出力が安定しない」だ。

同じプロンプトを投げても、返ってくる JSON のキーが微妙に違う。昨日は動いていた抽出処理が、今日は説明文が混ざって壊れる。プロンプトを書く人間の側も、毎回同じ精度では書けない。AI の出力も、人間の入力も、本質的にゆらぐ。

それなのに、プロダクトや業務システムは「一定の出力」を要求する。API のレスポンスは決まったスキーマであってほしいし、バッチ処理は毎回同じ結果を返してほしい。このギャップ — ゆらぐ部品で、ゆらがない出力を作る — をどう埋めるかが、AI を「賢く使う」より先に効いてくる。

結論から言うと、効くのは賢い AI でも上手いプロンプトでもない。ゆらぎを吸収して一定の出力に収束させる「仕組み」、つまり昔から地味にやってきたシステム連携・信頼性設計のパターンだ。この記事では、そのパターンを7つ整理する。

なぜ「賢い AI」だけでは安定しないのか

まず前提を揃えておく。LLM の出力は確率的にサンプリングされるので、temperature=0 にしても完全な決定性は保証されない(同じ入力でも実行系・バージョンで揺れる)。プロンプトを足せば足すほど、指示が衝突して別の壊れ方をすることもある。

人間側も同じだ。プロンプトを書くエンジニアの体調・知識・その日の集中力で、入力の質はばらつく。「いいプロンプトを書けば安定する」は、属人性をプロンプトに移しただけで、ゆらぎの発生源が消えたわけではない。

だから戦略を変える。部品(AI・人間)を安定させようとするのをやめて、ゆらぐ部品を前提に、出口で一定にする仕組みを作る。これは新しい発想ではなく、不安定なネットワーク・不安定なハードウェアの上で安定したシステムを作ってきた、ごく普通の信頼性設計の応用だ。

パターン1: スキーマ強制 — 出力の「形」を先に決める

一番効くのがこれ。自由なテキストで返させると無限にゆらぐので、出力スキーマを先に定義して、それに合致するまで弾く

from pydantic import BaseModel, ValidationError

class ExtractResult(BaseModel):
    title: str
    amount: int
    currency: str

def parse_llm_output(raw: str) -> ExtractResult:
    # LLM の生出力を pydantic で検証。形が違えば例外
    return ExtractResult.model_validate_json(raw)

OpenAI / Anthropic の Structured Output(JSON スキーマを渡してその形でしか返させない機能)や、関数呼び出し(tool use)も思想は同じだ。「自由に書いていいよ」をやめて、「この型で返せ」に変える。ゆらぎの自由度を、スキーマの分だけ削る。

パターン2: 検証ゲート — 通す前に必ず一度止める

スキーマが合っていても、中身が妥当とは限らない。amount: -5 のような「形は正しいが意味が壊れている」出力を止めるために、検証ゲートを1枚挟む

def validate(result: ExtractResult) -> ExtractResult:
    if result.amount < 0:
        raise ValueError(f"amount が負: {result.amount}")
    if result.currency not in {"JPY", "USD", "EUR"}:
        raise ValueError(f"未知の通貨: {result.currency}")
    return result

ポイントは「AI の出力を一次情報として信用しない」こと。生成 AI は流暢に間違えるので、流暢さに引っ張られて検証を省くと、壊れた出力がそのまま下流に流れる。ゲートは多少うっとうしくても、必ず置く。

パターン3: フォールバック — 失敗を「想定内」にする

検証で弾いたあと、どうするか。失敗時の代替経路を最初から用意しておく

def extract_with_fallback(text: str) -> ExtractResult:
    try:
        return validate(parse_llm_output(call_llm(text)))
    except (ValidationError, ValueError):
        # 1回だけ「形式を厳密に」と念押しして再試行
        return validate(parse_llm_output(call_llm(text, strict=True)))
    # それでもダメなら呼び出し元で握る(後述の人間ゲートへ)

フォールバックが無いと、ゆらぎが 1 回出ただけで処理全体が落ちる。代替経路があれば、ゆらぎは「想定内の分岐」になる。重要なのは、フォールバックの失敗を握りつぶさないこと。最終的に失敗したら、それは下流に「失敗した」と伝える(後述)。

パターン4: 冪等性 — 何度実行しても同じ結果にする

AI を組み込んだ処理はリトライが増える。リトライのたびに副作用が二重に走ると、出力どころか状態がゆらぐ。同じ入力なら何度実行しても同じ結果になる(冪等)ように設計する

def upsert(record_id: str, result: ExtractResult):
    # INSERT ではなく UPSERT。再実行しても重複しない
    db.execute(
        "INSERT INTO results (id, data) VALUES (?, ?) "
        "ON CONFLICT(id) DO UPDATE SET data = excluded.data",
        (record_id, result.model_dump_json()),
    )

冪等にしておくと、リトライ・フォールバック・並列実行が安全になる。「もう一回流していい」という安心感が、不安定な部品を扱う土台になる。

パターン5: リトライは「回数上限つき」で

フォールバックと近いが、リトライは無限ループの危険があるので分けて書く。指数バックオフ + 上限回数で、諦めどころを必ず決める。

import time

def retry(fn, max_attempts=3):
    for attempt in range(max_attempts):
        try:
            return fn()
        except TransientError:
            if attempt == max_attempts - 1:
                raise  # 上限到達。握りつぶさず上げる
            time.sleep(2 ** attempt)

「成功するまで回す」は一見やさしいが、ゆらぎが構造的な場合(プロンプトが根本的に悪い等)は永遠に終わらない。上限を切って、超えたら人間に渡す。

パターン6: 人間ゲート — 不可逆な操作の前で止める

自動化を進めるほど、「AI の出力で不可逆な操作(公開・送金・削除)が走る」リスクが上がる。不可逆操作の直前に、人間の承認を1枚挟む

def publish(article, auto_checks_passed: bool, human_approved: bool):
    if not auto_checks_passed:
        raise GateError("自動チェック未通過")
    if not human_approved:
        # 機械チェックが通っても、不可逆操作は人間承認を要求
        raise GateError("人間レビュー未完了 — 公開保留")
    do_publish(article)

機械チェックと人間チェックは役割が違う。機械は「形式・閾値・集合一致」を高速に弾き、人間は「これは本当に出していいのか」という文脈判断をする。両方を直列に置くと、ゆらぎの取りこぼしが減る。

パターン7: パイプライン分割 — ゆらぎを1工程に閉じ込める

最後に全体構造の話。生成・検証・整形・保存を1つの巨大な関数に詰めると、どこでゆらいだか分からなくなる。工程を分割して、各工程の入出力を固定する

[生成] --raw text--> [スキーマ強制] --typed--> [検証] --valid--> [整形] --> [冪等保存]
                          ↑ ゆらぎはこの2工程に閉じ込める

こうすると、ゆらぎが発生しうるのは「生成」と「スキーマ強制」の境界だけになる。それより下流は型が保証された世界なので、安定して書ける。デバッグ時も「どの工程で壊れたか」が一意に分かる。

AI 時代だからこそ、連携技術が効いてくる

ここまで挙げた7つ — スキーマ強制・検証ゲート・フォールバック・冪等性・リトライ上限・人間ゲート・パイプライン分割 — は、どれも AI 専用の新技術ではない。不安定なネットワークや外部 API を相手に、システムを安定させるために昔からやってきた、地味な連携・信頼性設計のパターンだ。

AI の登場で「賢い部品」は手に入った。けれど賢い部品は、同時にゆらぐ部品でもある。ゆらぐ部品を一定の出力に収束させる仕事は、AI が賢くなっても消えない。むしろ、部品が強力になったぶん、その出力を受け止める「仕組み」の設計がボトルネックに移ってきている。

プロンプトを磨くのも大事だが、その手前で「ゆらいでも一定に収束する仕組み」を1枚噛ませる。AI を業務に組み込んで出力の不安定さに悩んでいるなら、まずこの7パターンのどれが欠けているかを見てみるといい。多くの場合、足りないのは賢さではなく、ゆらぎを受け止める枠組みのほうだ。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?