AI

GPT-6.1 Sol Multi-agent入門——FirestoreでAI分担レビューを監査する

OpenAI APIのGPT-6.1 SolとResponses API Multi-agent betaを題材に、Next.js + FirestoreでAI分担レビューの実行台帳を作る実践手順を解説します。

2026年10月2日
OpenAIGPT-6.1 SolResponses APIMulti-agentFirestore
GPT-6.1 Sol Multi-agent入門——FirestoreでAI分担レビューを監査する

はじめに

AIにコードレビューや仕様レビューを頼むとき、1つの長いプロンプトで全部を見てもらう設計には限界があります。

よくある依頼1エージェントだけで困ること
PR差分のレビュー正しさ、セキュリティ、テスト観点が混ざり、指摘の優先度がぶれやすい
20〜30件のIssue整理どの観点で判断したのか、あとから説明しにくい
リリース前チェック速度を上げるほど、抜け漏れの検証が雑になりやすい

OpenAI APIのChangelogでは、2026年9月29日に gpt-6.1-sol がResponses APIとChat Completions APIへ追加され、あわせてResponses APIのMulti-agent betaをサポートすると案内されています。Multi-agentでは、multi_agent.enabled を有効化すると、root agentがサブエージェントを生成し、分担、メッセージング、待機を使って作業できます。

本記事では、架空の開発支援SaaS「ReviewLane」を題材に、Next.js App Router + Firestore + TypeScript + GitHub Actionsで「AI分担レビューの実行台帳」を作ります。この記事を読み終えると、Multi-agentをただ呼ぶだけではなく、誰が、どのPRに、どのモデルで、どんな観点のレビューを実行したかを監査できる形にできます。

参考にした一次情報は次の通りです。

何が変わったのか

gpt-6.1-sol は、公式モデルページで複雑なコーディング、computer use、専門的な作業向けのモデルとして説明されています。モデルページでは、1,050,000トークンのコンテキストウィンドウ、128,000トークンの最大出力、Responses APIでのツール利用、low、medium、high、xhigh、max のreasoning effort対応も案内されています。

ただし、実務で大事なのは「長く入る」ことよりも「分担の根拠を残す」ことです。PRレビューなら、1つのAIに全部を任せるより、正しさ、セキュリティ、テストの観点を明示して、最後にroot agentへ統合させる方がレビューしやすくなります。

事前準備

ReviewLaneでは、OpenAI APIキーをNext.jsのサーバー側だけで扱います。クライアントコンポーネントから直接呼ばず、Route Handlerで認証、認可、入力制限、監査ログをまとめて処理します。

yarn add openai firebase-admin

環境変数は実行環境に設定します。GitHub Actionsで夜間レビューを動かす場合も、リポジトリへAPIキーを置かず、Secretsやホスティング環境のシークレット管理を使います。

OPENAI_API_KEY=sk-...

ハンズオン1: Firestoreに実行台帳を作る

まず、AIレビューを「実行した事実」として保存する型を決めます。本文や差分を丸ごと保存すると、権限範囲を広げすぎることがあります。台帳には、対象PR、観点、モデル、実行者、利用量、レイテンシを中心に残します。

export type MultiAgentReviewRun = {
  provider: "openai";
  model: "gpt-6.1-sol";
  projectId: string;
  repository: string;
  pullRequestNumber: number;
  requestedBy: string;
  perspectives: Array<"correctness" | "security" | "tests">;
  status: "running" | "completed" | "failed";
  responseId: string | null;
  usage: unknown;
  latencyMs: number | null;
  resultPreview: string | null;
  createdAt: FirebaseFirestore.FieldValue;
  updatedAt: FirebaseFirestore.FieldValue;
};

usage はSDKやAPIの返却形式に合わせてそのまま保存できるよう、最初は unknown にしています。本番では、実際に使うSDKバージョンの型へ寄せると、集計クエリを書きやすくなります。

