T-CREATOR

GPT-5 監査可能な生成系:プロンプト/ツール実行/出力のトレーサビリティ設計

GPT-5 監査可能な生成系:プロンプト/ツール実行/出力のトレーサビリティ設計

生成 AI を業務で活用する企業が増える中、AI の振る舞いを監査できる仕組みの重要性が高まっています。特に GPT-5 のような高度な AI モデルがツール実行や複雑な推論を行うようになると、「どんな指示を受けて」「どんな処理をして」「どんな結果を出したのか」を追跡できることが、コンプライアンスや品質保証の観点から必須となるでしょう。

本記事では、GPT-5 時代の生成 AI システムにおける監査可能性を実現するため、プロンプト・ツール実行・出力という 3 つの層でトレーサビリティを設計する方法を解説します。TypeScript と Next.js を使った具体的な実装例も交えて、実務に活かせる設計パターンをご紹介しますね。

背景

生成 AI 監査の必要性

企業が生成 AI を導入する際、単に便利な機能を提供するだけでは不十分です。金融、医療、法務などの分野では、AI の判断根拠を後から検証できることが法規制や内部統制の要件となっています。

GPT-5 のような次世代モデルは、複数のツールを組み合わせた複雑なタスクを自律的に実行できます。しかし、その処理過程がブラックボックスのままでは、問題が発生したときに原因を特定できません。

以下の図は、生成 AI システムにおける監査の必要性が生じる主要な場面を示しています。

mermaidflowchart TD
  user["ユーザー"] -->|リクエスト| ai["GPT-5 システム"]
  ai -->|ツール実行| tools["外部ツール<br/>(DB/API/計算)"]
  tools -->|結果| ai
  ai -->|レスポンス| user

  ai -.->|監査ログ| audit["監査システム"]
  tools -.->|実行記録| audit

  audit -->|分析| compliance["コンプライアンス<br/>チーム"]
  audit -->|レビュー| quality["品質保証<br/>チーム"]

この図が示すように、GPT-5 システムは多様なステークホルダーに対して透明性を提供する必要があります。ユーザーの要求から AI の判断、ツールの実行、最終的な出力まで、すべてのプロセスが記録され、後から検証可能でなければなりません。

トレーサビリティの 3 つの層

監査可能な生成 AI システムを構築するには、以下の 3 層でトレーサビリティを確保する必要があります。

#記録対象目的
1プロンプト層ユーザー入力、システムプロンプト、コンテキスト入力の妥当性検証
2ツール実行層呼び出されたツール、パラメータ、実行順序処理過程の追跡
3出力層生成されたテキスト、メタデータ、信頼度スコア結果の品質評価

これらの層を適切に設計することで、AI システムの動作を完全に再現し、問題発生時の原因分析やパフォーマンス改善に活用できます。

課題

既存システムの限界

従来の生成 AI システムでは、以下のような監査上の課題が存在しています。

プロンプトの追跡不足

多くのシステムでは、ユーザーの入力テキストは記録されますが、システムが内部で追加するコンテキストや Few-shot の例文、動的に生成されるプロンプト部分が記録されていません。

後から「なぜこの回答になったのか」を検証しようとしても、実際に AI に送られた完全なプロンプトが分からないため、再現が困難です。

mermaidsequenceDiagram
  participant U as ユーザー
  participant App as アプリケーション
  participant AI as GPT-5

  U->>App: 質問を送信
  Note over App: システムプロンプト追加<br/>コンテキスト注入<br/>(記録されない)
  App->>AI: 完全なプロンプト
  AI->>App: 回答
  App->>U: 回答を表示
  Note over App: ユーザー入力と<br/>回答のみ記録

この図が示すように、従来のシステムではアプリケーション層で行われる重要な処理が記録されず、監査の死角となっています。

ツール実行の不透明性

GPT-5 は関数呼び出し(Function Calling)やプラグインを通じて外部ツールを実行できますが、どのツールがどの順序で呼ばれたか、どんなパラメータが渡されたかが記録されないケースがあります。

特に以下のような問題が顕在化しています。

