このページでは

For AI agents: a documentation index is available at /docs/llms.txt. Append .md to any page URL for markdown, or send Accept: text/markdown.

エージェント アナリティクス SDK

このページは、Amplitude AI SDKの開発者向けリファレンスです。 プロダクトレベルの設定の概要については、「エージェント アナリティクスの設定」を参照してください。プロダクトの概念とAmplitudeがデータをどのように使用するかについては、「エージェント アナリティクスの概要」と「エージェント結果の分析」を参照してください。

以下のタイムラインは、インストルメンテーションによって何が生成されるかを示しています。任意のイベントをクリックして、その形状と、そのイベントを発信する呼び出しを確認します。

SDKからセッション中のリアルタイムAmplitudeより事後エンリッチメントロードターン1ターン2終了ViewedPage(ブラウザSDK)User MessageTool CallAI ResponseUser MessageTool CallAI Response···Session End1ターン内でも発生しますSpanSession RecordEvaluator Result × N
イベントタイプ
[Agent] AI ResponseSDK から
発生時刻
22:33:48
アイデンティティ
[Agent] Session ID4ddcc6b2-1041-432a-aa8c-ebe3eccac40b
[Agent] Agent IDsupport-chatbot
[Agent] Trace IDb4f63d43-d752-4b1f-8489-d234ddf586b2
イベント固有の
$llm_message.textI can help. Your subscription renews on Aug 15…
[Agent] Model Namegpt-4o-mini
[Agent] Provideropenai
[Agent] Input Tokens1245
[Agent] Output Tokens87
[Agent] Latency Ms3420
[Agent] Cost USD0.0012
ターンを閉じます。 セットアップ時にSDKドクターが確認する8つのフィールド(Session ID、Agent ID、Model、Provider、Latency ms、Input/Output Tokens、Cost USD)を保持します。s.trackAiMessage(...)またはプロバイダーラッパーによって発行されます。

前提条件

  • Agentアナリティクスが有効になっているAmplitudeプロジェクト。
  • エージェントアナリティクスオブジェクトの表示権限。管理者は、ロールベースのアクセス制御(RBAC)を通じてアクセスを許可します。
  • Node.jsまたはPythonで計測するためのエージェントのコードベース(または、Amplitude HTTP APIを呼び出すことができるランタイム)。
  • 適切なデータセンター用のプロジェクトの API キー。 Agent アナリティクスは米国とEUで動作します。

SDKをインストールする

plaintext
Instrument this app with @amplitude/ai. Follow node_modules/@amplitude/ai/amplitude-ai.md

このプロンプトを AI コーディング エージェント (Cursor、Claude Code、Windsurf、GitHub Copilot、または Codex) に貼り付けます。 エージェントはリンクされた命令ファイルを読み取り、コードベースをスキャンし、すべての LLM コールサイトとセッション ライフサイクルを検出し、それらを計測します。

SDKの初期化

アプリケーションのエントリポイントで一度初期化し、インスタンスを再利用してください。 推奨されるパターンは、aiおよびラップされたプロバイダークライアントをエクスポートするブートストラップモジュールです。

typescript
// src/lib/amplitude.ts
import { AmplitudeAI, AIConfig, OpenAI } from "@amplitude/ai";
export const ai = new AmplitudeAI({
  apiKey: process.env.AMPLITUDE_AI_API_KEY!,
  config: new AIConfig({ contentMode: "full", redactPii: true }),
});
export const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY!,
  amplitude: ai,
});

'openai'から直接インポートするのではなく、このモジュールからopenaiをインポートしてください。必要に応じて、ラップされたプロバイダーをさらに追加してください。

イベントを送信せずに開発中に検証するには、AIConfig で dryRun: true (Node) または dry_run=True (Python) を設定します。

SDK を設定する

AIConfigをAmplitudeAIコンストラクターに渡します。すべてのオプションは任意です。ほとんどのアプリでデフォルト設定が機能します。

編集レシピ(名前付き置換、カスタムスクラバー、国際ロケール)については、「プライバシーモードの選択」を参照してください。

エージェントセッションの計測

各エージェント呼び出しをセッションにラップします。 セッションはすべてのイベント(ユーザーメッセージ、モデルレスポンス、ツール呼び出し、スパン)を1つのレコードに関連付けます。

エージェントセッションとは、ユーザーが最初から最後までエージェントに手渡す 1 つのジョブです。つまり、実際の結果をもたらす作業単位です。 新しい ID を作成するのではなく、すでに追跡している ID から sessionId を設定してください。

  • チャットボットまたはコパイロット: 会話スレッドID。
  • コーディング エージェント: タスクまたはワークセッション ID。
  • サポートエージェント:チケットID。
  • 音声エージェント:コール ID。
  • バックグラウンドまたは自律エージェント:実行またはジョブ ID。

エージェントセッションと標準分析セッション

エージェントセッションはAmplitudeの標準分析セッションではありません。エージェントセッションは、[Agent] Session ID、ユーザーがエージェントに渡すジョブの1つです。Amplitudeの標準的な分析セッション$session_idは、セッションリプレイとプロダクトレポートの基盤となる、ユーザーのアプリまたはウェブへの訪問です。エージェント セッションを自分の ID から設定し、この 2 つをリンクさせる場合は、標準アナリティクス セッション ID をネットワーク境界を越えて転送します。

typescript
import { ai, openai } from "@/lib/amplitude";
const agent = ai.agent("chat-handler", {
  description: "Customer support chatbot",
});
export async function POST(req: Request) {
  const { messages, userId } = await req.json();
  return agent.session({ userId }).run(async (s) => {
    s.trackUserMessage(messages[messages.length - 1].content);
    const response = await openai.chat.completions.create({
      model: "gpt-4o-mini",
      messages,
    });
    return Response.json(response);
  });
}

Python SDKはai.agent(...).session(...)と同じパターンに従います。run()で開かれたセッションは、コールバックが返ったときに終了します。複数の要求にまたがるセッションの場合は、次のいずれかの方法でセッションを終了します。

  • **明示的に閉じる(推奨):**チケットのクローズや実行の完了など、ジョブが終了したときに trackSessionEnd() (Node) または track_session_end() (Python) を呼び出します。サーバ側の評価はクローズ時に実行されます。クローズは完了マーカーであり、厳密なロックではありません。クローズ後に到着したイベントは引き続き受け入れられて保存されますが、エンリッチメントはセッションごとに1回だけ実行されるため、遅れて到着したターンがセッションのシグナル、ロールアップ、またはセッションレコードに反映されることはありません。
  • アイドル タイムアウトで終了させる: タイムアウトのデフォルト値は、セッションに対して最後に受信されたエージェント イベントから測定された 30 分間の非アクティブ状態です。 これは、idleTimeoutMinutes(Node) または idle_timeout_minutes(Python) を使用してセッションごとに設定できます。数時間にわたって対応されるサポートチケットの例(240)のように、自然な空白期間が長いジョブの場合は、その値を上げてください。これを-1に設定すると、アイドルウィンドウが最大90日まで延長され、デフォルトの24時間の期間上限がなくなります。そのため、実際には明示的な「セッション終了」のみがクローズの条件となります。

アイドル状態の上書きを設定する場所。 セッションパラメータを設定し、エージェントのcontextにidle_timeout_minutesキーも含めます。現在、コンテキスト ルートは、明示的に閉じられることがないセッションについて、確実にサーバに到達するためのルートです。両方を設定することで、オーバーライドが有効になり、サーバ側の修正プログラムが出荷された後も機能し続けることが保証されます。

typescript
const agent = ai.agent("support-bot", {
  context: { idle_timeout_minutes: 240 },   // reliable today
});
const session = agent.session({
  userId,
  sessionId: ticketId,
  idleTimeoutMinutes: 240,                  // the intended parameter
});

同じユーザーが新しい目的で戻ってきた場合は、古いセッションを継続するのではなく、新しいsessionIdを使用して新しいセッションを開始してください。

最小限の実行可能なインストルメンテーション

Agentアナリティクスでは、イベントを関連付けるためにAPIキー、ユーザー識別子(userIdまたはdeviceId)、agentId、sessionIdの4つのフィールドが必要です。推奨パターンは自動的にプロバイダーラッパーを追加するため、SDK はモデル、トークン、コスト、レイテンシのデータをキャプチャします。