ハンズオン2: Multi-agentレビューを呼び出す

公式のMulti-agent guideでは、TypeScript例として client.beta.responses.create、multi_agent.enabled、max_concurrent_subagents、betas: ["responses_multi_agent=v1"] を使う形が示されています。ここでもその形に合わせます。

// src/lib/ai/openaiMultiAgentReview.ts
import OpenAI from "openai";

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

type ReviewPerspective = "correctness" | "security" | "tests";

type ReviewInput = {
  diff: string;
  perspectives: ReviewPerspective[];
};

function readRootFinalAnswer(response: Awaited<ReturnType<typeof openai.beta.responses.create>>) {
  return response.output
    .flatMap((item) =>
      item.type === "message" &&
      item.agent?.agent_name === "/root" &&
      item.phase === "final_answer"
        ? item.content
        : [],
    )
    .filter((part) => part.type === "output_text")
    .map((part) => part.text)
    .join("");
}

export async function runMultiAgentReview(input: ReviewInput) {
  const perspectiveText = input.perspectives.join(", ");

  const response = await openai.beta.responses.create({
    model: "gpt-6.1-sol",
    input:
      "次のpull request diffを日本語でレビューしてください。\n" +
      `観点: ${perspectiveText}\n` +
      "各観点をサブエージェントに分担し、重複や矛盾を統合して、重要度順に返してください。\n\n" +
      `<diff>\n${input.diff}\n</diff>`,
    multi_agent: {
      enabled: true,
      max_concurrent_subagents: 3,
    },
    betas: ["responses_multi_agent=v1"],
  });

  return {
    response,
    text: readRootFinalAnswer(response),
  };
}

ここで重要なのは、サブエージェント名や内部イベントの細かい構造にアプリを強く依存させないことです。ガイドではroot agentの最終回答を取り出す例が示されているため、まずは最終回答と response.id、usage を台帳に残し、詳細な中間出力の保存は必要になってから検討します。

ハンズオン3: Next.js Route Handlerで監査する

次に、PR差分を受け取り、Firestoreへ実行台帳を作ってからMulti-agentを呼びます。requireUser、assertCanReadProject、fetchPullRequestDiff はアプリ側の既存ヘルパーという想定です。

// src/app/api/projects/[projectId]/ai-pr-review/route.ts
import { FieldValue } from "firebase-admin/firestore";
import { adminDb } from "@/lib/firebase/admin";
import { requireUser } from "@/lib/auth/requireUser";
import { assertCanReadProject } from "@/lib/projects/permissions";
import { fetchPullRequestDiff } from "@/lib/github/fetchPullRequestDiff";
import { runMultiAgentReview } from "@/lib/ai/openaiMultiAgentReview";

type Params = {
  params: Promise<{ projectId: string }>;
};

type RequestBody = {
  repository: string;
  pullRequestNumber: number;
};

export async function POST(request: Request, { params }: Params) {
  const user = await requireUser(request);
  const { projectId } = await params;
  const body = (await request.json()) as RequestBody;

  await assertCanReadProject(user.uid, projectId);

  const diff = await fetchPullRequestDiff({
    repository: body.repository,
    pullRequestNumber: body.pullRequestNumber,
  });

  const safeDiff = diff.slice(0, 180_000);
  const perspectives = ["correctness", "security", "tests"] as const;

  const runRef = await adminDb.collection("multiAgentReviewRuns").add({
    provider: "openai",
    model: "gpt-6.1-sol",
    projectId,
    repository: body.repository,
    pullRequestNumber: body.pullRequestNumber,
    requestedBy: user.uid,
    perspectives,
    status: "running",
    responseId: null,
    usage: null,
    latencyMs: null,
    resultPreview: null,
    createdAt: FieldValue.serverTimestamp(),
    updatedAt: FieldValue.serverTimestamp(),
  });

  const startedAt = Date.now();

  try {
    const { response, text } = await runMultiAgentReview({
      diff: safeDiff,
      perspectives: [...perspectives],
    });

    await runRef.update({
      status: "completed",
      responseId: response.id,
      usage: response.usage ?? null,
      latencyMs: Date.now() - startedAt,
      resultPreview: text.slice(0, 1000),
      updatedAt: FieldValue.serverTimestamp(),
    });

    return Response.json({
      runId: runRef.id,
      answer: text,
    });
  } catch (error) {
    await runRef.update({
      status: "failed",
      latencyMs: Date.now() - startedAt,
      errorMessage: error instanceof Error ? error.message : "unknown error",
      updatedAt: FieldValue.serverTimestamp(),
    });

    return Response.json({ runId: runRef.id }, { status: 502 });
  }
}