#問題影響
1ツール実行の並列化による順序不明デバッグ困難
2エラー時のリトライ記録なし障害分析不可
3パラメータの暗黙的な変換予期しない動作
4実行時間の未測定パフォーマンス分析不可

出力の検証困難性

生成された出力が適切かどうかを判断する基準や、出力に使われたソース情報の記録が不十分なことも課題です。

例えば、AI が複数の情報源から回答を生成した場合、どの部分がどの情報源に基づいているかが不明だと、ファクトチェックができません。また、出力のトークン数や生成時間、使用したモデルのバージョンなどのメタデータも重要ですが、記録されていないケースが多いです。

セキュリティとプライバシーの懸念

監査ログには機密情報が含まれる可能性があるため、以下のセキュリティ要件も満たす必要があります。

  • 個人情報の適切なマスキング
  • アクセス権限の厳格な管理
  • ログの改ざん防止
  • 保存期間とデータ削除のポリシー

これらの要件を満たしながら、十分なトレーサビリティを確保することが設計上の大きな挑戦となります。

解決策

3 層トレーサビリティアーキテクチャ

監査可能な生成 AI システムを実現するため、プロンプト・ツール実行・出力の 3 層で体系的にログを記録するアーキテクチャを設計します。

以下の図は、提案する 3 層トレーサビリティアーキテクチャの全体像を示しています。

mermaidflowchart TB
  subgraph Input["プロンプト層"]
    user_input["ユーザー入力"]
    sys_prompt["システムプロンプト"]
    context["コンテキスト<br/>(履歴/RAG)"]
  end

  subgraph Processing["ツール実行層"]
    orchestrator["オーケストレーター"]
    tool1["ツールA"]
    tool2["ツールB"]
    tool3["ツールC"]
  end

  subgraph Output["出力層"]
    response["生成テキスト"]
    metadata["メタデータ"]
    citations["引用情報"]
  end

  subgraph Audit["監査基盤"]
    trace_db[("トレースDB")]
    analyzer["分析エンジン"]
  end

  user_input --> orchestrator
  sys_prompt --> orchestrator
  context --> orchestrator

  orchestrator --> tool1
  orchestrator --> tool2
  orchestrator --> tool3

  tool1 --> response
  tool2 --> response
  tool3 --> response

  response --> metadata
  response --> citations

  Input -.->|プロンプトログ| trace_db
  Processing -.->|実行ログ| trace_db
  Output -.->|出力ログ| trace_db

  trace_db --> analyzer

このアーキテクチャでは、各層が独立してログを生成し、統一的な監査基盤に集約されます。これにより、システム全体の動作を時系列で追跡できるようになります。

プロンプト層の設計

プロンプト層では、AI に送信される完全な入力を記録します。

プロンプトログのデータモデル

以下の TypeScript 型定義は、プロンプトログの構造を示しています。

typescript// プロンプトログの型定義
interface PromptLog {
  id: string; // ユニークなログID
  timestamp: Date; // 記録日時
  userId: string; // ユーザー識別子
  sessionId: string; // セッション識別子
  userInput: string; // ユーザーの入力テキスト
  systemPrompt: string; // システムプロンプト
  context: ContextData[]; // コンテキスト情報
  finalPrompt: string; // AIに送信された完全なプロンプト
  metadata: PromptMetadata; // メタデータ
}

この型定義により、プロンプトに関するすべての情報を構造化して記録できます。

typescript// コンテキストデータの型定義
interface ContextData {
  type: 'history' | 'rag' | 'example'; // コンテキストの種類
  content: string; // コンテキストの内容
  source?: string; // 情報源(RAGの場合)
  relevanceScore?: number; // 関連度スコア
}

コンテキストの種類を明確に区別することで、AI の判断材料を正確に把握できます。

typescript// プロンプトメタデータの型定義
interface PromptMetadata {
  tokenCount: number; // トークン数
  language: string; // 言語
  category?: string; // カテゴリ(業務分類など)
  priority?: 'low' | 'medium' | 'high'; // 優先度
  tags?: string[]; // タグ
}

メタデータを記録することで、後から特定の条件でログを検索・分析できます。

プロンプトログの記録実装

以下は、プロンプトログを記録する関数の実装例です。