2 つの ID ルールにより、1 人のユーザーが次の 2 つに分割されるのを防ぎます。

  • "anonymous"や""などのプレースホルダuserId、または一時的なIDを渡さないでください。代わりに userId を省略してください。Amplitudeは設定後にuserIdを変更できないため、プレースホルダは後でマージされない別のユーザーを作成します。
  • プレアカウントセッション全体で、同じ deviceId を再利用してください。バックエンドがリクエストごとに新しい deviceId を生成すると、マージが機能しなくなります。ブラウザSDKからdeviceIdを読み取り、それを転送します。

プロバイダーの呼び出しを自動計測する

SDK は、プロバイダーのアクティビティをキャプチャするための 2 つのゼロコードパスを提供します。

プロバイダーラッパー

構築時にプロバイダークライアントをラップします。ラッパーは、コールを基盤となるクライアントに転送し、要求、応答、トークン、遅延、およびコストを記録します。

typescript
import OpenAI from "openai";
const openaiWrapped = new OpenAI({ amplitude: ai });

コンストラクションサイト(生成箇所)を変更できない場合は、既存のクライアントの生成コードを修正することなく、wrap(existingClient, ai)を使用してインスツルメントします。

サービス範囲はプロバイダによって異なります。 すべてのラッパーはストリーミング、システムプロンプト、コストをキャプチャします。残りはプロバイダーの API が公開している内容に依存します:

us.anthropic.claude-3-5-sonnetなどの基盤モデル ID は、価格検索用に自動的に正規化されます。

patch()

ゼロコード計装のために、スタートアップ時に patch({ amplitudeAI: ai }) を一度呼び出してください。SDK はサポート対象のクライアントにモンキーパッチを適用し、OpenAI Chat Completions、OpenAI Responses、および Anthropic Messages のメッセージ配列から[Agent] Tool Callイベントを自動的に抽出します。メッセージ検査からは実行タイミングが分からないため、抽出されたツールコールは latencyMs: 0 となります。実際のツールレイテンシが必要な場合は、tool()またはtrackToolCall()を使用してください。

**patch() にはアクティブなセッションコンテキストが必要です。**セッション コンテキスト外で発火されたパッチ適用済みコールは、サイレントに廃棄されます。イベントは発生せず、エラーも発生しません。また、プロバイダー コール自体も影響を受けません。 これにはamplitude-ai-instrumentを使用するAMPLITUDE_AI_AUTO_PATCH=trueも含まれており、プロセス開始時にパッチを適用しますが、アンビエントセッションは確立しません。これは、「計測を行ったがイベントが表示されない」という最も一般的な原因です。 パッチ適用済みの設定を確認するには、セッションで 1 つのコールをラップします。

const ai = new AmplitudeAI({ apiKey: process.env.AMPLITUDE_AI_API_KEY! });
patch({ amplitudeAI: ai });
const agent = ai.agent("verify", { userId: "verify-user-1" });
await agent.session().run(async () => {
  await openai.chat.completions.create({
    model: "gpt-4o",
    messages: [{ role: "user", content: "Hello" }],
  });
}); // one [Agent] AI Response

リクエスト・スコープの作業の場合、Express ミドルウェアはセッション・コンテキストを自動的に確立します (フレームワークのノートを参照してください)。

ツールの追跡

tool()高階関数はツール関数をラップするため、SDK は各呼び出しを記録します。

import { tool } from "@amplitude/ai";
const searchProducts = tool(searchDB, { name: "search_products" });
// Inside session.run, call as usual:
const result = await searchProducts(query);
// [Agent] Tool Call event emitted with duration, success, input/output

インラインツールコールまたはサポートされていないフローの場合は、s.trackToolCall(name, latencyMs, success, { input, output })を直接使用してください。

スパンの追跡

スパンは、ベクトル検索、再ランク、ガードレール、またはターン内に存在する時間指定された作業などの内部サブオペレーションをラップします。 これらは[Agent] Spanイベントを発行し、トレースのアイデンティティを共有します。

OTEL 有効時の動作

OTEL が enable_otel() / enableOtel() を通じて有効になっている場合、observe() / @observe はイベントを直接送信する代わりに実際の OTEL スパンを作成します。SpanEventMapperはこれらのスパンを適切な[Agent]イベント タイプに変換します。type パラメータを使用してルーティングを制御します。@observe(type="tool") は、スパンを [Agent] Span ではなく [Agent] Tool Call としてルーティングします。

import { observe } from "@amplitude/ai";
// As a higher-order function:
const runSubAgent = observe(
  async (prompt: string) => {
    return await subAgent.execute(prompt);
  },
  { name: "sub-agent-execution" },
);
// Or explicitly when you need error capture:
const start = Date.now();
try {
  const result = await subAgent.execute(prompt);
  s.trackSpan({
    name: "sub-agent-execution",
    latencyMs: Date.now() - start,
    inputState: { prompt: prompt.slice(0, 1000) },
    outputState: { response: result.slice(0, 1000) },
  });
} catch (e) {
  s.trackSpan({
    name: "sub-agent-execution",
    latencyMs: Date.now() - start,
    isError: true,
    errorType: (e as Error).name,
    errorMessage: (e as Error).message,
  });
  throw e;
}

スパンはターンレベルのイベントを置き換えるものではありません。

エージェントアナリティクスのターン数とインタラクションビューは、スパンではなく、[Agent] User Message および [Agent] AI Responseによって決定されます。内部ステップ周辺のスパンのみを送信する場合、ダッシュボードにはターンレベルのアナリティクスが行われないトレースが表示されます。ユーザーに表示される各サイクルに対して常にユーザーメッセージとAIレスポンスのペアを出力し、その最上位でスパンを使用してください。

手動計装

カスタムフローまたはサポートされていないプロバイダーの場合は、セッションオブジェクトに対して直接手動メソッドを使用してください。それぞれが 1 つの[Agent]イベント タイプにマップされます。

ラッパー(プロキシ、カスタムゲートウェイ)を経由しない AI レスポンスの場合、完了レスポンスから使用状況を渡します。

s.trackAiMessage(completedMessage.content, "gpt-4o", "openai", latencyMs, {
  inputTokens: usage.prompt_tokens,
  outputTokens: usage.completion_tokens,
  totalTokens: usage.total_tokens,
});

コストが正しく自動計算されるように、内部ゲートウェイラベルではなく、正規プロバイダーモデルID(gpt-4o-mini、claude-sonnet-4-20250514)を渡してください。

コンテキストによるセグメンテーションを追加

contextディクショナリをai.agent(...)に渡して、すべてのイベントに任意のセグメンテーションディメンションを付加します。SDKはそれを[Agent] Contextにシリアル化するため、新しいグローバルプロパティを登録することなく、AIセッションをセグメント化できます。

const agent = ai.agent("support-bot", {
  context: {
    agent_type: "executor",
    experiment_variant: "reasoning-enabled",
    surface: "chat",
  },
});

これらのキーは、最も一般的なセグメンテーションのニーズに対応しています。

子エージェント間でコンテキストをマージする

子エージェントは親のコンテキストを継承します。 子のキーは一致する親キーよりも優先されます。子が設定していない親キーは保持されます。

const parent = ai.agent("orchestrator", {
  context: { experiment_variant: "treatment", surface: "chat" },
});
const child = parent.child("researcher", {
  context: { agent_type: "retriever" },
});
// child context = { experiment_variant: "treatment", surface: "chat", agent_type: "retriever" }

Amplitudeでのクエリコンテキスト

[Agent] Context はJSON文字列です。 個々のキーをクエリするには:

  • 派生プロパティ:頻繁に使用されるキーの場合、値を永続的に抽出する派生イベントプロパティ([データ] > [プロパティ] > [派生] > [新規])を作成します。
  • フィルター: チャートフィルターでの文字列照合には [Agent] Context contains "key":"value" を使用します。

複数のテナントを使用する

マルチテナントプラットフォームでは、ai.tenant(orgId, opts?)(Node)またはai.tenant(org_id, ...)(Python)を使用してテナントスコープのハンドルを作成します。ハンドルから作成されたすべてのエージェントは customerOrgId を事前にバインドし、各イベントで [Agent] Customer Org ID として反映されるため、すべてのコールで org ID を引き回すことなく、エンドカスタマーごとに使用状況をセグメント化できます。

const tenant = ai.tenant("org-456", { env: "production" });
const agent = tenant.agent("support-bot", { userId: "user-123" });
// agent.track* calls carry [Agent] Customer Org ID = "org-456"

