AI Agents · レッスン

ファクトチェックとハルシネーション防止

根拠に基づいて検証し、すべての主張を取得した情報源までたどれるようにします。

レッスン 4/413 ステップ

「ファクトチェックとハルシネーション防止」はCoddyKit上の無料AI Agentsレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Agents学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Agentsコースには全4レッスンが含まれています。

調査エージェントにおけるハルシネーションの問題

LLMは、取得した情報源に根拠がないにもかかわらず、もっともらしい主張を生成することがあります。調査エージェントでは、出力が権威あるものに見え、実際には主張を裏付けていない引用が付いている可能性もあるため、特に危険です。

ハルシネーションの防止は、最優先で取り組むべき重要な課題です。

グラウンディング:すべての主張を情報源まで追跡可能にする

グラウンディングの基本原則は、最終出力に含まれるすべての事実に関する主張を、少なくとも1つの取得した文書まで追跡可能にすることです。追跡できない主張は、ハルシネーションであるか、根拠がないかのどちらかであり、研究レポートではどちらも許容されません。

def check_grounding(claim: str, retrieved_docs: list[dict]) -> dict:
    doc_texts = '\n\n'.join(
        f'[DOC {i+1}] ({d["url"]})\n{d["text"][:800]}'
        for i, d in enumerate(retrieved_docs[:5])
    )
    return {
        'claim': claim,
        'docs':  doc_texts,
        'grounded': None   # to be filled by LLM verifier
    }

if __name__ == '__main__':
    docs = [{'url': 'https://example.com/geo', 'text': 'Paris is the capital of France.'}]
    result = check_grounding('Paris is the capital of France.', docs)
    print('Claim:', result['claim'])
    print('Supporting docs used:')
    print(result['docs'])

LLM-as-Verifierパターン

別のLLM呼び出しである「verifier」を使い、主張が提供された情報源によって裏付けられているかどうかを判定します。これにより、生成器とverifierを独立した呼び出しにするチェック・アンド・バランスの仕組みを作れます。

import openai, json

client = openai.OpenAI(api_key='YOUR_OPENAI_KEY')

def verify_claim(claim: str, source_texts: list[str]) -> dict:
    sources_block = '\n---\n'.join(source_texts[:3])
    prompt = (
        f'Is the following claim directly supported by the provided sources?\n'
        f'Claim: "{claim}"\n\n'
        f'Sources:\n{sources_block}\n\n'
        f'Return JSON: {{\n'
        f'  "supported": true/false,\n'
        f'  "confidence": 0.0-1.0,\n'
        f'  "reason": "one sentence explanation"\n'
        f'}}'
    )
    resp = client.chat.completions.create(
        model='gpt-4o',
        messages=[{'role': 'user', 'content': prompt}],
        response_format={'type': 'json_object'}
    )
    return json.loads(resp.choices[0].message.content)

信頼度で出力を制御する仕組み

verifierの信頼度スコアにしきい値を設定します。0.85を超える主張は公開し、0.5〜0.85の主張は免責事項付きで公開します。0.5未満の主張は除外します。

INCLUDE_THRESHOLD  = 0.85
DISCLAIMER_THRESHOLD = 0.50

def gate_claim(claim: str, source_texts: list[str]) -> dict:
    result = verify_claim(claim, source_texts)
    conf = result.get('confidence', 0.0)
    supported = result.get('supported', False)

    if not supported or conf < DISCLAIMER_THRESHOLD:
        return {'action': 'exclude', 'claim': claim, 'reason': result.get('reason')}
    elif conf < INCLUDE_THRESHOLD:
        return {
            'action': 'include_with_disclaimer',
            'claim': f'[LOW CONFIDENCE] {claim}',
            'reason': result.get('reason')
        }
    else:
        return {'action': 'include', 'claim': claim}

特定のハルシネーションパターンを検出する

特定の主張パターンは、ハルシネーションのリスクが高い傾向にあります。たとえば、正確な統計値(割合や金額)、具体的な日付、個人名、因果関係などです。これらには特に厳密な検証を行います。