typescript// プロンプトログを記録する関数
async function logPrompt(
  userId: string,
  sessionId: string,
  userInput: string,
  systemPrompt: string,
  context: ContextData[]
): Promise<string> {
  // 完全なプロンプトを構築
  const finalPrompt = buildFinalPrompt(
    systemPrompt,
    context,
    userInput
  );

まず、ユーザー入力、システムプロンプト、コンテキストから最終的なプロンプトを構築します。

typescript// トークン数を計算
const tokenCount = calculateTokenCount(finalPrompt);

// ログオブジェクトを生成
const promptLog: PromptLog = {
  id: generateUniqueId(),
  timestamp: new Date(),
  userId,
  sessionId,
  userInput,
  systemPrompt,
  context,
  finalPrompt,
  metadata: {
    tokenCount,
    language: detectLanguage(userInput),
    tags: extractTags(userInput),
  },
};

ログオブジェクトを生成する際、トークン数や言語検出などのメタデータも自動的に付与します。

typescript  // データベースに保存
  await saveToDatabase('prompt_logs', promptLog);

  // ログIDを返す(後続処理で使用)
  return promptLog.id;
}

生成したログをデータベースに保存し、ログ ID を返すことで、後続のツール実行ログや出力ログと関連付けられるようにします。

ツール実行層の設計

ツール実行層では、AI が呼び出すすべてのツールの実行を記録します。

ツール実行ログのデータモデル

typescript// ツール実行ログの型定義
interface ToolExecutionLog {
  id: string; // ユニークなログID
  promptLogId: string; // 関連するプロンプトログID
  timestamp: Date; // 実行開始時刻
  toolName: string; // ツール名
  parameters: Record<string, any>; // 実行パラメータ
  executionOrder: number; // 実行順序
  duration: number; // 実行時間(ミリ秒)
  result: ToolResult; // 実行結果
  status: 'success' | 'error' | 'timeout'; // ステータス
  error?: ErrorDetails; // エラー詳細
}

ツール実行の成功・失敗、実行時間などを詳細に記録することで、パフォーマンス分析やエラー原因の特定が可能になります。

typescript// ツール実行結果の型定義
interface ToolResult {
  data: any; // 実行結果データ
  dataSize: number; // データサイズ(バイト)
  summary?: string; // 結果の要約
  citations?: Citation[]; // 引用情報
}

// 引用情報の型定義
interface Citation {
  source: string; // 情報源
  url?: string; // URL
  retrievedAt: Date; // 取得日時
}

ツールが外部データを取得した場合、その出典情報も記録することで、生成された回答の信頼性を検証できます。

ツール実行の記録実装

typescript// ツール実行をラップして記録する関数
async function executeAndLogTool(
  promptLogId: string,
  toolName: string,
  parameters: Record<string, any>,
  executionOrder: number
): Promise<ToolResult> {
  const startTime = Date.now();
  let status: 'success' | 'error' | 'timeout' = 'success';
  let result: ToolResult;
  let error: ErrorDetails | undefined;

ツール実行の前に、開始時刻を記録し、実行後に経過時間を計算します。

typescript  try {
    // タイムアウト付きでツールを実行
    result = await executeToolWithTimeout(
      toolName,
      parameters,
      30000  // 30秒のタイムアウト
    );
  } catch (err) {
    // エラーハンドリング
    status = err instanceof TimeoutError ? 'timeout' : 'error';
    error = {
      code: err.code || 'UNKNOWN_ERROR',
      message: err.message,
      stack: err.stack
    };

エラーが発生した場合は、エラーコード、メッセージ、スタックトレースを記録します。エラーコードを記録することで、後から特定のエラーを検索しやすくなります。

typescript    // エラー時は空の結果を返す
    result = {
      data: null,
      dataSize: 0,
      summary: `Error: ${error.message}`
    };
  }

エラー時も一貫した形式で結果を返すことで、後続処理を簡潔に保ちます。

typescript  // 実行ログを生成
  const executionLog: ToolExecutionLog = {
    id: generateUniqueId(),
    promptLogId,
    timestamp: new Date(startTime),
    toolName,
    parameters,
    executionOrder,
    duration: Date.now() - startTime,
    result,
    status,
    error
  };

  // データベースに保存
  await saveToDatabase('tool_execution_logs', executionLog);

  return result;
}