モデル階層の分類

SDK はモデル名からモデル階層を推測し、すべての [Agent] AI Response に [Agent] Model Tier としてそれを関連付けます。階層を使用すると、すべてのモデルをリストすることなく、モデルクラス間でコストとパフォーマンスを比較できます。

階層を直接解決するには inferModelTier() / infer_model_tier() を呼び出します:

import { inferModelTier } from "@amplitude/ai";
inferModelTier("gpt-4o-mini"); // 'fast'
inferModelTier("claude-3.5-sonnet"); // 'standard'
inferModelTier("o1-preview"); // 'reasoning'

名前から判別できないカスタムモデルや微調整済みモデルの場合は、AIメッセージ呼び出しで modelTier / model_tier を渡して、推論された値を上書きします。

s.trackAiMessage(response.content, "ft:gpt-4o:my-org:custom", "openai", latencyMs, {
  modelTier: "standard",
});

添付ファイルを追跡する

ユーザーメッセージ呼び出しにattachments1 />配列を渡して、メッセージとともに送信されたファイル(画像、PDF、URL)を記録します。各エントリには、type、name、およびsize_bytesが含まれています。

s.trackUserMessage("Analyze this document", {
  attachments: [
    { type: "image", name: "chart.png", size_bytes: 102400 },
    { type: "pdf", name: "report.pdf", size_bytes: 2048576 },
  ],
});

SDKはこれらのプロパティを配列から取得し、添付ファイルのメタデータのみを記録し、ファイルのコンテンツは記録しません:[Agent] Has Attachments、[Agent] Attachment Types、[Agent] Attachment Count、[Agent] Total Attachment Size Bytes、および[Agent] Attachments。添付ファイルは、モデル生成画像などのAI応答にも適用されます。同じattachmentsオプションをAIメッセージコールに渡します。

暗黙的なフィードバックの取得

行動信号は、レスポンスがユーザーのニーズを満たしているかどうかを示すものであり、明示的な評価は必要ありません。 これらのオプションは関連するトラックコールに設定してください。SDKはそれらをクエリ可能な品質プロパティにマップします。

// AI response the user copied (positive)
s.trackAiMessage("To create a funnel, go to...", "gpt-4o", "openai", latencyMs, { wasCopied: true });
// User regenerates (negative — first response fell short)
s.trackUserMessage("How do I create a funnel?", { isRegeneration: true });
// User edits and resubmits their prompt
s.trackUserMessage("How do I create a conversion funnel for signups?", {
  isEdit: true,
  editedMessageId: originalMsgId,
});
// User left after the first AI response
agent.trackSessionEnd({ sessionId: "sess-1", abandonmentTurn: 1 });

既存の会話をインポート

1 回の呼び出しで完全なメッセージ履歴をバックフィルするには、trackConversation()(Node) または track_conversation()(Python) を使用します。 { role, content }メッセージの配列を渡します。各メッセージは [Agent] User Messageまたは [Agent] AI Responseとなり、ターン ID は順番に自動的に増分されます。systemメッセージはスキップされます。

import { trackConversation } from "@amplitude/ai";
import * as amplitude from "@amplitude/analytics-node";
trackConversation({
  amplitude,
  userId: "user-123",
  sessionId: "sess-abc",
  agentId: "support-bot",
  messages: [
    { role: "user", content: "How do I reset my password?" },
    {
      role: "assistant",
      content: "Go to Settings > Security > Reset Password.",
      model: "gpt-4o",
      provider: "openai",
      latency_ms: 1200,
      input_tokens: 15,
      output_tokens: 42,
    },
    { role: "user", content: "Thanks, that worked!" },
  ],
});

これを使用して、会話履歴をインポートしたり、外部システムからデータを移行したりできます。 この関数は、個々のトラッキングメソッドと同じコンテキストフィールドを受け入れます。

ユーザーフィードバック(スコア)の送信

応答に対する親指のアップまたはダウンやオプションの評価など、ユーザーの明示的なフィードバックを[Agent] Scoreイベントとしてキャプチャします。 スコアはアプリケーションからのみ得られます。Amplitudeのエンリッチメント・パイプラインはスコアを生成しません。

// Thumbs up/down on a specific AI response
ai.score({
  userId: "user-123",
  name: "user-feedback",
  value: 1.0,
  targetId: aiMessageId,
  targetType: "message",
  source: "user",
});

バイナリ評価コントロールにuser-feedbackという名前を使用してください。この名前には特別な意味があり、セッションで検出された否定的なフィードバック信号を上書きします。完全なパターン(セッション中およびリクエスト後のパス、CSAT のスケール)については、セットアップ ページの「サムズアップ/サムズダウン」セクションを参照してください。

SDKを使用せずにイベントを直接取り込む場合は、[Agent] Score Nameをスコア名(例:user-feedback)に設定した[Agent] Scoreイベントを送信してください。

マルチエージェントアーキテクチャ

親エージェントは子エージェントに委任できます。 子エージェントは親のセッションを継承するため、すべてのイベントは1つのセッションIDの下で相互に関連付けられたままになります。

const orchestrator = ai.agent('shopping-agent', { description: 'Orchestrates shopping requests' });
const recipeAgent = orchestrator.child('recipe-agent', { description: 'Finds recipes' });
await orchestrator.session({ userId }).run(async (s) => {
  s.trackUserMessage(userInput);
  const result = await s.runAs(recipeAgent, async (cs) => {
    cs.trackUserMessage(delegatedQuery);
    return openai.chat.completions.create({ model: 'gpt-4o', messages: [...] });
  });
});

子のLLMコールだけでなく、ディスパッチ自体についてレイテンシーとエラーのメトリックが必要な場合は、委任コールをラップobserve()してください。trackSpan

連携パターン

シングルリクエストAPIエンドポイント

サーバーレス関数やワンショットエンドポイントの場合、ハンドラー内にセッションを作成し、戻る前にフラッシュしてください。そうすることで、イベントが送信される前にランタイムがフリーズしないようになります。

app.post("/chat", async (req, res) => {
  const agent = ai.agent("api-handler", { userId: req.userId });
  const result = await agent.session({ sessionId: req.sessionId }).run(async (s) => {
    s.trackUserMessage(req.body.message);
    const start = performance.now();
    const response = await openai.chat.completions.create({
      model: "gpt-4o",
      messages: req.body.messages,
    });
    s.trackAiMessage(
      response.choices[0].message.content ?? "",
      "gpt-4o",
      "openai",
      performance.now() - start,
      {
        inputTokens: response.usage?.prompt_tokens,
        outputTokens: response.usage?.completion_tokens,
      },
    );
    return response.choices[0].message.content;
  });
  await ai.flush();
  res.json({ response: result });
});

長時間持続するセッション(チャットボット)

複数ターンでの会話の場合、セッションを一度作成し、複数のターンにわたって再利用してください。ユーザーとAIのペアをターンごとに追跡します。セッションはrun()が値を返すか、アイドルタイムアウトが発生すると終了します。

const agent = ai.agent("chatbot", { userId: "user-123", env: "production" });
await agent.session({ sessionId: conversationId }).run(async (s) => {
  s.trackUserMessage("What is Amplitude?");
  const r1 = await llm.chat("What is Amplitude?");
  s.trackAiMessage(r1.content, "gpt-4o", "openai", r1.latencyMs, {
    inputTokens: r1.usage.input,
    outputTokens: r1.usage.output,
  });
  s.trackUserMessage("How does it track events?");
  const r2 = await llm.chat("How does it track events?");
  s.trackAiMessage(r2.content, "gpt-4o", "openai", r2.latencyMs, {
    inputTokens: r2.usage.input,
    outputTokens: r2.usage.output,
  });
});

マルチエージェントのオーケストレーション

親エージェントが専門の子に委任する場合、各委任を runAs() / arun_as() でラップします。手動による追跡コールとコールバック内のプロバイダーラッパーの両方が、チャイルドのアイデンティティを自動的に取得します。基本的な委任形態については、「マルチエージェントアーキテクチャ」を参照してください。

