トップ開発者向け教科書第4章

第4章

技術実践ガイド 読了目安 14分

第4章 既存アプリケーションへのAI Agent融合手法(実践レシピ)

対象=一般Web/アプリ開発者・バックエンドエンジニア。すでに稼働しているWebアプリや社内基幹システムを壊さずに、安全かつ段階的にAI Agentを組み込むためのアーキテクチャパターンを解説する。


1. 3大統合アーキテクチャパターン

既存システムにAgentを追加する場合、システムの性質に応じて以下の3つのパターンから選択します。

【パターンA: API Gateway 方式】
 ユーザー ──► [AI Gateway (Agent)] ──(REST/GraphQL)──► [既存バックエンド]
              ・自然言語の意図をAPIコールに変換

【パターンB: Sidecar / Copilot 方式】
 ユーザー ──► [既存フロントエンド画面] ◄── [Copilot Panel (Agent)]
              ・画面コンテキストを参照し、入力補助・提案・ドラフト生成

【パターンC: Event-driven / Background Worker 方式】
 [既存DB / MQ] ──(Webhook/Queue)──► [Agent Worker] ──► [ドラフトDB] ──► [承認画面]
              ・非同期バッチ・例外監視・バックグラウンド分析
パターンユースケース実装難易度既存コードへの影響
A. API Gateway方式自然言語インターフェース、Slack/Teams連携低(既存APIをツールとして呼ぶだけ)
B. Sidecar方式業務画面の入力支援、見積作成支援低〜中低(横にチャット/提案パネルを追加)
C. Event-driven方式請求書自動照合、障害検知、夜間バッチ中〜高極小(Webhookやメッセージキューで疎結合)

2. パターン別の詳細設計とコード構成例

パターンA:API Gateway / Proxy 方式

既存のREST API群を手前に置いたAgentから「Function Calling」経由で叩かせる構成です。

// agent-runner.ts (Node.js / Express / Next.js API)
import { OpenAI } from "openai";

const tools = [
  {
    type: "function",
    function: {
      name: "search_orders",
      description: "既存の受注管理APIから顧客名または注文ステータスで注文を検索する",
      parameters: {
        type: "object",
        properties: {
          customerName: { type: "string", description: "顧客名" },
          status: { type: "string", enum: ["pending", "shipped", "delivered"] }
        },
        required: ["status"]
      }
    }
  }
];

// Agentループ実行部
async function handleUserRequest(userMessage: string) {
  const messages = [
    { role: "system", content: "あなたは社内受注管理アシスタントです。既存APIを呼び出して回答してください。" },
    { role: "user", content: userMessage }
  ];

  const response = await openai.chat.completions.create({
    model: "gpt-4o",
    messages,
    tools,
    tool_choice: "auto"
  });

  const message = response.choices[0].message;
  if (message.tool_calls) {
    for (const toolCall of message.tool_calls) {
      if (toolCall.function.name === "search_orders") {
        const args = JSON.parse(toolCall.function.arguments);
        // 既存のバックエンドAPIを直接コール(サービス間認証トークンを使用)
        const apiResult = await existingBackendClient.getOrders(args);
        
        // 実行結果を会話に追加して最終回答を生成
        messages.push(message);
        messages.push({
          role: "tool",
          tool_call_id: toolCall.id,
          content: JSON.stringify(apiResult)
        });
        
        return await openai.chat.completions.create({ model: "gpt-4o", messages });
      }
    }
  }
  return response;
}

3. 実践:安全な Human-in-the-Loop(HITL)承認フローの実装

業務システムにおいて、Agentが直接 UPDATE orders SET status = 'cancelled' などの更新を行うのは重大な事故の元です。 必ず 「ドラフト作成 → 承認キュー投入 → 人間が画面で確認 → 既存APIで正式確定」 の2段階構成(State Machine)をとります。

承認ステートマシンのテーブル設計(例: PostgreSQL)

-- Agentが生成したドラフトと承認状態を管理するテーブル
CREATE TABLE agent_action_proposals (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    task_id VARCHAR(64) NOT NULL,
    action_type VARCHAR(64) NOT NULL,       -- 例: 'INVOICE_APPROVE', 'SEND_EMAIL'
    proposed_payload JSONB NOT NULL,         -- 実行しようとしているパラメータ
    reasoning_log TEXT NOT NULL,             -- なぜこの判断をしたかのAgent思考ログ
    status VARCHAR(32) NOT NULL DEFAULT 'PENDING', -- PENDING, APPROVED, REJECTED, EXECUTED
    created_by_agent VARCHAR(64) NOT NULL,
    reviewed_by VARCHAR(64),                 -- 承認者の社員ID
    reviewed_at TIMESTAMP WITH TIME ZONE,
    execution_error TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

バックエンドでの安全な分離処理

// Agentに持たせるツールは「実行ツール」ではなく「提案作成ツール」にする
async function proposeInvoiceApproval(payload: InvoiceApprovalPayload, reasoning: string) {
  // DBの提案テーブルに保存するだけで、本番の決済APIは呼ばない!
  const proposal = await db.insert(agentActionProposals).values({
    actionType: 'INVOICE_APPROVE',
    proposedPayload: payload,
    reasoningLog: reasoning,
    status: 'PENDING'
  }).returning();

  // 承認者のSlack/Teamsに通知
  await notifyReviewer(proposal.id, payload.invoiceId, payload.amount);

  return {
    success: true,
    message: `承認申請ドラフト (ID: ${proposal.id}) を作成しました。人間の確認待ちです。`
  };
}

// 人間が承認画面で「承認」ボタンを押したときに呼ばれる正式確定ハンドラー
async function approveProposalByHuman(proposalId: string, reviewerUserId: string) {
  const proposal = await db.getProposal(proposalId);
  if (proposal.status !== 'PENDING') throw new Error('すでに処理済みです');

  // ここで初めて、本来の本番決済・更新トランザクションを実行する
  const result = await realProductionService.executeAction(proposal.actionType, proposal.proposedPayload);

  // 監査ログ(Audit Trail)を更新
  await db.updateProposal(proposalId, {
    status: 'EXECUTED',
    reviewedBy: reviewerUserId,
    reviewedAt: new Date()
  });

  return result;
}

4. 本番運用のためのオブザーバビリティ(可観測性)と防御策

  1. タイムアウトと最大リトライ回数のハードリミット:
  • 1リクエストのAgentループは最大8ターン、全体の実行時間は最大60秒で強制終了する。
  1. トークン上限と月次コスト監視:
  • ユーザー/テナントごとに日次・月次の消費トークン上限を設定し、超過時はHTTP 429(Too Many Requests)を返す。
  1. トレーシング(OpenTelemetry / Langfuse):
  • 各ReActターンの Thought、Action、Latency、消費Token、エラー率をすべてトレースIDで紐づけ、ダッシュボードで異常なループやレイテンシ劣化を即座に検知する。
  1. キルスイッチ(緊急停止機能):
  • システム環境変数やRedisフラグで AGENT_KILL_SWITCH_ENABLED=true にすると、Agent機能のみをバイパスして即座に従来の手動運用へフォールバックできる設計にしておく。

次のアクション

ソフトウェア開発全9工程プレイブックへ → 技術相談・導入支援へ