実行ログをデータベースに保存し、結果を返します。成功時もエラー時も同じ形式でログが記録されるため、分析が容易です。

出力層の設計

出力層では、AI が生成した最終的な回答と関連メタデータを記録します。

出力ログのデータモデル

typescript// 出力ログの型定義
interface OutputLog {
  id: string; // ユニークなログID
  promptLogId: string; // 関連するプロンプトログID
  timestamp: Date; // 生成完了時刻
  generatedText: string; // 生成されたテキスト
  model: string; // 使用したモデル(例: "gpt-5")
  modelVersion: string; // モデルバージョン
  finishReason: string; // 完了理由(completed/length/...)
  usage: TokenUsage; // トークン使用量
  citations: Citation[]; // 引用情報
  qualityMetrics: QualityMetrics; // 品質メトリクス
}

モデルのバージョンまで記録することで、モデル更新前後での動作の違いを検証できます。

typescript// トークン使用量の型定義
interface TokenUsage {
  promptTokens: number; // プロンプトのトークン数
  completionTokens: number; // 生成テキストのトークン数
  totalTokens: number; // 合計トークン数
  estimatedCost: number; // 推定コスト(ドル)
}

トークン使用量とコストを記録することで、コスト分析や最適化の判断材料になります。

typescript// 品質メトリクスの型定義
interface QualityMetrics {
  confidenceScore?: number; // 信頼度スコア(0-1)
  toxicityScore?: number; // 有害性スコア(0-1)
  factualityScore?: number; // 事実性スコア(0-1)
  coherenceScore?: number; // 一貫性スコア(0-1)
  readabilityScore?: number; // 可読性スコア
}

出力の品質を自動評価することで、問題のある出力を早期に検出できます。

出力ログの記録実装

typescript// 出力ログを記録する関数
async function logOutput(
  promptLogId: string,
  generatedText: string,
  model: string,
  modelVersion: string,
  usage: TokenUsage,
  citations: Citation[]
): Promise<string> {
  // 品質メトリクスを評価
  const qualityMetrics = await evaluateQuality(generatedText);

生成されたテキストの品質を自動的に評価します。有害性や事実性のチェックは、AI の安全性を確保する上で重要です。

typescript// 出力ログを生成
const outputLog: OutputLog = {
  id: generateUniqueId(),
  promptLogId,
  timestamp: new Date(),
  generatedText,
  model,
  modelVersion,
  finishReason: 'completed',
  usage,
  citations,
  qualityMetrics,
};

// データベースに保存
await saveToDatabase('output_logs', outputLog);

出力ログをデータベースに保存します。

typescript  // 品質アラートをチェック
  if (qualityMetrics.toxicityScore > 0.8) {
    await sendAlert({
      type: 'HIGH_TOXICITY',
      outputLogId: outputLog.id,
      score: qualityMetrics.toxicityScore
    });
  }

  return outputLog.id;
}

有害性が高い出力を検出した場合、アラートを送信します。このような自動監視により、問題を迅速に発見できます。

統合監査 API

3 層のログを統合して検索・分析する API を提供します。

typescript// 統合監査APIの型定義
interface AuditQuery {
  userId?: string; // ユーザーフィルタ
  sessionId?: string; // セッションフィルタ
  startDate?: Date; // 開始日時
  endDate?: Date; // 終了日時
  toolName?: string; // ツール名フィルタ
  status?: 'success' | 'error'; // ステータスフィルタ
  minToxicity?: number; // 有害性スコアの下限
}

柔軟なクエリ条件により、様々な観点からログを分析できます。

typescript// 統合監査結果の型定義
interface AuditResult {
  promptLog: PromptLog;
  toolExecutions: ToolExecutionLog[];
  outputLog: OutputLog;
  timeline: TimelineEvent[];
}

1 つのリクエストに対する全ログを時系列で取得できます。

