Copilot Metrics APIのエージェント別スキーマ解説|agent_id集計と二重計上を防ぐ方法

GitHub Copilot usage metrics APIでAgent App別の利用状況を集計する場合は、新しく追加されたtotals_by_3rd_party_agent配列を利用します。

ただし、安全に集計するために必ず守るべきポイントが2つあります。期間をまたぐ集計やデータ結合にはagent_nameではなくagent_idを使うこと、そして配列内のuser_initiated_interaction_countをトップレベルの同名フィールドへ加算しないことです。

GitHubは2026年8月7日、Enterprise、Organization、ユーザー単位の1日・28日レポートで、認識されたAgent Appごとの利用状況を取得できるようにしました。これにより、複数のエージェントを導入している組織でも、エージェントごとの利用実態を分けて分析できます。(The GitHub Blog)

目次

Copilot Metrics APIに追加されたtotals_by_3rd_party_agentとは

totals_by_3rd_party_agentは、レポート期間中に利用された、認識済みのAgent Appごとのメトリックを格納するオプションの配列です。

従来は外部エージェントの利用が実質的に一つのまとまりとして扱われていましたが、この配列によってエージェント別のジョブ開始数やセッション数を取得できます。公式仕様上のレポート別の違いは次のとおりです。(The GitHub Blog)

レポート種別配列が入る位置session_count主な用途
Enterprise・Organizationの1日集約レポート集約レコード内ありその日のエージェント別利用比較
Enterprise・Organizationの28日集約レポートday_totals[]内の日別レコードあり28日間の日次推移
Enterprise・Organizationのユーザー別1日レポートユーザーレコード内なしエージェント別の利用者把握
Enterprise・Organizationのユーザー別28日レポート期間単位のユーザーレコード内なし28日間のユーザー別利用分析

集約レポートとユーザー別レポートではスキーマが異なります。特に、ユーザー別レポートにsession_countが含まれない点は、データベース設計やBIツールへの取り込み時に注意が必要です。(GitHub Docs)

エージェント別スキーマのフィールド構成

totals_by_3rd_party_agentの各要素には、次のフィールドが含まれます。(GitHub Docs)

JSONフィールド意味取り込み先で推奨する名前
agent_idstringAgent Appの安定した識別子agent_id
agent_namestring画面表示用のエージェント名agent_name_snapshot
user_initiated_interaction_countintegerユーザーが開始したAgent Appジョブの開始数agent_job_start_count
session_countintegerAgent Appのセッション数agent_session_count

agent_idは、値が数字だけに見える場合でも文字列として定義されています。数値型へ変換せず、文字列のまま保存するのが安全です。(GitHub Docs)

簡略化したJSONの例

{
  "day": "2026-08-07",
  "user_initiated_interaction_count": 120,
  "totals_by_3rd_party_agent": [
    {
      "agent_id": "1234567",
      "agent_name": "Example Agent",
      "session_count": 18,
      "user_initiated_interaction_count": 31
    },
    {
      "agent_id": "7654321",
      "agent_name": "Another Agent",
      "session_count": 12,
      "user_initiated_interaction_count": 19
    }
  ]
}

この例では、トップレベルと配列内に同じuser_initiated_interaction_countという名前のフィールドがあります。しかし、両者は意味も計測単位も異なります。

集計と結合にはagent_nameではなくagent_idを使う

agent_nameは表示名であり、将来変更される可能性があります。一方、agent_idはレポート期間をまたいでデータを結合するための安定した識別子です。GitHubも、グループ化や期間比較にはagent_idを使用するよう案内しています。(The GitHub Blog)

例えば、同じエージェントの表示名が途中で変更されたとします。

agent_idagent_nameジョブ開始数
8月1234567Example Agent100
9月1234567Example Agent for GitHub140

agent_nameで集計すると、8月と9月が別のエージェントとして分割されます。agent_idで集計すれば、同じエージェントの継続データとして合計240件を取得できます。

実務では、次のように扱うと安全です。

  • 主キーや結合キーにはagent_idを使用する
  • agent_nameは表示用のスナップショットとして保存する
  • ダッシュボードでは最新のagent_nameを表示する
  • 過去レポートのagent_nameを書き換えず、名称変更履歴を残す
  • agent_idを整数へ変換しない

