1
1

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エージェントが生成した監査コードに潜んでいた「未来を見てしまうバグ」

1
Last updated at Posted at 2026-07-20

本記事では、生成AIにantigravity CLIを使用しました。

  • 金融監査AIのサンプルコードをAIエージェントに生成させたら、コードは正しく動くのに不正取引を検知できないバグが埋まっていた
  • 原因はロジックではなく、モックデータの中に「これから検知すべき異常」が事前に混入していたこと
  • これは機械学習・金融システムの文脈で Data Leakage / Look-ahead Bias と呼ばれる、よく知られた設計上の落とし穴
  • AIエージェントが生成したコードは、文法・型は正しくても「業務の時系列」まではレビューしないと壊れていることがある

何が起きたか

AIエージェントに、以下のような簡易的な金融監査エージェントを生成させました。ルールベースのスクリーニングで検知した「疑わしい取引」を、口座の取引履歴と照合してリスクスコアを算出する、というものです。

from typing import Dict, Any, List

# データベースのモック(監査対象の口座履歴)
TRANSACTION_DB = {
    "ACC001": [
        {"amount": 5000, "location": "東京", "category": "食費"},
        {"amount": 1000000, "location": "ロンドン", "category": "宝飾品"}, # 異常値
        {"amount": 8000, "location": "東京", "category": "交通費"}
    ]
}

class FinancialAuditAgent:
    def evaluate_alert(self, account_id: str, alert_data: Dict[str, Any]) -> Dict[str, Any]:
        history = TRANSACTION_DB.get(account_id, [])
        locations = {tx["location"] for tx in history}

        risk_score = 0.0
        reason = ""

        if alert_data["location"] not in locations:
            risk_score += 0.7
            reason += "普段使用されていない場所での取引です。 "
        if alert_data["amount"] > 500000:
            risk_score += 0.2
            reason += "取引金額が平均値を大きく上回っています。"

        is_fraud = risk_score >= 0.8

        return {
            "is_fraud": is_fraud,
            "risk_score": risk_score,
            "reason": reason.strip() or "通常の取引パターン内です。"
        }

このコードに「ロンドンで100万円の取引」というアラートを流し込んだところ、結果は以下の通りでした。

[Audit Agent] アラート評価中: 口座 ACC001 - 金額 1000000円
  -> 口座履歴を確認: 過去の取引場所 = {'東京', 'ロンドン'}
✅ 【パス】監査クリア (スコア: 0.2) - 取引金額が平均値を大きく上回っています。

普段使わないロンドンでの高額決済という、明らかに高リスクなケースにもかかわらず、「監査クリア」と誤判定されました。スコアも0.8の閾値に届かず0.2止まりです。

原因:監査対象がすでに履歴に含まれていた

コードのロジック自体はどこも間違っていません。問題はTRANSACTION_DBというモックデータの中身です。

TRANSACTION_DB = {
    "ACC001": [
        {"amount": 5000,    "location": "東京"},
        {"amount": 1000000, "location": "ロンドン"},  # ← これから監査する取引そのもの
        {"amount": 8000,    "location": "東京"},
    ]
}

本来なら「これから監査すべき対象」であるロンドンでの100万円取引が、最初から「過去の正常な履歴」としてDBに登録されていたのです。正しい履歴データであれば、取引場所は {'東京'} だけのはずでした。

このデータを使って

locations = {tx["location"] for tx in history}
# => {'東京', 'ロンドン'}

を実行すると、判定式

if alert_data["location"] not in locations:

False になります。ロンドンはすでにlocationsの中にあるので、「普段と異なる場所」として検知されず、加点(+0.7)が発生しません。結果として金額分の+0.2しか積み上がらず、閾値0.8を超えられませんでした。

なぜコードレビューで気づきにくいのか

実際にこのコードをレビューしても、多くの人は違和感を持たないはずです。

if alert_data["location"] not in locations:

このロジック自体は健全です。in の使い方も、リスクスコアの積み上げ方も、特に問題は見当たりません。壊れているのはコードではなく、コードが前提とするデータの方です。

  • プログラムは正しい
  • 入力データの前提が間違っていた

この種の「意味論(semantic)のバグ」は、静的解析やユニットテストでは検出しづらく、レビュアーがデータの中身まで時系列的に検証しない限り見逃されます。

このバグには名前がある

機械学習や金融システムの文脈では、この種の問題は

  • Data Leakage
  • Target Leakage
  • Look-ahead Bias

と呼ばれています。評価・学習に使うデータの中に、本来はまだ知り得ないはずの未来の情報(今回で言えば監査対象そのもの)が混入してしまう、典型的な設計ミスです。

金融システムにおける正しい処理順序は

新しい取引 → 監査 → 問題なし → 履歴へ登録

であるべきところ、今回生成されたコードのデータ設計は

履歴へ登録 → 監査

という順序になっていました。これは未来の情報を参照して判断していることに等しく、異常検知の精度を根本から損ないます。

AIエージェントがなぜこのコードを書いたのか

これはPythonの文法ミスではありません。AIエージェントは「取引履歴のサンプルデータを作る」という要求を満たすために、サンプルとして手近にある取引(=監査対象そのもの)を履歴に含めてしまいました。

人間であれば「これはこれから評価するデータだから、履歴に入れてはいけない」と直感的に判断します。しかしLLMは時系列や業務フローを暗黙知として理解しているわけではなく、局所的な目的(それらしいサンプルデータを作ること)を優先した結果、こうした設計ミスを生成コードに埋め込むことがあります。

まとめ

  • AIエージェントが生成したコードは、文法的にも型的にも正しく動作するのに、業務ロジック(時系列)が壊れていることがある
  • 特にデータ生成・モック作成をAIに任せると、Data Leakage / Look-ahead Bias のような「データの前提」に関わるミスが紛れ込みやすい
  • コードレビューだけでなく、「このデータはいつ時点で存在してよいものか」という時系列の整合性確認まで含めて初めて品質が保証される

AIエージェント時代のレビューでは、「動くから正しい」ではなく「このデータは未来を覗き見していないか」という視点を持つことが重要だと感じた事例でした。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?