import re

def is_high_risk_claim(claim: str) -> bool:
    patterns = [
        r'\d+\.?\d*\s*%',            # percentages: 9.1%
        r'\$\s*\d+',                  # dollar amounts
        r'\b(January|February|March|April|May|June|July|August|September|October|November|December)\s+\d{4}',  # dates
        r'\b[A-Z][a-z]+\s+[A-Z][a-z]+\s+(said|stated|argued|claimed)',  # named quotes
        r'(caused?|led to|resulted in)',                                  # causal claims
    ]
    return any(re.search(p, claim) for p in patterns)

print(is_high_risk_claim('Inflation hit 9.1% in June 2022'))  # True
print(is_high_risk_claim('Inflation was elevated'))            # False

分解型検証ループ

長いセクションでは、テキストを個々の主張に分解し、それぞれを独立して検証してから、検証済みの主張だけでセクションを再構成します。

def decompose_into_claims(section_text: str) -> list[str]:
    prompt = (
        f'Break this text into individual verifiable factual claims.\n'
        f'Each claim should be a single sentence containing exactly one fact.\n'
        f'Return JSON: {{"claims": ["..."]}}\n\n'
        f'TEXT:\n{section_text}'
    )
    resp = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[{'role': 'user', 'content': prompt}],
        response_format={'type': 'json_object'}
    )
    return json.loads(resp.choices[0].message.content).get('claims', [])

検証済みテキストの再構成

各主張を採用するか判定した後、検証済みの主張だけから、読みやすく一貫性のあるセクションを再構成します。信頼度の低い主張は除外し、読みやすさを考慮して書き直します。

def reconstruct_section(verified_claims: list[str],
                        section_name: str) -> str:
    claims_text = '\n'.join(f'- {c}' for c in verified_claims)
    prompt = (
        f'Rewrite these verified facts as a coherent {section_name} section.\n'
        f'Do NOT add any new information not present in the claims.\n'
        f'Only use the facts provided.\n\n'
        f'VERIFIED CLAIMS:\n{claims_text}'
    )
    resp = client.chat.completions.create(
        model='gpt-4o',
        messages=[{'role': 'user', 'content': prompt}]
    )
    return resp.choices[0].message.content

数値のずれを検出する

LLMは、情報源にある数値を微妙に変更することがあります(たとえば、9.1%が9.2%になるなど)。主張と情報源のテキストから数値を抽出し、明示的に比較します。

import re

def extract_numbers(text: str) -> list[float]:
    matches = re.findall(r'[-+]?\d*\.?\d+', text)
    return [float(m) for m in matches]

def check_numeric_drift(claim: str, source_text: str,
                        tolerance: float = 0.01) -> bool:
    claim_nums  = extract_numbers(claim)
    source_nums = extract_numbers(source_text)

    for cn in claim_nums:
        found_match = any(abs(cn - sn) <= tolerance for sn in source_nums)
        if not found_match:
            return True  # numeric drift detected
    return False

print(check_numeric_drift(
    'Inflation reached 9.2% in June 2022',
    'The CPI rose 9.1 percent in June 2022'
))  # True — 9.2 vs 9.1

帰属監査

レポート全体を生成した後、帰属監査を実行します。レポート内の各文について、いずれかの情報源に帰属させられることを確認します。対応する情報源の抜粋がない文にはフラグを付けます。

def attribution_audit(report_text: str, sources: list[dict]) -> list[str]:
    sentences = [s.strip() for s in report_text.split('.') if len(s.strip()) > 20]
    unattributed = []

    for sentence in sentences:
        found = False
        for src in sources:
            if any(word in src.get('text', '') for word in sentence.split()[:5]):
                found = True
                break
        if not found:
            unattributed.append(sentence)

    return unattributed

# Flag unattributed sentences for manual review or re-verification