また、同じエージェントに対応する複数のインテグレーションがある場合、それらの活動は同じagent_idの一つのエントリへまとめられます。逆に、エージェントを特定できない活動は配列に含まれません。そのため、この配列だけで個別のGitHub Appインストール単位や、未識別エージェントを分析できるわけではありません。(The GitHub Blog)

同名のuser_initiated_interaction_countを加算してはいけない

最も間違えやすいのが、トップレベルと配列内にある同名フィールドの扱いです。

JSONパス計測しているもの
user_initiated_interaction_countCopilotへ明示的に送信されたプロンプト数
totals_by_3rd_party_agent[].user_initiated_interaction_countユーザー操作によって開始されたAgent Appジョブ数

配列内の値はサーバー側のAgent Appジョブ活動から取得されます。トップレベルの値は、別の対応済みテレメトリから取得された明示的なプロンプト数です。GitHubは、両者を加算したり、同じ指標として扱ったりしないよう明記しています。(The GitHub Blog)

先ほどのJSON例では、次の計算は誤りです。

120 + 31 + 19 = 170件のユーザー操作

正しくは、別々のKPIとして表示します。

Copilotへの明示的なプロンプト数:120件
認識済みAgent Appのジョブ開始数:50件

配列内の31件と19件を合計し、認識済みAgent Appのジョブ開始数を50件とすることはできます。ただし、それをトップレベルの120件へ加えて「総インタラクション数」とすることはできません。

取り込み先では、同じフィールド名をそのまま使わず、次のように意味が分かる名前へ変更すると事故を防げます。

トップレベル:
copilot_explicit_prompt_count

Agent App配列内:
agent_job_start_count

配列を展開するとトップレベル値が重複する点にも注意する

二重計上は、フィールド同士を直接加算した場合だけでなく、配列をテーブルへ展開した場合にも発生します。

元データが次の内容だったとします。

トップレベルのプロンプト数:120
Agent App数:2

BIツールやETLで配列を2行へ展開し、各行へトップレベルの120をコピーすると、次のようなテーブルになります。

agent_idAgent Appジョブ開始数トップレベルのプロンプト数
123456731120
765432119120

このテーブルでプロンプト数を単純に合計すると、実際は120件なのに240件になります。

安全な設計は、トップレベル指標とエージェント別指標を別テーブルに分ける方法です。

テーブルデータ粒度保存する主な値
copilot_usage_fact日・組織・ユーザーなどのレポート粒度copilot_explicit_prompt_count
copilot_agent_usage_fact日・組織・ユーザー・agent_idagent_job_start_countagent_session_count

一つのテーブルへまとめる場合は、トップレベル値を合計不可の属性として扱う必要があります。ただし、Power BIや集計SQLで誤ってSUMされる可能性を考えると、別テーブルに分ける方が安全です。

session_countがない場合は0ではなくNULLとして扱う

session_countは、EnterpriseまたはOrganizationの集約レポートにだけ含まれます。ユーザー別レポートでは省略されます。(The GitHub Blog)

ユーザー別レポートにsession_countがないからといって、「そのユーザーのセッション数は0」と解釈してはいけません。正しい意味は「このレポート粒度では提供されていない」です。

したがって、データベースには次のように保存します。

集約レポートでsession_countが10:
10として保存

ユーザー別レポートでsession_countが存在しない:
NULLとして保存

0とNULLを区別しないと、「セッションを一度も使っていないユーザー」と「APIがセッション数を返していないユーザー」を判別できなくなります。

配列が存在しない場合に備える

totals_by_3rd_party_agentは必須フィールドではありません。対象期間に認識済みAgent Appの活動がなければ、空配列ではなくフィールド自体が省略されることがあります。今回の変更は既存フィールドの形を変えない後方互換の追加です。(The GitHub Blog)

そのため、次のような直接参照は避けます。

record.totals_by_3rd_party_agent.map(...)

フィールドがない場合でも処理を継続できるよう、配列かどうかを確認します。

const agents = Array.isArray(record.totals_by_3rd_party_agent)
  ? record.totals_by_3rd_party_agent
  : [];

フィールドが省略されている場合は、エラー行や架空の「Unknown Agent」行を作らず、エージェント別テーブルへ0行を出力するのが基本です。