typescript// 監査ログを取得する関数
async function getAuditTrail(
  promptLogId: string
): Promise<AuditResult> {
  // プロンプトログを取得
  const promptLog = await getFromDatabase<PromptLog>(
    'prompt_logs',
    { id: promptLogId }
  );

  // 関連するツール実行ログを取得
  const toolExecutions = await getFromDatabase<ToolExecutionLog[]>(
    'tool_execution_logs',
    { promptLogId },
    { sort: { executionOrder: 1 } }
  );

  // 関連する出力ログを取得
  const outputLog = await getFromDatabase<OutputLog>(
    'output_logs',
    { promptLogId }
  );

プロンプトログ ID を起点に、関連するすべてのログを取得します。

typescript  // タイムラインを構築
  const timeline = buildTimeline(
    promptLog,
    toolExecutions,
    outputLog
  );

  return {
    promptLog,
    toolExecutions,
    outputLog,
    timeline
  };
}

タイムラインを構築することで、処理の流れを視覚化できます。

具体例

Next.js アプリケーションでの実装

実際の Next.js アプリケーションで、3 層トレーサビリティを実装する例を見ていきましょう。

プロジェクト構成

以下は、監査可能な GPT-5 アプリケーションのプロジェクト構成です。

typescript// プロジェクト構成
src/
├── app/
│   ├── api/
│   │   ├── chat/
│   │   │   └── route.ts          // チャットエンドポイント
│   │   └── audit/
│   │       └── route.ts          // 監査API
│   └── page.tsx                  // フロントエンド
├── lib/
│   ├── gpt/
│   │   ├── client.ts             // GPT-5クライアント
│   │   └── tools.ts              // ツール定義
│   ├── audit/
│   │   ├── logger.ts             // ログ記録ロジック
│   │   └── analyzer.ts           // ログ分析ロジック
│   └── db/
│       └── prisma.ts             // データベースクライアント
└── types/
    └── audit.ts                  // 型定義

この構成により、監査機能が明確に分離され、保守しやすくなっています。

データベーススキーマ

Prisma を使用したデータベーススキーマの定義例です。

prisma// prisma/schema.prisma

// プロンプトログテーブル
model PromptLog {
  id            String   @id @default(uuid())
  timestamp     DateTime @default(now())
  userId        String
  sessionId     String
  userInput     String   @db.Text
  systemPrompt  String   @db.Text
  context       Json
  finalPrompt   String   @db.Text
  metadata      Json

  // リレーション
  toolExecutions ToolExecutionLog[]
  output         OutputLog?

  @@index([userId, timestamp])
  @@index([sessionId])
}

インデックスを適切に設定することで、ユーザーやセッションでの検索が高速化されます。

prisma// ツール実行ログテーブル
model ToolExecutionLog {
  id              String   @id @default(uuid())
  promptLogId     String
  timestamp       DateTime @default(now())
  toolName        String
  parameters      Json
  executionOrder  Int
  duration        Int
  result          Json
  status          String
  error           Json?

  // リレーション
  promptLog       PromptLog @relation(fields: [promptLogId], references: [id])

  @@index([promptLogId, executionOrder])
  @@index([toolName, timestamp])
}

ツール名と日時でのインデックスにより、特定のツールの実行履歴を効率的に取得できます。

prisma// 出力ログテーブル
model OutputLog {
  id              String   @id @default(uuid())
  promptLogId     String   @unique
  timestamp       DateTime @default(now())
  generatedText   String   @db.Text
  model           String
  modelVersion    String
  finishReason    String
  usage           Json
  citations       Json
  qualityMetrics  Json

  // リレーション
  promptLog       PromptLog @relation(fields: [promptLogId], references: [id])

  @@index([timestamp])
  @@index([model, modelVersion])
}

モデルとバージョンでのインデックスにより、モデルごとのパフォーマンス分析が可能になります。

チャット API の実装

以下は、監査機能を組み込んだチャット API の実装例です。

typescript// src/app/api/chat/route.ts

import { NextRequest, NextResponse } from 'next/server';
import {
  logPrompt,
  executeAndLogTool,
  logOutput,
} from '@/lib/audit/logger';
import { createGPTClient } from '@/lib/gpt/client';
import { getTools } from '@/lib/gpt/tools';

// チャットリクエストの型定義
interface ChatRequest {
  message: string;
  userId: string;
  sessionId: string;
}