if __name__ == '__main__':
    report = ("Water boils at 100 degrees Celsius at sea level. "
              "The moon is made primarily of green cheese according to this document.")
    sources = [{'text': 'Water boils at 100C (212F) at standard atmospheric pressure.'}]
    unattributed = attribution_audit(report, sources)
    print('Unattributed sentences (need manual review):')
    for s in unattributed:
        print(' -', s)

不確実な主張にヘッジ表現を使う

すべてを高い信頼度で検証できるわけではありません。境界的な主張を完全に除外するのではなく、「一部のアナリストは〜と指摘しています」「Xによると〜です」「〜と報告されています」のような不確実性を示す表現を使います。これにより、情報を維持しながら不確実性を伝えられます。

def hedge_claim(claim: str, confidence: float) -> str:
    if confidence >= 0.85:
        return claim  # state as fact
    elif confidence >= 0.65:
        hedges = ['Some sources suggest', 'According to available evidence',
                  'Analysts have noted']
        return f'{hedges[hash(claim) % len(hedges)]}, {claim.lower()}'
    else:
        return f'It has been reported (with low confidence) that {claim.lower()}'

print(hedge_claim('Inflation peaked at 9.1%', 0.90))
print(hedge_claim('Supply chain disruptions contributed to inflation', 0.72))
print(hedge_claim('Specific policy caused inflation', 0.45))

ハルシネーション率の監視

ハルシネーション率を継続的に追跡します。生成された主張のうち、検証に失敗した割合はどの程度でしょうか。アラートのしきい値を設定し、数値が急増した場合は、プロンプトエンジニアリングまたは検索の品質が低下したと判断します。

verification_log = []  # In production: a database

def log_verification(claim: str, supported: bool, confidence: float):
    verification_log.append({
        'claim':      claim[:100],
        'supported':  supported,
        'confidence': confidence
    })

def hallucination_rate() -> float:
    if not verification_log:
        return 0.0
    failed = sum(1 for v in verification_log if not v['supported'])
    return failed / len(verification_log)

def check_hallucination_alert(threshold: float = 0.15):
    rate = hallucination_rate()
    if rate > threshold:
        print(f'ALERT: Hallucination rate {rate:.1%} exceeds threshold {threshold:.1%}')
    return rate

if __name__ == '__main__':
    log_verification('The sky is blue', True, 0.95)
    log_verification('The moon is made of cheese', False, 0.2)
    log_verification('Water boils at 100C at sea level', True, 0.99)
    rate = check_hallucination_alert(threshold=0.15)
    print(f'Hallucination rate so far: {rate:.1%}')

「LLM-as-verifier」パターンの主な目的は何ですか

LLM-as-verifierは、ハルシネーションを防止するための重要なアーキテクチャパターンです。何を検証し、なぜ別の呼び出しとして実行するのかを理解することが重要です。

ハルシネーション防止のまとめ

グラウンディングチェック(すべての主張を情報源まで追跡可能にする)、LLM-as-verifier(別の呼び出しで根拠の有無を判定する)、信頼度による判定(しきい値未満の主張を除外する)、数値のずれの検出、境界的なケースに対するヘッジ表現、そして回帰を検出するためのハルシネーション率の監視によって、ハルシネーションを防止します。

無料で開始

AI チューターと学ぶ AI Agents — 無料

ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。

コース
60
レッスン
239

よくある質問

「ファクトチェックとハルシネーション防止」レッスンは無料ですか?

はい。「ファクトチェックとハルシネーション防止」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Agentsコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Agentsコースには全4レッスンが含まれています。

「ファクトチェックとハルシネーション防止」で何を学びますか?

根拠に基づいて検証し、すべての主張を取得した情報源までたどれるようにします。 ブラウザで直接実行するハンズオンコードでAI Agentsを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

AI Agentsを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのAI Agentsは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「ファクトチェックとハルシネーション防止」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このAI Agentsレッスンでコードを書いて実行できますか?

はい。すべてのAI Agentsレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 複数ステップのリサーチループ設計
  2. 情報源の検証と引用
  3. 構造化レポートの生成
  4. ファクトチェックとハルシネーション防止
← AI Agentsに戻る