ただし、特定できないエージェントの活動も配列から除外されます。そのため、配列がないことを根拠に「すべての外部エージェント利用が完全に0だった」とまで断定しない方が安全です。(The GitHub Blog)

28日レポートはday_totalsの位置を確認する

EnterpriseとOrganizationの28日集約レポートでは、日別データがday_totals配列に格納されます。totals_by_3rd_party_agentも、それぞれの日別レコードの中に入ります。(GitHub Docs)

{
  "report_start_day": "2026-07-11",
  "report_end_day": "2026-08-07",
  "day_totals": [
    {
      "day": "2026-08-07",
      "totals_by_3rd_party_agent": [
        {
          "agent_id": "1234567",
          "agent_name": "Example Agent",
          "session_count": 18,
          "user_initiated_interaction_count": 31
        }
      ]
    }
  ]
}

一方、ユーザー別28日レポートは、ユーザーごとの期間レコードとして扱われます。集約28日レポートとユーザー別28日レポートを、同じJSON階層だと決めつけないことが重要です。

また、1日レポートと、その日を含む28日レポートを同じ集計へ加えると期間が重複します。日次推移には1日レポートまたはday_totalsのどちらか一方を使用してください。

APIから取得したレポートを安全に正規化するコード

Copilot usage metrics APIのエンドポイントは、メトリック本体を直接返すのではなく、レポートファイルのdownload_linksを返します。レポートは有効期限付きの署名URLからダウンロードするため、レスポンスを取得した後は全リンクを保存し、NDJSONを行単位で処理します。(GitHub Docs)

Organizationの1日集約レポートを要求する例は次のとおりです。APIバージョンは、利用時点でGitHub公式ドキュメントに掲載されているサポート対象バージョンを確認してください。公式例では2026-03-10が使用されています。(GitHub Docs)

curl -L \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer $GITHUB_TOKEN" \
  -H "X-GitHub-Api-Version: 2026-03-10" \
  "https://api.github.com/orgs/ORG/copilot/metrics/reports/organization-1-day?day=2026-08-07"

ダウンロードしたNDJSONの各オブジェクトは、次のJavaScriptでトップレベル指標とエージェント別指標に分けて正規化できます。

/**
 * 1件のCopilot usage metricsレコードを、
 * トップレベル指標とAgent App指標に分離する。
 */
function normalizeCopilotUsageRecord(record) {
  const metricRecords = Array.isArray(record.day_totals)
    ? record.day_totals
    : [record];

  const createContext = (metric) => ({
    day: metric.day ?? null,
    reportStartDay: record.report_start_day ?? metric.day ?? null,
    reportEndDay: record.report_end_day ?? metric.day ?? null,
    enterpriseId:
      metric.enterprise_id ?? record.enterprise_id ?? null,
    organizationId:
      metric.organization_id ?? record.organization_id ?? null,
    userId:
      metric.user_id ?? record.user_id ?? null
  });

  // トップレベルのプロンプト数を保存するテーブル用
  const usageRows = metricRecords.map((metric) => ({
    ...createContext(metric),
    copilotExplicitPromptCount:
      typeof metric.user_initiated_interaction_count === "number"
        ? metric.user_initiated_interaction_count
        : null
  }));

  // Agent App別のジョブ開始数を保存するテーブル用
  const agentRows = metricRecords.flatMap((metric) => {
    const agents = Array.isArray(metric.totals_by_3rd_party_agent)
      ? metric.totals_by_3rd_party_agent
      : [];

    return agents
      .filter(
        (agent) =>
          agent &&
          typeof agent === "object" &&
          typeof agent.agent_id === "string"
      )
      .map((agent) => ({
        ...createContext(metric),

        // 数字だけに見えても文字列のまま保持する
        agentId: agent.agent_id,

        // 結合キーではなく表示用のスナップショット
        agentNameSnapshot:
          typeof agent.agent_name === "string"
            ? agent.agent_name
            : null,

        // トップレベルとは別の指標
        agentJobStartCount:
          typeof agent.user_initiated_interaction_count === "number"
            ? agent.user_initiated_interaction_count
            : null,

        // ユーザー別レポートでは存在しないためNULLにする
        agentSessionCount:
          typeof agent.session_count === "number"
            ? agent.session_count
            : null
      }));
  });

  return {
    usageRows,
    agentRows
  };
}