差分は最大コンテキストに任せて無制限に送るのではなく、まずアプリ側で上限を置きます。巨大PRは、ファイル種別やディレクトリごとに分割し、3〜4件ずつレビューする方が再実行しやすく、費用も説明しやすくなります。

ハンズオン4: GitHub Actionsで夜間バッチにする

人がボタンを押すレビューだけでなく、ラベル付きPRを夜間にまとめて確認する運用もできます。GitHub Actionsから内部APIを叩く場合は、アプリ側で専用トークンを検証し、対象を少量ずつ処理します。

name: nightly-ai-pr-review

on:
  schedule:
    - cron: "0 18 * * 1-5"
  workflow_dispatch:

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - name: Trigger ReviewLane batch
        run: |
          curl -X POST "${{ secrets.REVIEWLANE_BATCH_URL }}" \
            -H "Authorization: Bearer ${{ secrets.REVIEWLANE_CRON_TOKEN }}" \
            -H "Content-Type: application/json" \
            -d '{"limit": 5}'

バッチの粒度は小さく始めます。Multi-agentは便利ですが、サブエージェントに分担させるぶん、1回の実行が重くなりがちです。最初は5PR、各PRで3観点、失敗時は次回へ回す、くらいの控えめな設計が扱いやすいです。

運用で見るべき指標

Firestoreに台帳を残すと、モデル変更の判断を感覚ではなく数値でできます。

指標見る理由Firestoreでの持ち方
latencyMs開発フローを止めないか確認する実行ごとに保存
usage費用増加を早めに検知するAPI返却値を保存
perspectivesどの観点をAIに任せたか説明する配列で保存
resultPreview監査画面で概要を確認する1000文字程度に制限
status失敗や再実行対象を拾うrunning、completed、failed

さらに、レビュー結果をそのままPRコメントに投稿する前に、人の承認ステップを挟むのがおすすめです。特にセキュリティ指摘は誤検知でも開発者の時間を奪うので、Firestore上で「下書き」「承認済み」「投稿済み」を分けると運用が落ち着きます。

注意点

Multi-agent betaを使うコードは、通常のResponses API呼び出しと分けておくのが安全です。betas: ["responses_multi_agent=v1"] を必要な箇所だけに閉じ込めると、将来の仕様変更時に影響範囲を追いやすくなります。

また、Chat Completions APIも gpt-6.1-sol のエンドポイントとして案内されていますが、公式モデルページではツール呼び出しにはResponses APIを使うよう説明されています。PRレビューのように将来的にfile search、MCP、code interpreterなどのツールを組み合わせたい処理は、最初からResponses API側に寄せるのが自然です。

まとめ

gpt-6.1-sol とResponses API Multi-agent betaは、AIレビューを「強い1人」から「観点ごとに分担するチーム」へ近づけるアップデートです。

実務では、サブエージェントの賢さだけに期待するのではなく、Next.jsで呼び出し口を絞り、Firestoreで実行台帳を残し、GitHub Actionsで小さく自動化するのが現実的です。まずはPRレビューの3観点、5件程度のバッチから始め、レイテンシ、費用、指摘の採用率を見ながら広げていきましょう。