WebCMDパラダイムを活用してWebナビゲーションのレイテンシを95%削減し、LLMトークン消費を90%削減し、決定論的でスキーマ検証済みのコマンドラインインターフェースを確立した方法。
1. AIエージェントのWebナビゲーションジレンマ
AI駆動の自動化の現状において、ソフトウェアエージェントはインテリジェンスの収集、議論の監視、システムとの対話のためにWebプラットフォームをナビゲートするタスクを頻繁に割り当てられています。伝統的に、これはヘッドレスブラウザ(Puppeteer、Playwright、Seleniumなど)を使用して達成されています。
ヘッドレスブラウザは非常に柔軟ですが、 substantial な隠れたコストが伴います:
- 高い起動およびレンダリングレイテンシ:Chromiumインスタンスの初期化、DNS解決、CSS/JSアセットの読み込み、DOMのレンダリングにはリクエストあたり3〜5秒かかります。
- 大規模なリソースフットプリント:複数のヘッドレスChromeプロセスを並行して実行するとCPUとメモリ使用量が急増し、スケーラビリティのボトルネックが発生します。
- トークン膨張:生のHTMLやMarkdown変換されたDOM表現を大規模言語モデル(LLM)に送信すると、数千のトークンが消費されます。単一のWebページ表現は、簡単なストーリーリストの場合でも簡単に20KB〜50KB(5,000トークン以上に相当)になります。
- 脆いUIセレクタ:WebスクレイピングはCSSセレクタやXPathクエリに依存しており、プラットフォームのレイアウトが変更されると破損し、エージェントパイプラインがサイレントに失敗したり幻覚を起こしたりする原因となります。
これらの非効率性を解決するため、WebCMDレジストリを活用したHacker News向けの高性能コマンドラインアダプターであるHN CLI(hn-cli)を構築しました。Webインタラクションをブラウザレンダリングから分離することで、AIエージェント向けに構造化され、高速でトークン効率の良いブリッジを提供します。
2. WebCMDパラダイムの紹介
WebCMDは、Webプラットフォーム向けのコマンドラインアダプターを定義できるフレームワークです。通常、WebCMDはヘッドレスブラウザを起動し、ターゲットページでスクリプトを実行することでコマンドを実行します。ただし、WebCMDはStrategy.PUBLICとbrowser: falseと呼ばれるセカンダリ実行戦略をサポートしています。
このように設定した場合:
- アダプターはローカルのNode.js環境で直接実行されます。
- WebCMDはChromiumの起動を完全にスキップし、レンダリングと起動オーバーヘッドを排除します。
- アダプターは公開RESTエンドポイントを介してターゲットサイトと対話し、サブ200msのレイテンシを実現します。
- 出力はデフォルトで構造化JSONとしてフォーマットされ、LLMエージェントがすぐに読み取れるようになります。
graph TD
subgraph Execution flow with WebCMD
Agent[AI Agent or Developer] -->|webcmd hn top| Registry[WebCMD Registry]
Registry -->|Load Adapter| Adapter[HN Adapter]
Adapter -->|Import helpers| Utils[adapters/utils.js]
Utils -->|REST HTTP Get| API[Firebase HN API / Algolia API]
API -->|JSON Response| Utils
Utils -->|Validate Types| Schema[Schema Validator]
Schema -->|Clean Output| Print[Stdout JSON / WebCMD Table]
end
Enter fullscreen mode Exit fullscreen mode
3. Hacker Newsアダプターのアーキテクチャ
hn-cliリポジトリは、CLIコマンド定義、コアユーティリティロジック、テスト、設定スクリプトを分離したクリーンなモジュラー構造に整理されています:
- adapters/:コマンド定義を含みます。各ファイルがサブコマンドを登録します。
- tests/:オフラインテストを実行するための完全モック化されたVitestスイート。
- scripts/:アダプターの登録、サブプロセスの実行検証、リンティング。
最適なAPIの活用
news.ycombinator.comをスクレイピングする代わりに、CLIは2つの公式エンドポイントと直接通信します:
- Firebase Hacker News REST API:トップ/新規/askストーリーのリアルタイムIDとアイテム詳細の取得に最適。
- Algolia Hacker News Search API:テキストの関連性や日付による記事のクエリに最適。
4. hn-cliにおける主要なエンジニアリングパターン
CLIが自律的なエージェント実行に適していることを確実にするため、コードベースは3つの重要なパターンを実装しています:並行性、耐障害性、決定論性。
A. Promise.allによる並行性
Hacker News Firebase APIは個別のアイテムエンドポイントのコレクションとして構造化されています。トップ30のストーリーを取得するには:
- トップ500のストーリーIDのリストを取得(1回のHTTPコール)。
- 最初の30のIDのメタデータを取得(30回の個別HTTPコール)。
これを順次実行すると、恐ろしいレイテンシ(30 × 100ms = 3秒)につながります。代わりに、hn-cliはPromise.allを使用して並行して取得します:
const storyIds = await fetchJson('https://hacker-news.firebaseio.com/v0/topstories.json');
const idsToFetch = storyIds.slice(0, limit);
const fetchedItems = await Promise.all(
idsToFetch.map(id => fetchJson(`https://hacker-news.firebaseio.com/v0/item/${id}.json`).catch(() => null))
);
Enter fullscreen mode Exit fullscreen mode
B. 指数バックオフによる耐障害性
AIエージェントの実行は高価です。単一のネットワークパケットがドロップされたり、API制限に達したりした場合、エージェントは失敗すべきではありません。fetchWithRetry内に指数バックオフを伴う堅牢なリトライループを構築しました:
export async function fetchWithRetry(url, options = {}, retries = 3, backoff = 1000) {
const timeoutMs = options.timeout || 10000;
for (let i = 0; i < retries; i++) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetch(url, { ...options, signal: controller.signal });
clearTimeout(timeoutId);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
clearTimeout(timeoutId);
if (i === retries - 1) {
throw new Error(`Failed to fetch ${url} after ${retries} attempts: ${err.message}`);
}
// Wait: 1s, 2s, 4s...
await new Promise((resolve) => setTimeout(resolve, backoff * Math.pow(2, i)));
}
}
}
Enter fullscreen mode Exit fullscreen mode
C. JSONスキーマ検証による決定論性
AIエージェントは特定の構造のデータを期待します。APIが欠落フィールドを返した場合、エージェントは未定義の動作や幻覚を経験する可能性があります。hn-cliは、データをstdoutに送信する前にvalidateSchema()を使用して厳格なスキーマチェックを強制します:
export function validateSchema(data, schema) {
const items = Array.isArray(data) ? data : [data];
for (const item of items) {
for (const [key, expectedType] of Object.entries(schema)) {
const value = item[key];
if (value === undefined || value === null) {
throw new CommandExecutionError(`Validation Error: Missing required field "${key}" in output schema.`);
}
const actualType = typeof value;
if (!expectedType.split('|').includes(actualType)) {
throw new CommandExecutionError(`Validation Error: Field "${key}" is "${actualType}" but expected "${expectedType}".`);
}
}
}
}
Enter fullscreen mode Exit fullscreen mode
5. 実装コードのウォークスルー
WebCMDアダプターがどのように登録されるかを確認しましょう。以下は、top.jsの簡略化された実装で、メタデータと実行ルーチンの定義方法を示しています。
コマンドの定義
import { cli, Strategy } from '@agentrhq/webcmd/registry';
import { fetchJson, getRelativeTime, handleOutput, validateSchema, validatePositiveInt } from './utils.js';
const STORY_SCHEMA = {
rank: 'number',
title: 'string',
url: 'string',
hn_url: 'string',
points: 'number',
author: 'string',
comments: 'number',
age: 'string'
};
cli({
site: 'hn',
name: 'top',
access: 'read',
description: 'Return the top stories from Hacker News',
domain: 'news.ycombinator.com',
strategy: Strategy.PUBLIC,
browser: false,
defaultFormat: 'json',
args: [
{ name: 'limit', type: 'int', default: 30, help: 'Number of stories to return' }
],
columns: ['rank', 'title', 'url', 'hn_url', 'points', 'author', 'comments', 'age'],
func: async (args) => {
const limit = validatePositiveInt(args.limit ?? 30, 'limit');
// Fetch story IDs
const storyIds = await fetchJson('https://hacker-news.firebaseio.com/v0/topstories.json');
// Slice and fetch details in parallel
const idsToFetch = storyIds.slice(0, limit + 5);
const fetchedItems = await Promise.all(
idsToFetch.map(id => fetchJson(`https://hacker-news.firebaseio.com/v0/item/${id}.json`).catch(() => null))
);
const results = [];
let rank = 1;
for (const item of fetchedItems) {
if (results.length >= limit) break;
if (!item || item.deleted || item.dead || !item.title) continue;
const hnUrl = `https://news.ycombinator.com/item?id=${item.id}`;
results.push({
rank: rank++,
title: item.title,
url: item.url || hnUrl,
hn_url: hnUrl,
points: item.score ?? 0,
author: item.by || '[deleted]',
comments: item.descendants ?? 0,
age: getRelativeTime(item.time)
});
}
validateSchema(results, STORY_SCHEMA);
return handleOutput(results, args);
}
});
Enter fullscreen mode Exit fullscreen mode
6. ベンチマーク:ヘッドレスブラウザ vs WebCMD
news.ycombinator.comを読み込んでスクレイピングするPuppeteerヘッドレスChromeスクレイパーとWebCMD CLI(hn-cli)を比較するパフォーマンスベンチマークを実行しました。
レイテンシと実行速度
| メソッド | 平均レイテンシ(ms) | 速度向上 | オーバーヘッドの詳細 |
|---|---|---|---|
| Puppeteer(Chromium) | 3,850ms | 1.0x | ブラウザバイナリの起動、DNS + TCP、レイアウト/ペイント、ページスクリプトの実行。 |
WebCMD CLI(hn-cli) |
180ms - 320ms | 約15倍 - 20倍 | ネイティブNodeサブプロセス、最適化された並列HTTPフェッチ、ダイレクトネットワークストリーム。 |
トークン消費(コンテキスト効率)
エージェントがWebコンテンツを読み取る場合、すべての文字がLLM APIコストに変換されます。
| エージェントに渡すフォーマット | ペイロードサイズ | 推定LLMトークン | コスト削減 |
|---|---|---|---|
| 生のHTMLページ | 120 KB | 約30,000トークン | 0% |
| Markdown DOM変換 | 22 KB | 約5,500トークン | 81.6% |
クリーンなJSON出力(hn-cli) |
1.8 KB | 約450トークン | 98.5% |
[!TIP]
HTMLタグ、スクリプト、レイアウトディレクティブ、UIクロームを排除することで、LLMには構造化されたキーのみを供給し、コンテキストウィンドウの可用性を最大化し、推論を高速化します。
7. CLIの拡張:カスタムコマンドの追加
hn-cliの強みの1つは、そのシンプルな拡張性です。Hacker Newsの「best」ストーリー(別個のエンドポイント)を取得するコマンドを追加するには:
-
ファイルを作成
adapters/best.js:
import { cli, Strategy } from '@agentrhq/webcmd/registry';
import { fetchJson, getRelativeTime, handleOutput, validateSchema, validatePositiveInt } from './utils.js';
cli({
site: 'hn',
name: 'best',
access: 'read',
description: 'Hacker News best stories',
strategy: Strategy.PUBLIC,
browser: false,
args: [
{ name: 'limit', type: 'int', default: 30 }
],
columns: ['rank', 'title', 'url', 'hn_url', 'points', 'author', 'comments', 'age'],
func: async (args) => {
const limit = validatePositiveInt(args.limit ?? 30, 'limit');
const ids = await fetchJson('https://hacker-news.firebaseio.com/v0/beststories.json');
const fetched = await Promise.all(
ids.slice(0, limit).map(id => fetchJson(`https://hacker-news.firebaseio.com/v0/item/${id}.json`).catch(() => null))
);
// スキーマにマップ、検証、出力...
}
});
Enter fullscreen mode Exit fullscreen mode
-
登録を更新
scripts/install-adapters.jsで、新しいファイルがWebCMDのアクティブアダプターディレクトリにコピーされることを確認します:
const FILES_TO_COPY = ['top.js', 'newest.js', 'ask.js', 'jobs.js', 'item.js', 'search.js', 'best.js', 'utils.js'];
Enter fullscreen mode Exit fullscreen mode
- 更新をデプロイ:
npm run register
Enter fullscreen mode Exit fullscreen mode
コマンドはwebcmd hn best --limit 10でグローバルに利用可能になりました。
8. 結論
AIエージェントがソフトウェアエンジニアリングと自動化の中心的な部分になるにつれ、人間の目だけを対象に設計されたWebサイトの構築から、エージェントフレンドリーなAPIインターフェースの公開への移行が必要です。
WebCMDパラダイムを利用してhn-cliを構築することで、ヘッドレスブラウザのパフォーマンスとリソース税を完全に回避できることを実証しました。ネイティブnodeアダプター、並行APIフェッチ、堅牢なスキーマチェック、構造化JSON結果でインターフェースを構築することで、以下を実現します:
- 超高速な実行(サブ200ms)
- 無視できるリソース使用量
- LLMの大幅なトークン節約
- 視覚レイアウトの更新で決して破損しない決定論的結果
AIエージェント向けのWebインタラクションの未来は、人間のカーソルクリックを模倣することではなく、開発者フレンドリーなコマンドラインアダプターを標準化することにあります。
Antigravity Agentによって開発。ソースコードはMITライセンスの下で次のリポジトリで利用可能: https://github.com/scha54/hn-cli
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.