まず、必要なモジュールと型定義をインポートします。

typescript// POSTハンドラー
export async function POST(request: NextRequest) {
  try {
    // リクエストボディを解析
    const { message, userId, sessionId }: ChatRequest = await request.json();

    // システムプロンプトを定義
    const systemPrompt = `あなたは親切で正確なAIアシスタントです。
ユーザーの質問に対して、利用可能なツールを使って正確な情報を提供してください。`;

システムプロンプトを明示的に定義し、後でログに記録できるようにします。

typescript// コンテキストを取得(履歴やRAGなど)
const context = await getContext(userId, sessionId);

// プロンプトログを記録
const promptLogId = await logPrompt(
  userId,
  sessionId,
  message,
  systemPrompt,
  context
);

プロンプトを記録し、このリクエストのログ ID を取得します。

typescript// GPT-5クライアントを初期化
const gpt = createGPTClient('gpt-5', {
  temperature: 0.7,
  maxTokens: 2000,
});

// ツールを取得
const tools = getTools();

GPT-5 クライアントと利用可能なツールを準備します。

typescript// GPT-5にリクエストを送信
let executionOrder = 0;
const response = await gpt.chat({
  systemPrompt,
  userMessage: message,
  context,
  tools,
  // ツール実行コールバック
  onToolCall: async (toolName, parameters) => {
    executionOrder++;
    return await executeAndLogTool(
      promptLogId,
      toolName,
      parameters,
      executionOrder
    );
  },
});

ツールが呼び出されるたびに、コールバック関数が実行され、ツール実行ログが記録されます。

typescript// 出力ログを記録
const outputLogId = await logOutput(
  promptLogId,
  response.text,
  'gpt-5',
  response.modelVersion,
  response.usage,
  response.citations || []
);

// レスポンスを返す
return NextResponse.json({
  message: response.text,
  auditId: promptLogId,
  citations: response.citations,
});

最終的な出力を記録し、クライアントにレスポンスを返します。レスポンスには監査 ID も含めることで、後から詳細を確認できます。

typescript  } catch (error) {
    // エラーハンドリング
    console.error('Chat API Error:', error);
    return NextResponse.json(
      {
        error: 'Internal Server Error',
        code: 'CHAT_API_ERROR',
        message: error.message
      },
      { status: 500 }
    );
  }
}

エラーが発生した場合も、エラーコードとメッセージをクライアントに返します。

監査ダッシュボードの実装

監査ログを可視化するダッシュボードのバックエンド API です。

typescript// src/app/api/audit/route.ts

import { NextRequest, NextResponse } from 'next/server';
import { getAuditTrail } from '@/lib/audit/analyzer';

// GETハンドラー
export async function GET(request: NextRequest) {
  try {
    // クエリパラメータを取得
    const { searchParams } = new URL(request.url);
    const promptLogId = searchParams.get('id');

    if (!promptLogId) {
      return NextResponse.json(
        {
          error: 'Bad Request',
          code: 'MISSING_PROMPT_LOG_ID',
          message: 'プロンプトログIDが必要です'
        },
        { status: 400 }
      );
    }

必須パラメータのバリデーションを行い、不足している場合は適切なエラーコードとメッセージを返します。

typescript// 監査トレイルを取得
const auditTrail = await getAuditTrail(promptLogId);

// タイムライン形式で整形
const timeline = formatTimeline(auditTrail);

return NextResponse.json({
  promptLog: auditTrail.promptLog,
  toolExecutions: auditTrail.toolExecutions,
  outputLog: auditTrail.outputLog,
  timeline,
});

取得した監査ログをタイムライン形式に整形してレスポンスします。

typescript  } catch (error) {
    console.error('Audit API Error:', error);
    return NextResponse.json(
      {
        error: 'Internal Server Error',
        code: 'AUDIT_API_ERROR',
        message: error.message
      },
      { status: 500 }
    );
  }
}

エラーハンドリングでは、一貫したエラー形式を返すことで、フロントエンドでの処理を簡潔にします。

タイムライン整形関数