runAs/ arun_as の仕組み:

  • 親セッションのsessionId、traceId、およびターンカウンターを共有します。
  • コールバックの期間中、子にagentIdを、親にparentAgentIdを設定します。
  • 自動ユーザーメッセージ追跡を抑制し、委任コール内の内部role: "user"プロンプトが意図しないユーザーターンを作成しないようにします。
  • [Agent] Session Endは発行されません。子は親セッション内で実行され、その親セッションが 1 つのセッション終了を発行します。
  • エラーが発生した場合でも、コールバックが完了したときに親コンテキストを復元します。
  • 入れ子構造(ネスティング)をサポートしています。子は孫をrunAsことができます。

ファンアウト(並列の子コール、シングル ユーザー ターン)

1回のユーザーターンで複数の並列LLM呼び出しがトリガーされると、newTrace()/new_trace()を使用して新しいトレースを開き、Promise.all(Node)またはasyncio.gather(Python)を使用して子をディスパッチし、参加後に単一のAI応答を送信します。これにより、内部コールの実行数に関係なく、1つのトレース、1つのユーザーターン、1つのAIレスポンスが維持されます。

await orchestrator.session({ sessionId }).run(async (s) => {
  s.newTrace();
  s.trackUserMessage("Generate plan from quiz results", { context: structuredState });
  const [a, b] = await Promise.all([
    s.runAs(scorer, () =>
      openai.chat.completions.create({ model: "gpt-4o", messages: scorerMessages }),
    ),
    s.runAs(matcher, () =>
      openai.chat.completions.create({ model: "gpt-4o", messages: matcherMessages }),
    ),
  ]);
  s.trackAiMessage(assemble(a, b), "gpt-4o", "openai", totalLatencyMs);
});

ストリームレスポンス

ストリーミングセッションは、ストリームが完全に消費されるまで開いたままにしておく必要があります。ストリームが終了する前にセッションを閉じると、AI レスポンスイベントがドロップされます。

typescript
// WRONG: session ends before stream is consumed
return agent.session({ userId }).run(async (s) => {
  const stream = await openai.chat.completions.create({
    model: "gpt-4o",
    messages,
    stream: true,
  });
  return new Response(stream.toReadableStream());
});
// CORRECT: session stays open until stream completes
return agent.session({ userId }).run(async (s) => {
  const stream = await openai.chat.completions.create({
    model: "gpt-4o",
    messages,
    stream: true,
  });
  const readable = stream.toReadableStream();
  const [passthrough, forClient] = readable.tee();
  const reader = passthrough.getReader();
  (async () => {
    while (!(await reader.read()).done) {}
  })();
  return new Response(forClient);
});

Vercel AI SDKを使用して、onFinishコールバック内でフラッシュします:

typescript
const result = await streamText({
  model: openai("gpt-4o"),
  messages,
  onFinish: async () => {
    await ai.flush();
  },
});

標準分析セッションへのリンク

セッションがネットワーク境界を越えると、リクエストヘッダーにAmplitude IDを渡すことで、サーバー側のイベントがユーザーの標準分析セッションに参加するようになります($session_id)。 値をセッションのbrowserSessionIdフィールドとして渡します:

const browserSessionId = req.headers.get("x-amplitude-session-id");
const deviceId = req.headers.get("x-amplitude-device-id");
const session = agent.session({ userId, browserSessionId, deviceId });

バックエンドサービス間のクロスサービス伝搬には、送信側でinjectContext()を、受信側でextractContext(headers)を使用します。

サービス間でコンテキストを伝播

あるバックエンドサービスが別のバックエンドサービスを呼び出す場合、ダウンストリームイベントが新しいトレースを開始するのではなく、同じトレースに参加するように、アクティブなIDとセッションを伝播します。アウトバウンド側では、injectContext()はアクティブなコンテキスト(セッションID、トレースID、ユーザーID)を要求ヘッダーにシリアル化します。受信側では、extractContext(headers) がこれらを読み返します。

// --- Service A (outbound) ---
import { injectContext } from "@amplitude/ai";
await agent.session({ userId, sessionId }).run(async (s) => {
  s.trackUserMessage(message);
  const headers = injectContext({ "content-type": "application/json" });
  await fetch("https://service-b/internal/enrich", {
    method: "POST",
    headers,
    body: JSON.stringify({ message }),
  });
});
// --- Service B (inbound) ---
import { randomUUID } from "node:crypto";
import { extractContext, runWithContextAsync, SessionContext } from "@amplitude/ai";
export async function POST(req: Request) {
  const extracted = extractContext(Object.fromEntries(req.headers));
  const ctx = new SessionContext({
    sessionId: extracted.sessionId ?? randomUUID(),
    traceId: extracted.traceId ?? null,
    userId: extracted.userId ?? null,
  });
  return runWithContextAsync(ctx, async () => {
    await handleEnrichment(req);
  });
}

injectContext()は新しいheadersオブジェクトを返し、オリジナルを決して変更しません。アクティブなセッションがない場合、ヘッダーを変更せずに返すため、無条件に呼び出すことは安全です。

サポートされているプロバイダーとフレームワーク

ネイティブラッパーを提供するプロバイダー: OpenAI(Chat Completions + Responses)、Anthropic、Azure OpenAI、Gemini(@google/generative-ai)、Google Gen AI(@google/genai)、Mistral、Bedrock(Converse APIs)。

ファーストパーティとの統合を備えたエージェントフレームワーク: LangChain、LlamaIndex、OpenAI Agents SDK、Anthropic Tool Use、Claude Agent SDK(ClaudeAgentSDKTracker)、Anthropic Managed Agents、CrewAI(Pythonのみ)。

フレームワークとの統合

以下の統合は、エージェントフレームワーク独自のコールバックまたはトレースシステムをAgent アナリティクスに橋渡しします。それぞれはaiインスタンスとアイデンティティフィールドを受け取り、フレームワークにフックします。 CrewAIはPythonのみに対応しています。Nodeでは、設計上AmplitudeCrewAIHooksがエラーをスローします。代わりに、LangChain または OpenTelemetry パスを使用してください。

LangChain

AmplitudeCallbackHandlerをLangChainのコールバックに渡します。

import { AmplitudeCallbackHandler } from "@amplitude/ai";
const handler = new AmplitudeCallbackHandler({ amplitudeAI: ai, userId: "user-123", sessionId: "sess-1" });
// Pass handler to any LangChain runnable via { callbacks: [handler] }

LlamaIndex

import { AmplitudeLlamaIndexHandler } from "@amplitude/ai";
const handler = new AmplitudeLlamaIndexHandler({ amplitudeAI: ai, userId: "user-123", sessionId: "sess-1" });

OpenAIエージェントSDK

AmplitudeTracingProcessorをトレース プロセッサとして登録します。

import { AmplitudeTracingProcessor } from "@amplitude/ai";
const processor = new AmplitudeTracingProcessor({ amplitudeAI: ai, userId: "user-123", sessionId: "sess-1" });
// Register with the OpenAI Agents SDK trace provider.

Anthropicツールの使用

AmplitudeToolLoopAnthropicのマルチターンtool_useループを実行し、各AIの応答とツール呼び出しを追跡します。

import { AmplitudeToolLoop } from "@amplitude/ai";
const loop = new AmplitudeToolLoop({ amplitudeAI: ai, userId: "user-123", sessionId: "sess-1" });
await loop.run({ client, model: "claude-sonnet-4-20250514", messages, tools, toolExecutor });

OpenTelemetry属性マッピング

フレームワークがすでにOpenTelemetry GenAIスパンを発行している場合、SDKはそれらを[Agent]プロパティにマップします。この機能を有効にする方法(スパンファーストenableOtel()/enable_otel()パスやマニュアルAmplitudeGenAIExporter/AmplitudeAgentExporterエクスポータなど)については、「OpenTelemetryスパンの取り込み」を参照してください。エクスポータのマッピングは次のように適用されます。

一部の信号にはOTELと同等のものがなく、ネイティブプロバイダーのラッパーが必要です。これには、推論コンテンツとトークン、TTFB、ストリーミング検出、暗黙的なフィードバック、添付ファイル、および[Agent] Parent Message IDを通じたイベントグラフのリンクが含まれます。同じコールに対してOTELとネイティブラッパーを一緒に実行できます。SDK は重複除外を実行するため、二重イベントは発生しません。

プロバイダー固有の注意事項

Vercel AI SDK

プロバイダーラッパーは、Vercelの抽象化ではなく、基盤となるSDK (openai) をインスツルメントします。@ai-sdk/openaiのみが存在する場合は、openaiを直接の依存関係として追加するか、patch()にフォールバックしてください。ストリーミング応答の場合は、onFinish を使用して await ai.flush() を呼び出します(ストリーム応答を参照してください)。

