Peace Melodi

ある中小規模のデジタルバンクの融資担当者は、データチームが構築した新しいモデルに興奮していました。このモデルは、収入、支出パターン、返済履歴に基づいてローン申請を1秒未満でスコアリングし、自動的に承認または却下できます。最初の週は順調に稼働しました。しかし2週目、午後半ばにモデル提供元で障害が発生しました。その時間帯に提出されたすべてのローン申請が応答待ちのままハングし、応答は決して返ってきませんでした。一部の顧客はページを更新して2回申請してしまいました。また、以前に誰かが記述したものの適切にテストされていなかったフォールバック経路が、スコアリングできなかった申請をすべて承認してしまい、本来却下されるべきいくつかの申請が承認されてしまいました。

データチームの誰も、モデル自体で間違ったことはしていませんでした。モデルは正確でした。欠けていたのはモデルの周辺のすべて——モデルが遅い、誤っている、利用できない、あるいは単にこれほど大きな意思決定を盲目的に信頼すべきではない場合に何が起こるかを決める部分でした。

本当の問題はモデル自体ではない

銀行がローン承認や不正検知のような用途でモデルを採用する際、実際にはAIの問題を抱えているわけではありません。統合の問題を抱えているのです。モデルは呼び出すサービスであり、他のサービスと同様に、遅くなったり、失敗したり、自信たっぷりに誤ったりする可能性があります。重要なのはモデルが賢いかどうかではありません。モデルが想定通りに動作しなかった瞬間にバックエンドが何をするかです。

タイムアウトを強制し、スローモデルがフロー全体をフリーズさせないようにする

最初のガードレールは、モデルへの呼び出しが無期限にハングしないようにすることです。モデル提供元が遅いかダウンしている場合、リクエストは速やかに失敗し、安全なデフォルトにフォールバックすべきで、顧客がローディング画面を眺めたまま放置されるべきではありません。

import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom, timeout, catchError } from 'rxjs';

@Injectable()
export class LoanScoringService {
  constructor(private readonly httpService: HttpService) {}

  async scoreApplication(applicationId: string, payload: Record<string, unknown>) {
    return firstValueFrom(
      this.httpService.post('https://ai-provider.example.com/score', payload).pipe(
        timeout(2000),
        catchError(() => {
          throw new HttpException(
            'Scoring service unavailable',
            HttpStatus.SERVICE_UNAVAILABLE,
          );
        }),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

ここでのフォールバックが暗黙の承認ではなく明示的なエラーである点に注目してください。サービスコールの失敗を理由にローンを承認するデフォルト動作は、それ自体として決して許容できる動作ではありません。

ローン決定におけるフェイルセーフの実際の意味を決める

タイムアウトや障害がキャッチされたら、次に何かが起こらなければなりません。そして、これほど重要な決定においては、その「何か」が自動承認であってはなりません。理にかなったフェイルセーフ経路は、申請を手動審査キューにルーティングすることです。

async handleScoringFailure(applicationId: string) {
  await this.reviewQueueService.enqueue({
    applicationId,
    reason: 'scoring_service_unavailable',
    requiresManualReview: true,
  });

  return {
    status: 'pending_review',
    message: 'Your application is being reviewed manually.',
  };
}

Enter fullscreen mode Exit fullscreen mode

これにより顧客には正直に情報が伝えられ、技術的障害が静かに意図しない財務的決定に変わることを防げます。

後で説明できるようにすべての決定をログに記録する

銀行はいずれ、特定の顧客が承認・却下・フラグ付けされた理由を、数ヶ月後、あるいは規制当局に対して説明する必要があります。つまり、モデルの出力や基になったデータを含むすべてのスコアリング決定を、単に実行して破棄するのではなく記録する必要があります。

import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';

@Entity('loan_decisions')
export class LoanDecision {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  applicationId: string;

  @Column({ type: 'jsonb' })
  modelInput: Record<string, unknown>;

  @Column({ type: 'jsonb' })
  modelOutput: Record<string, unknown>;

  @Column()
  finalDecision: string;

  @Column({ default: false })
  requiredManualReview: boolean;

  @CreateDateColumn()
  createdAt: Date;
}

Enter fullscreen mode Exit fullscreen mode

これにより、1秒未満で下された決定でも、後で完全に再現・説明できるようになり、これは他のほとんどのアプリケーションよりも銀行業務においてはるかに重要です。

最も重要な決定には人間を関与させる

すべての決定に人間のレビューが必要なわけではありません。しかし、実際の影響が大きい決定——高額ローン、境界線上のスコア、却下に対する顧客からの異議申し立て——は、モデルだけで最終決定されるべきではありません。NestJSは、銀行が実際に合意したルールに基づき、特定の結果を直接顧客に送るのではなく審査キューにルーティングするチェックを強制する、きれいな場所を提供します。

より大きな視点

NestJSはAIモデルをより正確にするわけではなく、そうしようとすべきでもありません。提供するのはモデルの周辺構造——タイムアウトを強制する場所、フェイルセーフの実際の意味を決める場所、後で説明できるようにすべての決定をログに記録する場所、人間がレビューすべき決定に人間を関与させる場所——です。この構造こそが、AIモデルをローン承認のような重大な用途に十分安全に使えるようにするものです。

あなたのチームが実金や実顧客に関わるプロセスにAIを導入しようとしているなら、その周辺のガードレールを適切に構築する方法についてお話しできれば幸いです。

私はPeace Melodi、バックエンドソフトウェアエンジニアです。ビジネスを大規模にスケールさせ、数百万ユーザーを快適に扱い、強力なスケーラビリティとセキュリティを備えたい方は、お気軽にご連絡ください。

LinkedIn: https://www.linkedin.com/in/melodi-peace-406494368
GitHub: https://github.com/PeaceMelodi