typescript// タイムラインイベントの型定義
interface TimelineEvent {
  timestamp: Date;
  type: 'prompt' | 'tool_execution' | 'output';
  description: string;
  details: any;
  duration?: number;
  status?: 'success' | 'error' | 'timeout';
}

タイムラインイベントは、すべてのログエントリを統一的に表現できる形式です。

typescript// タイムラインを整形する関数
function formatTimeline(auditTrail: AuditResult): TimelineEvent[] {
  const events: TimelineEvent[] = [];

  // プロンプトイベント
  events.push({
    timestamp: auditTrail.promptLog.timestamp,
    type: 'prompt',
    description: 'ユーザーリクエスト受信',
    details: {
      userInput: auditTrail.promptLog.userInput,
      tokenCount: auditTrail.promptLog.metadata.tokenCount
    }
  });

まず、プロンプト受信イベントをタイムラインに追加します。

typescript// ツール実行イベント
for (const execution of auditTrail.toolExecutions) {
  events.push({
    timestamp: execution.timestamp,
    type: 'tool_execution',
    description: `ツール実行: ${execution.toolName}`,
    details: {
      parameters: execution.parameters,
      result: execution.result,
    },
    duration: execution.duration,
    status: execution.status,
  });
}

各ツール実行をイベントとして追加します。実行順序に従って並べることで、処理の流れが明確になります。

typescript  // 出力イベント
  events.push({
    timestamp: auditTrail.outputLog.timestamp,
    type: 'output',
    description: '回答生成完了',
    details: {
      generatedText: auditTrail.outputLog.generatedText,
      tokenUsage: auditTrail.outputLog.usage,
      qualityMetrics: auditTrail.outputLog.qualityMetrics
    }
  });

  // 時系列でソート
  return events.sort((a, b) =>
    a.timestamp.getTime() - b.timestamp.getTime()
  );
}

最後に出力イベントを追加し、すべてのイベントを時系列でソートして返します。

フロントエンド実装

監査ダッシュボードのフロントエンドコンポーネントの例です。

typescript// src/components/AuditDashboard.tsx

'use client';

import { useState, useEffect } from 'react';
import type {
  AuditResult,
  TimelineEvent,
} from '@/types/audit';

// Propsの型定義
interface AuditDashboardProps {
  auditId: string;
}

監査 ID を受け取って、詳細情報を表示するコンポーネントです。