Claude Agent SDK

@amplitude/ai/integrations/claude-agent-sdkからClaudeAgentSDKTrackerを使用します。イベントを有効にするには、2つのフィールドが必要です。agentId43 />ai.agent()(44 />上:LLM使用アプリケーションレジストリ内のAI機能を識別します)と、userId45 />sessionId + 46 />agent.session()(47 />上:イベントを1つのインタラクションに結びつけます)です。

typescript
import { AmplitudeAI } from "@amplitude/ai";
import { ClaudeAgentSDKTracker } from "@amplitude/ai/integrations/claude-agent-sdk";
import { query } from "@anthropic-ai/claude-agent-sdk";
const ai = new AmplitudeAI({ apiKey: process.env.AMPLITUDE_AI_API_KEY! });
const agent = ai.agent({ agentId: "code-reviewer" });
const tracker = new ClaudeAgentSDKTracker();
await agent.session({ userId: "u1", sessionId: "sess-abc" }).run(async (s) => {
  for await (const message of query({
    prompt: "Analyze this codebase",
    options: { hooks: tracker.hooks(s) },
  })) {
    tracker.process(s, message);
  }
});

tracker.hooks(session) 正確なツールレイテンシを持つ PreToolUse / PostToolUse フックを返します。tracker.process(session, message) は、AI 応答とユーザーメッセージのメッセージストリームを処理します。

Anthropic管理対象エージェント

LLM呼び出しはユーザーのコードではなくAnthropicのクラウド上で行われるため、プロバイダーのラッパーは機能しません。手動による追跡とポーリングclient.beta.sessions.events.list()を使用してください。イベントタイプをSDKメソッドにマップするには:

events.list() は以前に取得されたイベントを返すため、ポーリング間でイベントの重複を排除します。

const seenIds = new Set<string>(savedState.seenIds);
for (const event of response.data) {
  if (seenIds.has(event.id)) continue;
  seenIds.add(event.id);
  // track event
}

レイテンシーは、session.status_runningポーリングの往復時間ではなく、processed_atイベントとイベントの間の壁時計時間として測定してください。events.list()Anthropic Admin API には使用状況やトークン数が含まれないため、コスト追跡には Anthropic Admin API が必要です。

OpenAI Assistants API

プロバイダーのラッパーは、Assistants API を自動的に計測しません(非同期/ポーリングベース)。 手動追跡を使用してください:メッセージの作成時にtrackUserMessage()、完了イベントのポーリング時にtrackAiMessage()を使用します。

MCPサーバ

MCPプロトコルは、送信元のユーザープロンプトをツールに渡さないため、MCPサーバはそれをキャプチャできません。LLM がその意図を自己説明できるようにするため、各ツールにオプションのrationaleパラメータを追加し、使用可能なセッションコンテンツを維持できます。

フレームワークに関するメモ

Next.js (アプリルーター)

SDK はサーバー側モジュールで初期化してください。クライアントコンポーネントでは初期化しません。 next.config.ts内のserverExternalPackagesに@amplitude/aiを追加します。セッション作成は各ルートハンドラ内でラップしてください。サーバーレス環境では、イベントが送信される前にランタイムがフリーズしないよう、ハンドラが戻る前に await ai.flush() を呼び出してください。

Express / Fastify / Hono

バンドルされているミドルウェアを使用して、すべてのリクエストにaiをアタッチします。

typescript
import { createAmplitudeAIMiddleware } from "@amplitude/ai";
app.use(
  createAmplitudeAIMiddleware({
    amplitudeAI: ai,
    userIdResolver: (req) => req.headers["x-user-id"] ?? null,
  }),
);

エッジランタイムとCloudflare Workers

@amplitude/aiはCloudflare Workersではバンドルできません。 SDKはnode:async_hooks、node:module、および node:cryptoに依存しています。 Workers Buildsはnodejs_compat_v2が有効な場合でもアップロードを拒否します。@amplitude/analytics-nodeも互換性がありません(Nodeのhttpに依存します)。

唯一安全なインポートは import type { ... } from '@amplitude/ai/types' です。これはコンパイル時に消去されます。 ランタイム追跡のために、[Agent]イベントを直接構築するフェッチベースのトランスポートを使用してください:

typescript
import type { AmplitudeClientLike, AmplitudeEvent } from "@amplitude/ai/types";
class FetchAmplitudeClient implements AmplitudeClientLike {
  private _apiKey: string;
  private _buffer: AmplitudeEvent[] = [];
  constructor(apiKey: string) {
    this._apiKey = apiKey;
  }
  track(event: AmplitudeEvent): void {
    this._buffer.push(event);
  }
  async flush(): Promise<void> {
    if (!this._buffer.length) return;
    const events = this._buffer.splice(0);
    try {
      const resp = await fetch("https://api2.amplitude.com/2/httpapi", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ api_key: this._apiKey, events }),
      });
      if (!resp.ok) console.error(`[Amplitude] Flush failed: ${resp.status}`);
    } catch (err) {
      console.error(`[Amplitude] Flush error: ${(err as Error).message}`);
    }
  }
}
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    if (env.AMPLITUDE_TRACKING_DISABLED) return handleRequest(request, env);
    const transport = new FetchAmplitudeClient(env.AMPLITUDE_API_KEY);
    transport.track({
      event_type: "[Agent] User Message",
      user_id: userId,
      event_properties: {
        "[Agent] Session ID": sessionId,
        "[Agent] Agent ID": "my-agent",
        $llm_message: { text: content },
      },
    });
    // After the LLM call completes:
    transport.track({
      event_type: "[Agent] AI Response",
      user_id: userId,
      event_properties: {
        "[Agent] Session ID": sessionId,
        "[Agent] Agent ID": "my-agent",
        "[Agent] Model Name": model,
        "[Agent] Provider": "anthropic",
        "[Agent] Latency Ms": latencyMs,
        $llm_message: { text: responseText },
      },
    });
    // Non-blocking flush so events ship before the isolate terminates
    ctx.waitUntil(transport.flush());
    return new Response("ok");
  },
};

リクエストごとにFetchAmplitudeClient構築することで、リクエスト間のバッファ漏れを防ぐことができます。 イベントのinsert_id重複排除には crypto.randomUUID() を使用し、トラッキングを無効にできるよう AMPLITUDE_TRACKING_DISABLED 環境変数で制御します。

サーバレス環境での実行

SDKはサーバーレスプラットフォーム(Vercel、AWS Lambda、Netlify、Google Cloud Functions、Azure Functions、Cloudflare Pages)を環境変数から自動検出します。それを検出すると、promiseが解決される前にsession.run()が保留中のイベントをフラッシュするため、明示的なai.flush()は必要ありません。長時間稼働しているサーバーでは、セッションごとのフラッシュをスキップし、アナリティクスクライアントが通常どおりバッチ処理を行うようにします。

autoFlushオプション(Node)またはauto_flush(Python)を使用して、これをセッションごとに制御できます。 自動検出を使用するには未設定のままにするか、セッション終了時に常にフラッシュするにはtrueに、決してフラッシュしない場合にはfalseに設定してください。

外部でイベントを追跡する場合session.run()、ハンドラーが戻る前にフラッシュします。そうしないと、イベントがバッファリングされたままランタイムがプロセスをフリーズさせる可能性があります。

ai.flush()とai.shutdown()は、それぞれ異なるライフサイクルに対応しています。

  • ai.flush() はバッファリングされたイベントを送信し、SDK を稼働させ続けます。 サーバーレスハンドラやAPIエンドポイントで使用することで、応答する前に配信を保証できます。
  • ai.shutdown()は、基盤となるアナリティクスクライアントをフラッシュしてから閉じます。プロセス終了時に一度呼び出してください。たとえば、SIGTERMハンドラです。 apiKeyを通じてクライアントを作成した場合にのみクライアントを閉じます。独自のインスタンスを渡した場合、そのライフサイクルを所有することになります。
process.on("SIGTERM", () => {
  ai.shutdown();
  process.exit(0);
});

Cloudflare Workers(エッジ分離)はサポートされているサーバーレスターゲットではありません。完全なSDKをWorkerにバンドルすることはできません。フェッチベースの転送については、EdgeランタイムとCloudflare Workersを参照してください。

OpenTelemetryスパンの取り込み

