コーディングエージェント向けAEOトラッキングの構築方法
Vercelは、コーディングエージェント向けのAIエンジン最適化(AEO)追跡システムを構築し、エージェントの検索行動や引用元を分析するために、サンドボックス環境での実行ライフサイクルを確立した。
キーポイント
コーディングエージェント向けAEOの必要性
従来のチャットモデルだけでは不十分であり、実際の開発ワークフローで使用されるコーディングエージェントの検索行動(約20%のプロンプトでウェブ検索を実行)を追跡する必要がある。
コーディングエージェントの特殊性と課題
コーディングエージェントは単一のAPI呼び出しではなく、ファイルシステムやシェルアクセスを含む完全な開発環境で動作するため、実行の分離と可観測性が新たな課題となる。
Vercelサンドボックスによる解決策
エフェメラルなLinux MicroVMを数秒で起動し、各エージェント実行に専用のサンドボックスを提供することで、安全な実行環境と統一された観測を実現している。
統一された6ステップのライフサイクル
サンドボックスの作成、エージェントCLIのインストール、認証情報の注入、プロンプトでのエージェント実行、レスポンスのキャプチャ、サンドボックスの破棄という標準化されたプロセスを確立した。
AI Gatewayによる統合管理
各エージェントに直接プロバイダーAPIキーを与える代わりに、環境変数を設定してすべてのLLM呼び出しをVercel AI Gateway経由にすることで、統一されたロギング、レート制限、コスト追跡を実現している。
影響分析・編集コメントを表示
影響分析
この記事は、AIエージェントが実際の開発ワークフローでどのように情報を検索・利用するかを可視化する新たな手法を示しており、企業が自社コンテンツのAI可視性を最適化するための実践的なフレームワークを提供している。特にコーディングエージェントに特化したAEO追跡は、開発者向けAIツールの評価と改善に重要な知見をもたらす可能性がある。
編集コメント
コーディングエージェントという実用的なAI応用領域に焦点を当て、その可視性追跡の具体的な技術的アプローチを詳細に説明している点が価値ある。企業のAEO戦略に新たな視点を提供する内容。
AIは人々が情報を見つける方法を変えました。企業にとって、これはLLM(大規模言語モデル)が自社のWebコンテンツをどのように検索し、要約するかを理解することが極めて重要であることを意味します。
私たちは、モデルがVercelおよび当社サイトをどのように発見、解釈、参照するかを追跡するAIエンジン最適化(AEO)システムを構築しています。
これは当初、標準的なチャットモデルのみに焦点を当てたプロトタイプとして始まりましたが、すぐにそれだけでは不十分だと気付きました。可視性の全体像を把握するには、コーディングエージェントを追跡する必要があったのです。
標準的なモデルでは、追跡は比較的単純です。AI Gatewayを使用して数十の一般的なモデル(GPT、Gemini、Claudeなど)にプロンプトを送信し、その応答、検索行動、引用元を分析します。
しかし、コーディングエージェントの振る舞いは大きく異なります。多くのVercelユーザーは、プロジェクトに積極的に取り組んでいる間、ターミナルやIDEを通じてAIと対話します。初期のサンプリングでは、コーディングエージェントは約20%のプロンプトでWeb検索を実行することが分かりました。これらの検索は実際の開発ワークフローに組み込まれて行われるため、応答の品質と情報源の正確性の両方を評価することが特に重要です。
コーディングエージェントのAEOを測定するには、モデル単体のテストとは異なるアプローチが必要です。コーディングエージェントは、単一のAPI呼び出しに応答するようには設計されていません。プロジェクト内で動作し、ファイルシステム、シェルアクセス、パッケージマネージャーを含む完全な開発環境を前提として構築されています。
これにより、新たな課題が生じます。
実行分離: 任意のコードを実行できる自律エージェントを安全に実行するにはどうすればよいか?
可観測性: 各エージェントが独自のトランスクリプト形式、ツール呼び出し規約、出力構造を持っている場合、エージェントが何をしたかをどのように記録するか?
コーディングエージェントAEOのライフサイクル
コーディングエージェントは通常、APIではなくCLI(コマンドラインインターフェース)を通じて何らかの形でアクセスされます。プロンプトを送信して応答を記録するだけの場合でも、CLIは完全なランタイム環境にインストールし、実行する必要があります。
Vercel Sandboxは、数秒で起動する一時的なLinux MicroVMを提供することでこれを解決します。各エージェントの実行は独自のサンドボックスを取得し、使用するCLIに関わらず、同じ6段階のライフサイクルに従います。
- サンドボックスの作成: 適切なランタイム(Node 24、Python 3.13など)とタイムアウトを設定した新しいMicroVMを起動します。タイムアウトは厳格な上限であるため、エージェントがハングまたはループした場合、サンドボックスはそれを強制終了します。
- エージェントCLIのインストール: 各エージェントはnpmパッケージ(例:
@anthropic-ai/claude-code、@openai/codexなど)として提供されます。サンドボックスはこれをグローバルにインストールし、シェルコマンドとして利用可能にします。 - 認証情報の注入: 各エージェントに直接プロバイダーAPIキーを与える代わりに、すべてのLLM呼び出しをVercel AI Gateway経由でルーティングする環境変数を設定します。これにより、各エージェントが異なる基盤プロバイダーを使用している場合でも(ただしシステムは直接プロバイダーキーも許可します)、すべてのエージェントにわたる統一されたロギング、レート制限、コスト追跡が可能になります。
- プロンプトによるエージェントの実行: これはエージェントごとに異なる唯一のステップです。各CLIには独自の呼び出しパターン、フラグ、設定形式があります。しかし、サンドボックスの観点では、これは単なるシェルコマンドです。
- トランスクリプトの記録: エージェントが終了した後、どのツールを呼び出したか、Web検索を行ったかどうか、応答で何を推奨したかを含む記録を抽出します。これはエージェント固有です(後述)。
- 後片付け: サンドボックスを停止します。何か問題が発生した場合、キャッチブロックによりサンドボックスは確実に停止されるため、リソースがリークすることはありません。
コードでは、ライフサイクルはこのようになります。
設定としてのエージェント
ライフサイクルが統一されているため、各エージェントは単純な設定オブジェクトとして定義できます。システムに新しいエージェントを追加するとは、新しいエントリを追加することを意味し、サンドボックスのオーケストレーションが他のすべてを処理します。
runtimeはMicroVMのベースイメージを決定します。ほとんどのエージェントはNodeで実行されますが、システムはPythonランタイムもサポートします。setupCommandsは配列です。なぜなら、一部のエージェントはグローバルインストール以上の設定を必要とするからです。例えば、Codexは~/.codex/config.tomlに書き込まれるTOML設定ファイルも必要とします。buildCommandはプロンプトを受け取り、実行するシェルコマンドを返す関数です。各エージェントのCLIには独自のフラグと呼び出しスタイルがあります。
ルーティングにAI Gatewayを使用
コストとログの管理を一元化するためにAI Gatewayを使用したいと考えました。これには、サンドボックス内の環境変数を介してプロバイダーのベースURLを上書きする必要がありました。エージェント自体はこれが起こっていることを認識せず、プロバイダーと直接通信しているかのように動作します。
Claude Codeの場合、次のようになります。
ANTHROPIC_BASE_URLはapi.anthropic.comではなくAI Gatewayを指します。エージェントのHTTP呼び出しはGatewayに送られ、そこからAnthropicにプロキシされます。ANTHROPIC_API_KEYは意図的に空文字列に設定されています。Gatewayは独自のトークンで認証するため、エージェントは直接のプロバイダーキーを必要とせず(持ちません)、
この同じパターンはCodex(OPENAI_BASE_URLを上書き)や、ベースURL環境変数を尊重する他のエージェントでも機能します。プロバイダーAPI認証情報を直接使用することもできます。
トランスクリプト形式の問題
エージェントがサンドボックス内での実行を終了すると、生のトランスクリプト、つまりエージェントが行ったすべての記録が得られます。
問題は、各エージェントが異なる形式でトランスクリプトを生成することです。Claude CodeはJSONLファイルをディスクに書き込みます。CodexはJSONを標準出力にストリーミングします。OpenCodeも標準出力を使用しますが、異なるスキーマです。同じツールに対して異なる名前を使用し、メッセージに対して異なるネスト構造、異なる規約を持っています。
これらすべてを単一のブランドパイプラインに投入する必要があったため、4段階の正規化レイヤーを構築しました。
- トランスクリプトキャプチャ: 各エージェントはトランスクリプトを異なる方法で保存するため、このステップはエージェント固有です。
- 解析: 各エージェントには独自のパーサーがあり、ツール名を正規化し、エージェント固有のメッセージ構造を単一の統一イベントタイプに平坦化します。
- エンリッチメント: ツール引数から構造化メタデータ(URL、コマンド)を抽出する共有の後処理ステップで、各エージェントが引数に名前を付ける方法の違いを正規化します。
- 要約とブランド抽出: 統一イベントを統計に集約し、標準モデル応答に使用されるのと同じブランド抽出パイプラインに投入します。
#### ステージ1: トランスクリプトキャプチャ
これはサンドボックスがまだ実行されている間(前セクションのライフサイクルのステップ5)に行われます。
Claude Codeはトランスクリプトをサンドボックスファイルシステム上のJSONLファイルとして書き込みます。エージェント終了後にそれを見つけて読み出す必要があります。
CodexとOpenCodeはどちらもトランスクリプトを標準出力に出力するため、記録はより簡単です。JSON行の出力をフィルタリングします。
このステージの出力はすべてのエージェントで同じです:生のJSONL文字列。しかし、各JSON行の構造はまだエージェントごとに完全に異なり、それが次のステージで処理されます。
#### ステージ2: ツール名とメッセージ形状の解析
各エージェントに対して専用のパーサーを構築し、2つのことを同時に行います:ツール名を正規化し、エージェント固有のメッセージ構造を単一のフォーマット済みイベントタイプに平坦化します。
ツール名の正規化
同じ操作でもエージェント間で異なる名前を持ちます。
| 操作 | Claude Code | Codex | OpenCode |
|---|---|---|---|
| ファイルを読む | Read | read_file | read |
| ファイルを書く | Write | write_file | write |
| ファイルを編集する | StrReplace | patch_file | patch |
| コマンドを実行する | Bash | shell | bash |
| Webを検索する | WebFetch | (様々) | (様々) |
各パーサーは、エージェント固有の名前を約10の正規名にマッピングするルックアップテーブルを保持します。
メッセージ形状の平坦化
名前付け以外に、イベントの構造はエージェント間で異なります。
- Claude Codeはメッセージを
messageプロパティ内にネストし、tool_useブロックをcontent配列に混在させます。 - Codexはツールイベントと並行してResponses APIライフサイクルイベント(
thread.started、turn.completed、output_text.delta)を持ちます。 - OpenCodeは
part.toolとpart.stateを介してツール呼び出しと結果を同じイベントにバンドルします。
各エージェントのパーサーはこれらの構造的差異を処理し、すべてを単一のTranscriptEventタイプに折りたたみます。
このステージの出力は平坦なTranscriptEvent[]配列で、どのエージェントが生成したかに関係なく同じ形状です。
#### ステージ3: エンリッチメント
解析後、すべてのイベントにわたって共有の後処理ステップが実行されます。これにより、ツール引数から構造化メタデータが抽出されるため、下流のコードはClaude Codeがファイルパスをargs.pathに置く一方でCodexがargs.fileを使用することを知る必要がなくなります。
#### ステージ4: 要約とブランド抽出
エンリッチされたTranscriptEvent[]配列は集計統計(タイプ別の総ツール呼び出し数、Webフェッチ、エラー)に要約され、標準モデル応答に使用されるのと同じブランド抽出パイプラインに投入されます。この時点から、システムはデータがコーディングエージェントから来たのかモデルAPI呼び出しから来たのかを知らず、気にしません。
Vercel Workflowによるオーケストレーション
このパイプライン全体はVercel Workflowとして実行されます。プロンプトが「エージェント」タイプとしてタグ付けされると、ワークフローは設定されたすべてのエージェントに並列にファンアウトし、各エージェントは独自のサンドボックスを取得します。
私たちが学んだこと
- コーディングエージェントはWeb検索から相当量のトラフィックを生み出します。プロンプトのランダムサンプルに対する初期テストでは、コーディングエージェントは約20%の確率で検索を実行することが示されました。より多くのデータを収集するにつれて、エージェントの検索行動についてより包括的なビューを構築しますが、これらの結果はコンテンツをコーディングエージェント向けに最適化することが重要であることを明確にしました。
- エージェントの推奨事項はモデル応答とは異なる形状をしています。コーディングエージェントがツールを提案する場合、インポート文、設定ファイル、デプロイメントスクリプトなど、そのツールを使用した動作コードを生成する傾向があります。推奨事項は出力に埋め込まれており、単に散文で言及されているだけではありません。
- トランスクリプト形式は混乱しています。そして、エージェントCLIツールが迅速なアップデートをリリースするにつれて、さらに混乱しています。早期に正規化レイヤーを構築したことで、絶え間ない破損から救われました。
- 同じブランド抽出パイプラインがモデルとエージェントの両方で機能します。難しい部分は上流のすべてです:エージェントを実行させ、何をしたかを記録し、評価可能な構造に正規化することです。
今後の計画
ツールのオープンソース化: 当社はシステムのOSS(オープンソースソフトウェア)バージョンをリリースする予定であり、他のチームが標準モデルとコーディングエージェントの両方で独自のAEO評価を追跡できるようにします。
手法の詳細解説: 完全なAEO評価手法をカバーするフォローアップ記事を準備中です:プロンプト設計、デュアルモードテスト(Web検索対トレーニングデータ)、クエリを第一級エンティティとするアーキテクチャ、およびシェア・オブ・ボイス指標について。
エージェントカバレッジの拡大: エコシステムの成長に伴い、より多くのエージェントを追加し、テストするプロンプトの種類を拡張します(「ツールを推薦する」だけでなく、完全なプロジェクトスキャフォールディング、デバッグなど)。
原文を表示
AI has changed the way that people find information. For businesses, this means it's critical to understand how LLMs search for and summarize their web content.
We're building an AI Engine Optimization (AEO) system to track how models discover, interpret, and reference Vercel and our sites.
This started as a prototype focused only on standard chat models, but we quickly realized that wasn’t enough. To get a complete picture of visibility, we needed to track coding agents.
For standard models, tracking is relatively straightforward. We use AI Gateway to send prompts to dozens of popular models (e.g. GPT, Gemini, and Claude) and analyze their responses, search behavior, and cited sources.
Coding agents, however, behave very differently. Many Vercel users interact with AI through their terminal or IDE while actively working on projects. In early sampling, we found that coding agents perform web searches in roughly 20% of prompts. Because these searches happen inline with real development workflows, it’s especially important to evaluate both response quality and source accuracy.
Measuring AEO for coding agents requires a different approach than model-only testing. Coding agents aren’t designed to answer a single API call. They’re built to operate inside a project and expect a full development environment, including a filesystem, shell access, and package managers.
That creates a new set of challenges:
Execution isolation: How do you safely run an autonomous agent that can execute arbitrary code?
Observability: How do you capture what the agent did when each agent has its own transcript format, tool-calling conventions, and output structure?
The coding agent AEO lifecycle
Coding agents are typically accessed at some level through CLIs rather than APIs. Even if you’re only sending prompts and capturing responses, the CLI still needs to be installed and executed in a full runtime environment.
Vercel Sandbox solves this by providing ephemeral Linux MicroVMs that spin up in seconds. Each agent run gets its own sandbox and follows the same six-step lifecycle, regardless of the CLI it uses.
Create the sandbox. Spin up a fresh MicroVM with the right runtime (Node 24, Python 3.13, etc.) and a timeout. The timeout is a hard ceiling, so if the agent hangs or loops, the sandbox kills it.
Install the agent CLI. Each agent ships as an npm package (i.e., @anthropic-ai/claude-code, @openai/codex, etc.). The sandbox installs it globally so it's available as a shell command.
Inject credentials. Instead of giving each agent a direct provider API key, we set environment variables that route all LLM calls through Vercel AI Gateway. This gives us unified logging, rate limiting, and cost tracking across every agent, even though each agent uses a different underlying provider (though the system allows direct provider keys as well).
Run the agent with the prompt. This is the only step that differs per agent. Each CLI has its own invocation pattern, flags, and config format. But from the sandbox's perspective, it's just a shell command.
Capture the transcript. After the agent finishes, we extract a record of what it did, including which tools it called, whether it searched the web, and what it recommended in the response. This is agent-specific (covered below).
Tear down. Stop the sandbox. If anything went wrong, the catch block ensures the sandbox is stopped anyway so we don't leak resources.
In the code, the lifecycle looks like this.
Agents as config
Because the lifecycle is uniform, each agent can be defined as a simple config object. Adding a new agent to the system means adding a new entry, and the sandbox orchestration handles everything else.
runtime determines the base image for the MicroVM. Most agents run on Node, but the system supports Python runtimes too.
setupCommands is an array because some agents need more than a global install. For example, Codex also needs a TOML config file written to ~/.codex/config.toml.
buildCommand is a function that takes the prompt and returns the shell command to run. Each agent's CLI has its own flags and invocation style.
Using the AI Gateway for routing
We wanted to use the AI Gateway to centralize management of cost and logs. This required overriding the provider’s base URLs via environment variables inside the sandbox. The agents themselves don’t know this is happening and operate as if they are talking directly to their provider.
Here’s what this looks like for Claude Code:
ANTHROPIC_BASE_URL points to AI Gateway instead of api.anthropic.com. The agent's HTTP calls go to Gateway, which proxies them to Anthropic.
ANTHROPIC_API_KEY is set to empty string on purpose — Gateway authenticates via its own token, so the agent doesn't need (or have) a direct provider key.
This same pattern works for Codex (override OPENAI_BASE_URL) and any other agent that respects a base URL environment variable. Provider API credentials can also be used directly.
The transcript format problem
Once an agent finishes running in its sandbox, we have a raw transcript, which is a record of everything it did.
The problem is that each agent produces them in a different format. Claude Code writes JSONL files to disk. Codex streams JSON to stdout. OpenCode also uses stdout, but with a different schema. They use different names for the same tools, different nesting structures for messages, and different conventions.
We needed all of this to feed into a single brand pipeline, so we built a four-stage normalization layer:
Transcript capture: Each agent stores its transcript differently, so this step is agent-specific.
Parsing: Each agent has its own parser that normalizes tool names and flattens agent-specific message structures into a single unified event type.
Enrichment: Shared post-processing that extracts structured metadata (URLs, commands) from tool arguments, normalizing differences in how each agent names its args.
Summary and brand extraction: Aggregate the unified events into stats, then feed into the same brand extraction pipeline used for standard model responses.
Stage 1: Transcript capture
This happens while the sandbox is still running (step 5 in the lifecycle from the previous section).
Claude Code writes its transcript as a JSONL file on the sandbox filesystem. We have to find and read it out after the agent finishes:
Codex and OpenCode both output their transcripts to stdout, so capture is simpler — filter the output for JSON lines:
The output of this stage is the same for all agents: a string of raw JSONL. But the structure of each JSON line is still completely different per agent, and that's what the next stage handles.
Stage 2: Parsing tool names and message shapes
We built a dedicated parser for each agent that does two things at once: normalizes tool names and flattens agent-specific message structures into a single formatted event type.
Tool name normalization
The same operation has different names across agents:
Operation
Claude Code
Codex
OpenCode
Read a file
Read
read_file
read
Write a file
Write
write_file
write
Edit a file
StrReplace
patch_file
patch
Run a command
Bash
shell
bash
Search the web
WebFetch
(varies)
(varies)
Each parser maintains a lookup table that maps agent-specific names to ~10 canonical names:
Message shape flattening
Beyond naming, the structure of events varies across agents:
Claude Code nests messages inside a message property and mixes tool_use blocks into content arrays.
Codex has Responses API lifecycle events (thread.started, turn.completed, output_text.delta) alongside tool events.
OpenCode bundles tool call + result in the same event via part.tool and part.state.
The parser for each agent handles these structural differences and collapses everything into a single TranscriptEvent type:
The output of this stage is a flat array of TranscriptEvent[] , which is the same shape regardless of which agent produced it.
Stage 3: Enrichment
After parsing, a shared post-processing step runs across all events. This extracts structured metadata from tool arguments so that downstream code doesn't need to know that Claude Code puts file paths in args.path while Codex uses args.file:
Stage 4: Summary and brand extraction
The enriched TranscriptEvent[] array gets summarized into aggregate stats (total tool calls by type, web fetches, errors) and then fed into the same brand extraction pipeline used for standard model responses. From this point forward, the system doesn't know or care whether the data came from a coding agent or a model API call.
Orchestration with Vercel Workflow
This entire pipeline runs as a Vercel Workflow. When a prompt is tagged as "agents" type, the workflow fans out across all configured agents in parallel and each gets its own sandbox:
What we’ve learned
Coding agents contribute a meaningful amount of traffic from web search. Early tests on a random sample of prompts showed that coding agents execute search around 20% of the time. As we collect more data we will build a more comprehensive view of agent search behavior, but these results made it clear that optimizing content for coding agents was important.
Agent recommendations have a different shape than model responses. When a coding agent suggests a tool, it tends to produce working code with that tool, like an import statement, a config file, or a deployment script. The recommendation is embedded in the output, not just mentioned in prose.
Transcript formats are a mess. And they are getting messier as agent CLI tools ship rapid updates. Building a normalization layer early saved us from constant breakage.
The same brand extraction pipeline works for both models and agents. The hard part is everything upstream: getting the agent to run, capturing what it did, and normalizing it into a structure you can grade.
What’s next
Open sourcing the tool. We're planning to release an OSS version of our system so other teams can track their own AEO evals, both for standard models and coding agents.
Deep dive on methodology. We are working on a follow-up post covering the full AEO eval methodology: prompt design, dual-mode testing (web search vs. training data), query-as-first-class-entity architecture, and Share of Voice metrics.
Scaling agent coverage. Adding more agents as the ecosystem grows and expanding the types of prompts we test (not just "recommend a tool" but full project scaffolding, debugging, etc.).
Read more
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み