typescriptexport function AuditDashboard({ auditId }: AuditDashboardProps) {
  const [auditData, setAuditData] = useState<AuditResult | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  // 監査データを取得
  useEffect(() => {
    async function fetchAuditData() {
      try {
        const response = await fetch(`/api/audit?id=${auditId}`);

        if (!response.ok) {
          const errorData = await response.json();
          throw new Error(
            `${errorData.code}: ${errorData.message}`
          );
        }

        const data = await response.json();
        setAuditData(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }

    fetchAuditData();
  }, [auditId]);

useEffect フックを使って、コンポーネントのマウント時に監査データを取得します。エラーハンドリングも適切に行います。

typescript  if (loading) return <div>読み込み中...</div>;
  if (error) return <div>エラー: {error}</div>;
  if (!auditData) return <div>データが見つかりません</div>;

  return (
    <div className="audit-dashboard">
      {/* タイムライン表示 */}
      <TimelineView events={auditData.timeline} />

      {/* プロンプト詳細 */}
      <PromptDetails promptLog={auditData.promptLog} />

      {/* ツール実行履歴 */}
      <ToolExecutionHistory
        executions={auditData.toolExecutions}
      />

      {/* 出力詳細 */}
      <OutputDetails outputLog={auditData.outputLog} />
    </div>
  );
}

各サブコンポーネントに適切なデータを渡して、監査情報を階層的に表示します。

エラー発生時の監査例

実際にエラーが発生したケースの監査ログを見てみましょう。

typescript// エラーケースの監査ログ例
const errorAuditExample = {
  promptLog: {
    id: 'prompt-001',
    userId: 'user-123',
    userInput: '最新の株価を教えて',
    // ... その他のフィールド
  },

  toolExecutions: [
    {
      id: 'tool-001',
      toolName: 'fetchStockPrice',
      parameters: { symbol: 'AAPL' },
      executionOrder: 1,
      duration: 5234,
      status: 'error',
      error: {
        code: 'API_TIMEOUT_ERROR',
        message: 'Stock API request timed out after 5000ms',
        stack: 'Error: timeout at fetchStockPrice...',
      },
    },
  ],

  outputLog: {
    id: 'output-001',
    generatedText:
      '申し訳ございません。株価情報の取得中にタイムアウトエラーが発生しました。',
    qualityMetrics: {
      confidenceScore: 0.3, // 低い信頼度
    },
    // ... その他のフィールド
  },
};

この監査ログから、API タイムアウトが原因でエラーが発生したことが明確に分かります。エラーコード API_TIMEOUT_ERROR により、同様のエラーを検索して頻度を分析することも可能です。

パフォーマンス分析の例

監査ログを分析することで、システムのパフォーマンスを評価できます。

typescript// パフォーマンス分析クエリの実装例
async function analyzePerformance(
  startDate: Date,
  endDate: Date
): Promise<PerformanceReport> {
  // 期間内のすべてのツール実行ログを取得
  const executions = await prisma.toolExecutionLog.findMany({
    where: {
      timestamp: {
        gte: startDate,
        lte: endDate
      }
    }
  });

まず、分析期間内のツール実行ログをすべて取得します。

typescript// ツールごとに実行時間を集計
const performanceByTool: Record<
  string,
  {
    count: number;
    totalDuration: number;
    avgDuration: number;
    errorCount: number;
  }
> = {};

for (const execution of executions) {
  if (!performanceByTool[execution.toolName]) {
    performanceByTool[execution.toolName] = {
      count: 0,
      totalDuration: 0,
      avgDuration: 0,
      errorCount: 0,
    };
  }

  const stats = performanceByTool[execution.toolName];
  stats.count++;
  stats.totalDuration += execution.duration;

  if (execution.status === 'error') {
    stats.errorCount++;
  }
}

ツールごとに実行回数、合計時間、エラー回数を集計します。

typescript  // 平均実行時間を計算
  for (const toolName in performanceByTool) {
    const stats = performanceByTool[toolName];
    stats.avgDuration = stats.totalDuration / stats.count;
  }

  return {
    period: { startDate, endDate },
    totalExecutions: executions.length,
    performanceByTool
  };
}

平均実行時間を計算し、レポートとして返します。このデータを基に、遅いツールを特定して最適化できます。

まとめ

本記事では、GPT-5 時代の生成 AI システムにおける監査可能性を実現するための 3 層トレーサビリティ設計について解説しました。

プロンプト層、ツール実行層、出力層のそれぞれで適切なログを記録することで、AI システムの動作を完全に追跡できます。これにより、コンプライアンス要件を満たすだけでなく、パフォーマンス分析やエラー原因の特定、品質改善にも活用できるでしょう。

TypeScript と Next.js を使った具体的な実装例も示しましたので、実際のプロジェクトにすぐに適用できます。監査ログの設計は、システムの初期段階から組み込むことが重要ですね。

今後、生成 AI の活用がさらに広がる中で、透明性と説明責任はますます重要になります。本記事で紹介した設計パターンが、皆様の AI システム開発の参考になれば幸いです。

実装のポイント

#ポイント詳細
1完全なプロンプトの記録システムプロンプトやコンテキストを含む全体を記録
2ツール実行の詳細追跡パラメータ、実行時間、エラー情報を漏れなく記録
3出力の品質評価有害性や事実性などのメトリクスを自動評価
4タイムラインの構築すべてのイベントを時系列で可視化
5エラーコードの活用検索可能なエラーコードで問題を分類
6プライバシー保護機密情報のマスキングとアクセス制御

今後の展開

監査システムをさらに強化するために、以下のような拡張も検討できます。

  • リアルタイムアラート機能の実装(異常検知時の即時通知)
  • A/B テストのためのログ比較分析ツール
  • 監査ログの長期保存とコスト最適化(アーカイブ戦略)
  • 機械学習による異常検出(通常と異なるパターンの自動検出)
  • 監査ログのエクスポート機能(CSV、JSON 形式での出力)

これらの機能を追加することで、より高度な監査とガバナンスが可能になるでしょう。

関連リンク