以下のエクスポーターは、このSDKを既に使用しているNodeまたはPythonアプリ内でインプロセスで実行されます。スパンがすでに OpenTelemetry Collector に到達している場合は、SDK を完全にスキップし、代わりにコレクタを Amplitude の OTLP エンドポイントに向けることができます。『OpenTelemetryトレースを直接送信』を参照してください。

OpenTelemetry GenAIスパンを既に出力しているスタック(OpenLIT、Traceloop、OpenAI の OTel 計測)の場合、それらを[Agent]イベントにマッピングします。

  • AmplitudeGenAIExporter(インバウンド、本番環境対応):GenAI のセマンティック・コンベンション・スパンを取り込み、[Agent]イベントを送信します。 GenAI 以外のスパンを無視するため、混在パイプラインでも安全です。
  • AmplitudeAgentExporter(アウトバウンド、実験用):AmplitudeイベントをフラットなOTelスパンに変換して、他のバックエンドに転送します。 トレース階層を保持しません。

プライバシーモードを選択する

AIConfigでcontentModeを設定します。SDK 内のすべてのコンテンツ送信チャネルは単一のプライバシーゲートを経由するので、このモードは直接の SDK 呼び出し、プロバイダーラッパー、observe()スパン、OTel 例外記録、およびあらゆるフレームワーク統合に均一に適用されます。

  • full (デフォルト): プロンプトと応答テキストをキャプチャします。redactPii: true はデフォルトでオンになっていて、イベントがプロセスを終了する前に電子メール、電話番号、SSN、クレジットカード番号、IP アドレス、および base64 でエンコードされた画像データをスクラブします。SDKは電話とSSNの検出を米国形式用に調整します。国際ロケール用には、customRedactionPatternsまたはcustomRedactionFnを追加してください。
  • metadata_only: 会話テキスト、例外メッセージ、スタックトレース、またはシステム命令がAmplitudeに到達することはありません。トークン数、レイテンシ、モデル、コスト、セッショングループ化は引き続き表示されます。機密データまたは規制対象データに使用してください。
  • customer_enriched:デフォルトではテキストはありません。 事前に採点された概要を trackSessionEnrichment() 経由で送信してください。 既存の評価スタックを使用しているチーム向けに設計されています。

metadata_onlyは次のチャネルをゲートします。これは、シングルゲート契約の顧客に表示される表現です。

  • ユーザーメッセージとAIレスポンスのメッセージテキスト($llm_message.text)
  • ツールの入力と出力 ([Agent] Tool Input, [Agent] Tool Output)
  • システム プロンプト ([Agent] System Prompt)
  • observe()およびOTelスパンのエラーパスによって記録される例外メッセージとスタックトレース
  • フレームワークによって発行されたコンテンツ属性 (gen_ai.system_instructionsおよび gen_ai.input.messages/ gen_ai.output.messagesなど)
  • スコアコメント ([Agent] Comment)

マネージドエージェントアーキテクチャの場合、redactPii: true を full とともに使用することをお勧めします。マネージドAPIはすでにメッセージコンテンツをサーバー側に保存しているため、metadata_only はプライバシー上のメリットを追加することはありません。

独自のセッションエンリッチメントを提供

customer_enrichedモードでは、SDK はメッセージテキストを送信しません。 お客様は独自の評価パイプラインを実行し、その結果を構造化されたセッションレベルの強化として返送します。 コンプライアンスにコンテンツの送信がゼロであることが必要である場合や、評価ロジックがAmplitudeの組み込みサーバー側の強化機能を超えている場合に使用します。

SessionEnrichments オブジェクトを構築し、trackSessionEnrichment() (Node) または track_session_enrichment() (Python) を使用して送信します。エンリッチメントは[Agent] Session Enrichmentイベントとして格納され、[Agent] Enrichmentsプロパティにシリアル化されます。セッションを閉じる前にエンリッチメントを設定すると、同じフィールドが[Agent] Session Endにも追加されます。

SessionEnrichmentsオブジェクトの主要なフィールド

import {
  AmplitudeAI,
  AIConfig,
  ContentMode,
  SessionEnrichments,
  RubricScore,
  TopicClassification,
} from "@amplitude/ai";
const ai = new AmplitudeAI({
  apiKey: process.env.AMPLITUDE_AI_API_KEY!,
  config: new AIConfig({ contentMode: ContentMode.CUSTOMER_ENRICHED }),
});
const agent = ai.agent("support-bot", { agentVersion: "2.1.0" });
// 1. Run the conversation — no content is sent, only metadata.
const { sessionId } = await agent.session({ userId: "user-42" }).run(async (s) => {
  s.trackUserMessage("Why was I charged twice?");
  s.trackAiMessage(aiResponse.content, "gpt-4o", "openai", latencyMs);
  return { sessionId: s.sessionId };
});
// 2. Score the raw messages with your own pipeline.
const evalResults = await myEvalPipeline(conversationHistory);
// 3. Ship the enrichments back to Amplitude.
const enrichments = new SessionEnrichments({
  qualityScore: evalResults.quality,
  sentimentScore: evalResults.sentiment,
  overallOutcome: evalResults.outcome,
  topicClassifications: {
    billing: new TopicClassification({ topic: "billing-dispute", confidence: 0.92 }),
  },
  rubricScores: [new RubricScore({ name: "accuracy", score: 4, maxScore: 5 })],
  customMetadata: { eval_model: "gpt-4o-judge-v2" },
});
agent.trackSessionEnrichment(enrichments, { sessionId });

これにより、Amplitudeの組み込みエンリッチメントと同じイベントプロパティ(トピック、ルーブリック、結果、メッセージラベル)が生成されますが、代わりにパイプラインから取得されます。

メッセージラベル

メッセージ ラベルは、フィルタリングとセグメンテーションのために個々のメッセージに付加されたキーと値のペアです。たとえば、ルーティング タグ (flow、surface)、分類子出力 (intent、sentiment)、ビジネス コンテキスト (tier、plan)などです。 メッセージイベントで[Agent] Message Labelsとして送信されます。これらを接続するには、2 つの方法があります。

  • トラッキング時にインラインで、labelsをtrackUserMessage() / track_user_message()に渡すことによって。
  • さかのぼって、セッション後に分類器の結果が到着したときに、各トラッキングコールから返されたメッセージIDをSessionEnrichments.messageLabelsキーとするを通じて実行されます。

コストとトークンの管理

s.trackAiMessage(...)は、バンドルされているPydantic genai-pricesカタログを通じてモデル名とトークン数から[Agent] Cost USDを自動計算します。

自動料金設定を妨げる要因は 3 つあります。

  1. 認識できないモデル名です。 Vertex AI エイリアスは正規の claude-sonnet-4-20250514 と一致しませんclaude-sonnet-4-6。 内部ゲートウェイラベルは解決されません。 真新しいモデルはまだ genai-prices にはないかもしれません。正規プロバイダー ID を渡すか、上書きするために totalCostUsd を明示的に設定してください。
  2. ファインチューニング済みモデル。ft:のモデル名は自動で料金計算が行われません。ファインチューニング済みモデルやカスタムモデルについては、totalCostUsdを明示的に渡してください。
  3. **プロンプト キャッシュを使用した不正確な。 inputTokens**SDKはキャッシュインクルーシブであることを想定していますinputTokens(キャッシュされたトークンはサブセットであり、決して加算的ではありません)。プロバイダの慣習は異なります:

プロバイダー名の注意点:

  • **Gemini → google (Node)。**Node SDK は料金検索のために内部でgeminiをgoogleにマッピングします。defaultProvider: 'gemini'を渡しても料金取得できますが、生のカタログを調べて geminiを探す場合は、代わりにgoogleを探してください。
  • **Bedrockの候補生成。**ルックアップではドット区切りのプレフィックスが段階的に(region.vendor.model→ vendor.model→ model)取り除かれ、regional.とglobal.のバリアントが試行されます。そのため、新しいAWSリージョンやBedrockベンダーはカタログを更新することなく自動的に機能します。
  • ローカルの Python レートオーバーライド。 Python SDK には、アップストリームカタログのギャップを埋めるため、costs.pyにローカル料金表が含まれています。具体的には、gpt-5.6(アップストリームではCache-Writeプレミアムとロングコンテキストの料金階層が不足しています)、Fireworksのfastルーター(API レスポンスがベースモデル IDを返す場合でも高速な料金階層で課金されます)、およびアップストリームのOpenAIのリリースへの対応が遅れている場合に、例えばgpt-5.4をgpt-5.2の料金に対応付ける_MODEL_PRICE_ALIASESマップなどが含まれます。