このコードでは、トップレベルのプロンプト数をagentRowsへコピーしていません。そのため、Agent App配列を展開した後に、トップレベル値がエージェント数だけ重複する問題を防げます。

エージェント別ダッシュボードで使える集計指標

正規化したデータからは、次の指標を作成できます。

指標計算方法注意点
エージェント別ジョブ開始数agent_idごとにagent_job_start_countを合計agent_nameでは集計しない
Agent App利用者数ユーザー別レポートでuser_idを重複除外session_countは利用しない
1利用者あたりジョブ開始数ジョブ開始数÷Agent App利用者数同じ期間とスコープを使う
エージェント構成比各エージェントのジョブ開始数÷全エージェントのジョブ開始数「認識済みAgent App内の構成比」と表示する
エージェント別セッション数集約レポートのsession_countを使用ユーザー別セッション数には分解できない

例えば、エージェント構成比を表示する場合は、「Copilot全利用の30%」ではなく、「認識済みAgent Appのジョブ開始数のうち30%」と表記します。トップレベルのプロンプト数とは母集団が異なるためです。

チーム別の利用状況を作る場合は、ユーザー別の日次レポートとuser-teamsレポートを、user_iddayorganization_idまたはenterprise_idで結合します。チーム単位のメトリックはAPI側で事前集計されていません。(GitHub Docs)

よくある集計ミスと修正方法

症状原因修正方法
表示名変更後に別エージェントとして分かれるagent_nameでグループ化しているagent_idでグループ化する
新配列対応後に全体件数が急増したトップレベル値と配列内値を加算した別々のKPIとして保存する
Agent App数に比例してプロンプト数が増える配列展開時にトップレベル値を複製したトップレベルとAgent Appを別テーブルにする
ユーザー別セッション数がすべて0になる欠落したsession_countを0へ変換した未提供を示すNULLで保存する
利用がない日に処理が停止する配列が常に存在すると仮定した存在しない場合は空配列として処理する
組織全体の件数がほぼ2倍になる集約レポートとユーザー別レポートを合算したKPIごとに使用するレポートを一つに決める
1日分の数値が重複する1日レポートと28日レポートを合算した重複しない期間・粒度に統一する
API変更後にJSON検証で失敗する未知フィールドを禁止する厳格なスキーマクライアントモデルを更新し、オプション項目として追加する

GitHub側では後方互換の変更でも、受け側のJSON SchemaがadditionalProperties: falseになっている場合や、固定クラスへ厳格にデシリアライズしている場合は、追加フィールドによって処理が失敗することがあります。

導入前に確認するテストケース

本番の集計処理へ反映する前に、最低限次のパターンをテストします。

  • totals_by_3rd_party_agentが存在しないレコード
  • Agent Appが1件だけのレコード
  • Agent Appが複数あるレコード
  • 同じagent_idagent_nameだけが変わったレコード
  • session_countを含む集約レポート
  • session_countを含まないユーザー別レポート
  • day_totalsを持つ28日集約レポート
  • 同じ期間の集約レポートとユーザー別レポート
  • 複数のdownload_linksに分割されたレポート

APIを利用するには、Copilot usage metricsポリシーを有効にし、EnterpriseまたはOrganizationのCopilot Metricsを閲覧できる権限が必要です。対象期間に認識済みAgent Appの活動がなければ、新しい配列はレポートから省略されます。(The GitHub Blog)

まとめ:安全な集計のために最初に直すべき点

Copilot Metrics APIのエージェント別スキーマへ対応する際は、単にtotals_by_3rd_party_agentを展開するだけでは不十分です。

まず、agent_idを文字列の結合キーとして保存し、agent_nameは表示用データとして扱います。次に、配列内のuser_initiated_interaction_countagent_job_start_countなどへ改名し、トップレベルのプロンプト数とは別テーブル・別KPIに分離します。

さらに、session_countがないユーザー別レポートではNULLを保持し、オプション配列が省略されても停止しない処理にします。

この設計にしておけば、表示名の変更、新しいAgent Appの追加、28日レポートへの対応があっても、過去データとの継続性を保ちながら安全に利用状況を比較できます。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次