組み込みのAnthropicラッパー、Bedrockラッパー、およびGeminiラッパーがこの正規化を代わりに処理します。手動での発信者はtrackAiMessage自分で処理する必要があります。SDKが差額料金を適用するように、cacheReadTokens / cacheCreationTokens を別々に渡してください。

自分でコストを計算する必要がある場合は、totalCostUsd を呼び出し、その結果をcalculateCost({ modelName, inputTokens, outputTokens, cacheReadInputTokens, cacheCreationInputTokens }) として渡してください。

calculateCost()のルール:

  • inputTokensは、キャッシュ読み取りとキャッシュ作成を含む入力の合計です。 Anthropicの場合は、input_tokens + cache_read_input_tokens + cache_creation_input_tokensを渡してください。OpenAI の場合、prompt_tokensにはキャッシュされたトークンが既に含まれているため、そのまま渡してください。
  • outputTokensは、プロバイダーが出力する推論トークンを含む出力の合計です。 OpenAIのcompletion_tokensには既に推論が含まれているため、別途追加しないでください。
  • cacheReadInputTokensおよび cacheCreationInputTokensはinputTokensのサブセットであり、差別料金率を適用するためだけに使用されます。
  • reasoningTokensパラメータは非推奨であり、無視されます。 これは下位互換性のために保持されています。別々に渡すとリクエストのコストが過剰になります。

欠落コストのデバッグ

SDKが料金を計算できない場合、一意の(model, provider, reason)タプルごとに警告を1件記録します。プロセスごとに記録される一意のタプルは最大100個に制限されます。ログをgrepしてUnable to calculate cost for model=を検索すると、どのルックアップが失敗したのか、その理由を確認できます。

非対話型エージェントのランコストをレポートする

プロバイダーラッパーはすべての [Agent] AI Response で自動的に出力を生成します。 [Agent] Cost USDバッチジョブやアーティファクトジェネレータなど、最終的なテキスト応答なしで実行を終了するエージェントは、[Agent] AI Response を発生しないことがありますので、そのコストは発生しません。 実行の最後に trackRunCost() を呼び出して、コールごとのイベントではカバーされなかったコストを出力します:

typescript
s.trackRunCost(totalCostUsd, inputTokens, outputTokens, model, "openai", {
  latencyMs,
  content: "[Artifact: batch run]", // optional; omit for a cost-only event
});
python
s.track_run_cost(
    total_cost_usd,
    input_tokens,
    output_tokens,
    model,
    "openai",
    latency_ms=latency_ms,
    content="[Artifact: batch run]",  # optional; omit for a cost-only event
)

trackRunCost() はデルタコストのみを出力するため、実行中にすでに追跡されているコールごとのコストと調整されます。 デフォルトではコンテンツは空になるため、コストのみのバランシングイベントの場合は content を省略するか、セッションビューアにバブルを表示したい場合は短い文字列を渡します。実行完了のコストは、ライフサイクル専用でコストフィールドを持たない [Agent] Session End ではなく、この方法で付随させてください。

料金データを最新の状態に保つ

コストはバンドルされているgenai-pricesカタログに依存するため、新しくリリースされたモデルは、カタログが更新されるまで[Agent] Cost USD/0とレポートする場合があります。実行時に最新の料金を取得するには、起動時にenableLivePriceUpdates()(Node)またはenable_live_price_updates()(Python)を使用してオプトインしてください。

  • デフォルトの状態はオフです。 オプトインする必要があります。
  • デフォルトの更新間隔は1時間です(Nodeでは3_600_000ミリ秒単位、Pythonでは3600秒単位)。最初の引数で設定可能です。
  • アップストリーム エンドポイント: パブリック raw.githubusercontent.com、Pydantic genai-prices GitHubリポジトリ。Amplitudeが所有するエンドポイントではありません。
  • 障害モード:サイレント。 ネットワークエラーは無視され、バンドルされたカタログが引き続き使用されます。
  • Idempotent: 2回目の呼び出しは何も行いません。
  • 動作していることを確認する方法: デバッグ フラグやステータス出力はありません。 唯一の信号は、[Agent] Cost USD以前はコストが不足していたモデルにデータが入力され始めていることです。

有効にするタイミング: バンドルカタログにまだ含まれていない新しくリリースされたモデルにアップグレードした場合。トグルがない場合、SDK のバージョンが変わるまで、[Agent] Cost USDそのモデルについては省略されたままになります。

プロセスがエアギャップ状態で実行されている場合や、環境が へのアウトバウンド HTTPS をブロックしている場合には、この機能を有効にしないでくださいraw.githubusercontent.com。 これには何のメリットもなく、すべてのスタートアップはネットワークの往復に失敗するコストを負担することになります。

デプロイメタデータのキャプチャ

SDK はスタートアップ時にデプロイを識別する環境変数を読み取り、すべての[Agent]イベントにその値をスタンプします。これにより、コード全体で値をスレッド化することなく、SHA またはリリースでグラフをフィルタリングできます。

リポジトリのURLのオプトインは、環境変数でのみ行えます。以前の SDK リリースでは、git メタデータからリポジトリを自動的にキャプチャしていましたが、これは削除されました。SDK のアップグレード後に空になった場合は、[Agent] Git Repo明示的に設定してくださいAMPLITUDE_GIT_REPO。

セマンティックキャッシュヒットの追跡

独自のセマンティックキャッシュまたはレスポンスキャッシュから完全なレスポンスを提供するときは、AIメッセージの呼び出しで wasCached: true (Node) または was_cached=True (Python) を渡します。これはトークンレベルのプロンプトキャッシュとは異なり、[Agent] Was Cachedにマッピングされるため、キャッシュヒット率とそれによって節約されるコストをチャートで確認できます。

メッセージコンテンツの構成

trackUserMessageへの最初の引数は、[Agent] User Message上の$llm_message.textになります。これは、セッションリスト、セグメンテーション、およびエンリッチメントが「ユーザーの発言」として扱うものです。 2つの実践的なルール:

**メッセージ本文として、自然言語による短い行を渡すようにしてください。**たとえば、実際のプロンプトやヘッドレス ジョブの標準的な概要:

s.trackUserMessage(
  "Summarize the attached design doc and list open questions",
  {
    context: { structuredPayload: payloadRecord },
  },
);

メッセージ本文として大きな JSON ブロブを渡さないでください。 このプロダクトはセッションタイトルとしてJSONを使用し、グラフを未加工のJSONで分類します。

// Session label becomes the JSON
s.trackUserMessage(JSON.stringify(payloadRecord));

構造化されたセグメンテーションのディメンションをcontextオプションに追加します([Agent] Context JSONになり、グラフでクエリ可能になります)。サーバー側のエンリッチメントが構造化データに基づいて推論できるように、コンテンツに重要な事実も保持してください。エンリッチメントは eval 入力を [Agent] Context ではなく、主にターンテキストから導き出します。

SDKを使用しないインスツルメント

サポートされていないランタイム(Java、Go、Ruby、エッジ環境)の場合、イベントを直接Amplitude HTTP APIに送信してください。

bash
curl -X POST https://api2.amplitude.com/2/httpapi \
  -H 'Content-Type: application/json' \
  -d '{
    "api_key": "YOUR_API_KEY",
    "events": [{
      "event_type": "[Agent] User Message",
      "user_id": "user-123",
      "event_properties": {
        "[Agent] Session ID": "sess-abc",
        "[Agent] Agent ID": "support-chatbot",
        "$llm_message": { "text": "How do I cancel my subscription?" }
      }
    }]
  }'

メッセージコンテンツには $llm_message.text を使用してください(取り込みパイプラインはインタラクションテキスト用にこのプロパティを読み取ります)。完全なプロパティ参照とイベント JSON の例については、Agent アナリティクスのタクソノミーを参照してください。

イベントを直接送信する場合、SDK がそれ以外の方法で処理する内容については、お客様が責任を負います。

データを検証する

doctor を実行して環境変数、インストール済みの依存関係、および event-pipeline への接続を検証します。

bash
npx amplitude-ai doctor

デフォルトの動作ではAMPLITUDE_AI_API_KEYを読み取ります。 アプリでキーに別の名前を付ける場合は、-key-envを使用してdoctorに正しい変数を指定してください:

bash
npx amplitude-ai doctor -key-env MY_KEY_NAME

値を指定せず-key-envを渡した場合、doctorはデフォルトにサイレントフォールバックするのではなく、使用方法のメッセージを表示してエラーになります。この規則は、フラグが存在する場合にのみ適用されます。

次に、イベントがAmplitudeに到着することを確認します:

  1. プロジェクトのライブイベントストリームを開きます。
  2. 計装済みコードからテストセッションを送信します。
  3. 数秒以内に、これらのプロパティが入力された [Agent] AI Response イベントが表示されるはずです。
    • [Agent] Session ID, [Agent] Agent ID
    • [Agent] Model Name, [Agent] Provider
    • [Agent] Latency Ms
    • [Agent] Input Tokens, [Agent] Output Tokens
    • [Agent] Cost USD

summary() を使用したローカル検証

導入前に、MockAmplitudeAI.summary() を使用して、キャプチャされたすべてのイベントのフィルレートレポートを取得します。データがAmplitudeに到達する前に、8つの検証ゲートをチェックし、ギャップにフラグを立てます。

typescript
import { AIConfig } from "@amplitude/ai";
import { MockAmplitudeAI } from "@amplitude/ai/testing";
const mock = new MockAmplitudeAI(new AIConfig({ contentMode: "full" }));
const agent = mock.agent("test-agent", { userId: "u1" });
await agent.session({ sessionId: "s1" }).run(async (s) => {
  s.trackUserMessage("hello");
  s.trackAiMessage("response", "gpt-4o-mini", "openai", 150);
});
console.log(mock.summary());

サマリー出力は次のようになります。

text
Agent Analytics fill-rate report
================================
Events captured: 2
  [Agent] User Message:  1
  [Agent] AI Response:   1
Verification gates (8/8 passing):
  ✓ user_id or device_id present
  ✓ [Agent] Session ID present
  ✓ [Agent] Agent ID present
  ✓ [Agent] Model Name present
  ✓ [Agent] Provider present
  ✓ [Agent] Latency Ms > 0
  ✓ [Agent] Input Tokens > 0
  ✓ [Agent] Output Tokens > 0
  ✓ [Agent] Cost USD > 0

一般的な問題の修正:

モッククライアントに対するテスト

CIの場合、イベントが正しく発行されることをアサートするために、@amplitude/ai/testingのMockAmplitudeAIを使用します:

import { AIConfig } from "@amplitude/ai";
import { MockAmplitudeAI } from "@amplitude/ai/testing";
const mock = new MockAmplitudeAI(new AIConfig({ contentMode: "full" }));
const agent = mock.agent("test-agent", { userId: "u1" });
await agent.session({ sessionId: "s1" }).run(async (s) => {
  s.trackUserMessage("hello");
  s.trackAiMessage("response", "gpt-4o-mini", "openai", 150);
});
mock.assertEventTracked("[Agent] User Message", { userId: "u1" });
mock.assertSessionClosed("s1");
// Data quality gate: every AI Response must carry the eight verification fields
for (const e of mock.eventsOfType("[Agent] AI Response")) {
  const p = e.event_properties ?? {};
  expect(e.user_id || e.device_id).toBeTruthy();
  expect(p["[Agent] Session ID"]).toBeTruthy();
  expect(p["[Agent] Model Name"]).toBeTruthy();
  expect(p["[Agent] Provider"]).toBeTruthy();
  expect(p["[Agent] Latency Ms"]).toBeGreaterThan(0);
  expect(p["[Agent] Input Tokens"]).toBeGreaterThan(0);
  expect(p["[Agent] Output Tokens"]).toBeGreaterThan(0);
  expect(p["[Agent] Cost USD"]).toBeGreaterThan(0);
}

不適切なモデル名やトークン数の欠落など、実行時に例外をスローせずにダッシュボードを破損させるサイレントなインスツルメンテーションの回帰を検出するため、このテストをCIに維持してください。

信頼性とエラー処理

インスツルメンテーションによってアプリケーションがダウンすることはありません:

  • **トラッキングコールが例外をスローすることはありません。**すべてのtrack*メソッドは、内部的に独自のエラーをキャッチして記録します。 シリアル化のバグや不正なフィールドがエージェントのリクエストパスを中断することはありません。
  • **SDKはイベントをバッファリングし、再試行します。**基盤となる@amplitude/analytics-nodeクライアントは、イベントをバッチ処理し、失敗した送信をそのトランスポート層から再試行します。
  • **障害が発生しても、適切に機能を縮小します。**Amplitudeに到達できない場合、SDKは再試行回数を使い果たした後、イベントをサイレントにドロップします。 アプリケーションは引き続き動作します。

開発時には、userId や sessionId などの不足している必須フィールドを早期に顕在化させるために、AIConfig で validate: true (Node) または validate=True (Python) を設定してください。検証エラーは ValidationErrorをスローするため、本番環境に到達する前にテストでそれらをキャッチできます。最も厳格な CI チェックを行うには dryRun / dry_run と組み合わせてください。

自動計測ツールとCLIツール

コール サイトを編集せずに計測するには、プロセス開始時にサポートされているプロバイダーに自動パッチを適用します。 これは SDK が接続されていることを確認する最も速い方法です。完全なイベントモデル (ユーザーメッセージ、セッション、スコア) については、「SDKの初期化」に示されているように、エージェントとセッションを使用します。

# Wrapper command
AMPLITUDE_AI_API_KEY=xxx AMPLITUDE_AI_AUTO_PATCH=true amplitude-ai-instrument node app.js
# Or Node's ESM preload flag directly
AMPLITUDE_AI_API_KEY=xxx AMPLITUDE_AI_AUTO_PATCH=true node --import @amplitude/ai/register app.js

どちらのランタイムも同じ環境変数を読み取ります。

アプリを実行せずに環境を検査するには、amplitude-ai status を使用します。 これにより、インストールされている SDK のバージョン、検出されたプロバイダー パッケージ、および現在の環境変数の設定が出力されます。依存関係とイベント パイプライン接続を検証するには、doctorコマンドの「データの検証」を参照してください。

データカタログにイベントスキーマを登録する

SDKには、すべての[Agent]イベントタイプとそのプロパティをAmplitudeのデータカタログに登録するCLIが付属しているため、イベントは取り込みから推測されるのではなく、説明、タイプ、および必須フラグとともにドキュメント化された状態で反映されます。

前提条件: タクソノミー API にアクセスできるプランと、[設定] > [プロジェクト] からプロジェクトの API キーとシークレットキーが必要です。

バンドルされている CLI は、イベントカタログを読み取り、実行可能な curl コマンドを出力します。これ自体はネットワーク要求を行わないため、コマンドを実行する前に確認できます。

bash
# Print commands with your keys
npx amplitude-ai-register-catalog --api-key YOUR_KEY --secret-key YOUR_SECRET
# Execute immediately
npx amplitude-ai-register-catalog --api-key YOUR_KEY --secret-key YOUR_SECRET | bash
# EU data residency
npx amplitude-ai-register-catalog --api-key YOUR_KEY --secret-key YOUR_SECRET --eu | bash

これらのコマンドはべき等です。欠落しているイベントやプロパティを作成し、既存のものを更新するため、SDK のアップグレードで新しいフィールドが追加された後でも安全に再実行できます。

デバッグとドライラン

2 つのAIConfigフラグはローカルでのイベント検査に役立ち、それぞれ自動計装で使用できる対応する環境変数が用意されています。

debug: true (Node) / debug=True (Python) は、すべてのイベントの 1 行サマリーを stderr に出力し、引き続き Amplitude にイベントを送信します:

text
[amplitude-ai] [Agent] AI Response | user=user-123 session=sess-abc agent=my-agent model=gpt-4o latency=1203ms tokens=150→847 cost=$0.0042

dryRun: true (Node) / dry_run=True (Python) はイベント JSON 全体を stderr に記録し、何も送信しません。これを使用して、ライブAPIキーなしでローカル開発やCIでのイベントシェイプを検証できます。自動計装の場合、代わりにコマンドでAMPLITUDE_AI_DEBUG=trueを設定してください。

トラブルシューティング

API リファレンス

コアクラス

セッション追跡方法

高階関数

その他のAPI

これは役に立ちましたか?