エージェント型 Web プロトコル (AWP)
ADK-Rust は、Web サイトやサービスを AI エージェントから利用可能にするための Agentic Web Protocol (AWP) の型と Axum 統合を提供します。実装は 2 つのクレートにまたがっています: awp-types(純粋なプロトコル型)と adk-awp(ルート、ミドルウェア、サービスインターフェース)です。アプリケーションは、エージェントのディスパッチ、認証、認可、そして永続的な webhook 配信を提供します。
概要
AWP は、任意の Web サイトがその機能、ポリシー、ビジネスコンテキストを機械可読な形式で宣言できるようにします。AI エージェントはこれらの機能を発見し、プロトコルバージョンを交渉し、イベントを購読し、型付きの A2A メッセージを通じてやり取りできます。adk-awp は、その HTTP 境界でボディ制限とレート制限を強制します。アプリケーションハンドラーは、ID と機能の認可を強制します。
AWP を使う場合:
- AI エージェントにサービスをプログラム的に発見・操作してほしい
- 信頼レベルのメタデータと、アプリケーションによるアクセス制御のためのフックが必要
- 同じエンドポイントで人間の訪問者と AI エージェントの両方にサービス प्रदानしたい
- イベント購読と HMAC-SHA256 署名プリミティブが必要
- サービス監視のためのヘルス状態マシンが必要
アーキテクチャ
AWP リクエストフロー
アプリケーションレイアウト
┌─────────────────────────────────────────────────┐
│ Your Application │
│ │
│ ┌──────────────┐ ┌──────────────────────────┐ │
│ │ LLM Agent │ │ awp_routes(state) │ │
│ │ (adk-agent) │ │ ├ /.well-known/awp.json │ │
│ │ │ │ ├ /awp/manifest │ │
│ │ Instructions│ │ ├ /awp/health │ │
│ │ derived from│ │ └ /awp/a2a │ │
│ │ business. │ │ auth + management routes│ │
│ │ toml │ │ │ │
│ └──────────────┘ └──────────────────────────┘ │
│ ▲ ▲ │
│ │ │ │
│ ┌────┴──────────────────────┴────┐ │
│ │ BusinessContextLoader │ │
│ │ (business.toml + ArcSwap) │ │
│ └────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
クレート
| クレート | 目的 | 依存関係 |
|---|---|---|
awp-types | プロトコル型(enum、struct、error) | 依存関係はゼロ、adk-* — serde、uuid、chrono、thiserrorのみ |
adk-awp | ルート、ミドルウェア、サービスインターフェース、およびインメモリ実装 | awp-types、adk-core、axum 0.8、tokio、dashmap |
この分割により、どの Rust プロジェクトでも awp-types に依存でき、ADK ツリーを取り込まずに済みます。
クイックスタート
1. business.toml を作成する
site_name = "My Shop"
site_description = "An online store powered by AWP"
domain = "myshop.example.com"
contact = "hello@myshop.example.com"
[business]
country = "US"
currency = "USD"
languages = ["en"]
[brand_voice]
tone = "friendly and helpful"
greeting = "Welcome! How can I help?"
[[capabilities]]
name = "browse_products"
description = "Browse the product catalog"
endpoint = "/api/products"
method = "GET"
access_level = "anonymous"
[[capabilities]]
name = "place_order"
description = "Place an order"
endpoint = "/api/orders"
method = "POST"
access_level = "known"
[[products]]
sku = "WIDGET-001"
name = "Standard Widget"
price = 1999
inventory = 500
tags = ["widget"]
[[policies]]
name = "privacy"
description = "Minimal data collection, no tracking."
policy_type = "privacy"
[payments]
providers = ["stripe"]
auto_approve_threshold = 5000
[support]
escalation_contacts = ["support@myshop.example.com"]
hours = "Mon-Fri 9-5 EST"
2. AWP ルートを読み込み、提供する
use std::sync::Arc;
use adk_awp::{AwpA2aHandler, AwpState, BusinessContextLoader, awp_routes};
use async_trait::async_trait;
use awp_types::AwpError;
use axum::http::{HeaderMap, header};
use serde_json::{Value, json};
struct ApplicationA2a {
bearer_token: Arc<str>,
}
#[async_trait]
impl AwpA2aHandler for ApplicationA2a {
async fn handle(&self, headers: HeaderMap, message: Value) -> Result<Value, AwpError> {
let expected = format!("Bearer {}", self.bearer_token);
let authorized = headers
.get(header::AUTHORIZATION)
.and_then(|value| value.to_str().ok())
.is_some_and(|value| value == expected);
if !authorized {
return Err(AwpError::Unauthorized("invalid A2A credential".to_string()));
}
// Authorize the requested capability and dispatch to the application agent.
Ok(json!({ "status": "processed", "messageId": message["id"] }))
}
}
let loader = BusinessContextLoader::from_file("business.toml".as_ref())?;
let a2a_token: Arc<str> = std::env::var("AWP_A2A_TOKEN")?.into();
let state = AwpState::builder(loader.context_ref())
.a2a_handler(Arc::new(ApplicationA2a { bearer_token: a2a_token }))
.build();
let app = axum::Router::new()
.merge(awp_routes(state))
.merge(your_custom_routes);
let listener = tokio::net::TcpListener::bind("127.0.0.1:3456").await?;
axum::serve(
listener,
app.into_make_service_with_connect_info::<std::net::SocketAddr>(),
)
.await?;
これにより、バージョンネゴシエーション、レート
制限、および 64 KiB A2A の本文制限付きで、4 つの公開 AWP エンドポイントが登録されます。AwpA2aHandler がない場合、
POST /awp/a2a は 503 を返し、ディスパッチされていない作業を決して受理しません。ConnectInfo は、匿名のレート制限バケットを分離するために使用されるピアアドレスを提供します。これがない場合、未知の呼び出し元は意図的に 1 つのバケットを共有します。
公開 AWP エンドポイント
| メソッド | パス | 説明 |
|---|---|---|
| GET | /.well-known/awp.json | ディスカバリードキュメント — エージェントのエントリーポイント |
| GET | /awp/manifest | JSON-LD 機能マニフェスト |
| GET | /awp/health | ヘルス状態(Healthy/Degrading/Degraded) |
| POST | /awp/a2a | アプリケーション提供のA2Aディスパッチ |
認証済み管理エンドポイント
awp_management_routes() は、サブスクリプション管理を個別に、かつ
認証レイヤーなしで返します。これをマージする前に、アプリケーションの認証ミドルウェアを適用してください:
| メソッド | パス | 説明 |
|---|---|---|
| POST | /awp/events/subscribe | webhookサブスクリプションを作成する |
| GET | /awp/events/subscriptions | すべてのサブスクリプションを一覧表示する |
| DELETE | /awp/events/subscriptions/{id} | サブスクリプションを削除する |
ディスカバリードキュメント
/.well-known/awp.json のディスカバリードキュメントは、あなたの business.toml から自動生成されます。
{
"version": { "major": 1, "minor": 0 },
"siteName": "My Shop",
"siteDescription": "An online store powered by AWP",
"capabilityManifestUrl": "https://myshop.example.com/awp/manifest",
"a2aEndpointUrl": "https://myshop.example.com/awp/a2a",
"eventsEndpointUrl": "https://myshop.example.com/awp/events/subscribe",
"healthEndpointUrl": "https://myshop.example.com/awp/health",
"supportedTrustLevels": ["anonymous"]
}
キャパビリティマニフェスト
/awp/manifest のマニフェストは JSON-LD 形式を使用します。
{
"@context": "https://schema.org",
"@type": "WebAPI",
"name": "My Shop",
"description": "An online store powered by AWP",
"capabilities": [
{
"name": "browse_products",
"description": "Browse the product catalog",
"endpoint": "/api/products",
"method": "GET"
}
]
}
信頼レベル
AWP は、アクセス権が段階的に増える 4 つの信頼レベルを使用します。
| レベル | 判別子 | 割り当て方法 |
|---|---|---|
Anonymous | 0 | 認証情報なし |
Known | 1 | 有効な API キーまたは JWT |
Partner | 2 | JWT と partner のスコープ |
Internal | 3 | JWT と internal のスコープ |
信頼レベルは順序付けられています: Anonymous < Known < Partner < Internal。business.toml の各機能は、その最小 access_level を宣言します。
DefaultTrustAssigner は各リクエストを Anonymous として分類します。Bearer または API
キーのヘッダーは、アプリケーションの検証器がそれを検証するまで信頼されません。したがって、より高い
信頼レベルにはカスタムの割り当て担当者が必要です。
その割り当て担当者とともに .supported_trust_levels(...) を設定し、検出が
デプロイメントで検証可能なレベルのみを公開するようにします。
カスタム信頼割り当て
カスタムロジックのために TrustLevelAssigner trait を実装します:
use std::sync::Arc;
use adk_awp::TrustLevelAssigner;
use async_trait::async_trait;
use awp_types::TrustLevel;
use axum::http::{HeaderMap, header};
struct MyTrustAssigner {
bearer_token: Arc<str>,
}
#[async_trait]
impl TrustLevelAssigner for MyTrustAssigner {
async fn assign(&self, headers: &HeaderMap) -> TrustLevel {
let expected = format!("Bearer {}", self.bearer_token);
if headers
.get(header::AUTHORIZATION)
.and_then(|value| value.to_str().ok())
.is_some_and(|value| value == expected)
{
TrustLevel::Known
} else {
TrustLevel::Anonymous
}
}
}
Partner または Internal を割り当てる際には、アプリケーションの他の部分と同じ、検証済みのアイデンティティおよびスコープのソースを使用してください。
レート制限
組み込みの InMemoryRateLimiter は、信頼レベルごとの制限を持つスライディングウィンドウアルゴリズムを使用します:
| 信頼レベル | デフォルト制限 |
|---|---|
| 匿名 | 30 リクエスト/分 |
| 既知 | 120 リクエスト/分 |
| パートナー | 600リクエスト/分 |
| 内部 | 無制限 |
拒否されたリクエストは、HTTP 429 と Retry-After ヘッダーを受け取ります。
カスタム制限
use std::collections::HashMap;
use awp_types::TrustLevel;
use adk_awp::{InMemoryRateLimiter, RateLimitConfig};
let mut limits = HashMap::new();
limits.insert(TrustLevel::Anonymous, RateLimitConfig {
max_requests: 10,
window_secs: 60,
});
limits.insert(TrustLevel::Known, RateLimitConfig {
max_requests: 100,
window_secs: 60,
});
let limiter = InMemoryRateLimiter::with_config(limits);
バージョンネゴシエーション
すべての AWP ルートには、バージョンネゴシエーションのミドルウェアが含まれます。
- クライアントは
AWP-Version: 1.1ヘッダーを送信します(任意 — デフォルトは現在のバージョン) - サーバーはメジャーバージョンの互換性をチェックします
- 互換性のあるリクエストは処理を続行し、互換性のないリクエストは HTTP 406 を受け取ります
- 形式不正なバージョン値は HTTP 400 を受け取ります
- レスポンスには
AWP-Version: 1.0ヘッダーが含まれます
イベント購読
購読管理は特権的な領域です。これらのリクエストを受け付ける前に、認証の背後に awp_management_routes() を配置してください。コールバック URLs は絶対 HTTPS URLs でなければならず、署名シークレットは少なくとも 32 バイトを含む必要があります。
# Subscribe
curl -X POST http://localhost:3456/awp/events/subscribe \
-H "Authorization: Bearer $AWP_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"subscriber": "my-agent",
"callbackUrl": "https://my-agent.example/webhook",
"eventTypes": ["health.changed"],
"secret": "replace-with-at-least-32-random-bytes"
}'
# List subscriptions
curl -H "Authorization: Bearer $AWP_ADMIN_TOKEN" \
http://localhost:3456/awp/events/subscriptions
InMemoryEventSubscriptionService は一致した配信に署名して記録しますが、ネットワーク I/O は行いません。本番アプリケーションでは、宛先検証、永続的なキュー、上限付き再試行、および独自の HTTP クライアントを用いて EventSubscriptionService を実装します。
HTTP の配信実装は、HMAC-SHA256 署名を含む X-AWP-Signature ヘッダーを運ぶことができます。
X-AWP-Signature: sha256=<hex_digest>
署名は adk_awp::verify_signature(payload, secret, signature) で検証します。
ヘルス状態マシン
ヘルスエンドポイントは、厳密に検証された遷移でサービス状態を追跡します。
Healthy → Degrading → Degraded
↑ │ │
└─────────┘ │
└─────────────────────┘
状態変更は、すべての一致するサブスクライバーに health.changed イベントを発行します。
use adk_awp::HealthStateMachine;
// Transition to degrading
health.report_degrading("database latency high").await?;
// Transition to degraded
health.report_degraded("database unreachable").await?;
// Recover
health.report_healthy().await?;
無効な遷移(例: Healthy → Degraded)はエラーを返します。
同意ストレージ
AWP には、同意ストレージのインターフェースが含まれます。規制順守には、アプリケーション固有の通知、法的根拠、保持、アクセス制御、および削除ポリシーも必要です。ストレージ実装を選択しても、コンプライアンスが成立するわけではありません。
use adk_awp::InMemoryConsentService;
let consent = InMemoryConsentService::new();
// Capture consent
consent.capture_consent("visitor-123", "analytics").await?;
// Check consent
let has_consent = consent.check_consent("visitor-123", "analytics").await?;
// Revoke consent
consent.revoke_consent("visitor-123", "analytics").await?;
リクエスター型の検出
AWP は、リクエストが人間からか AI エージェントからかを検出します。
X-AWP-Channel: agentヘッダー → Agent(明示的な上書き)Accept: application/json+ エージェントの User-Agent パターン → Agent- それ以外 → Human
エージェントの User-Agent パターン: bot, crawler, spider, agent, gpt, claude, gemini, perplexity, anthropic, openai。
use adk_awp::detect_requester_type;
use axum::http::HeaderMap;
let mut headers = HeaderMap::new();
headers.insert("X-AWP-Channel", "agent".parse().unwrap());
let requester = detect_requester_type(&headers);
// RequesterType::Agent
AWP メッセージタイプ
汎用の A2A メッセージに加えて、AWP はエージェントルーティングのための型付きメッセージカテゴリを定義します。
| 種類 | 説明 |
|---|---|
VisitorIntentSignal | 購入またはサービスの意図 |
ContentGapSignal | 欠落または古いコンテンツが検出されました |
PaymentIntent | 支払いライフサイクルメッセージ |
SupportEscalation | 人間のサポートへのエスカレーション |
ReviewSignal | プラットフォームからのレビューまたはフィードバック |
OperationsProposal | 在庫、スケジューリングの提案 |
InvokeCapability | 宣言された機能を呼び出す |
RenderUi | UI レンダリングを要求する |
OutboundTrigger | 能動的なアウトバウンドメッセージ |
use awp_types::{AwpMessageType, AwpTypedMessage};
let msg = AwpTypedMessage {
id: uuid::Uuid::now_v7(),
sender: "visitor-agent".to_string(),
recipient: "payment-agent".to_string(),
awp_type: AwpMessageType::PaymentIntent,
timestamp: chrono::Utc::now(),
payload: serde_json::json!({"sku": "WIDGET-001", "amount": 2500}),
};
Payment Intents
AWP は、所有者ポリシー駆動の支払いのための簡略化された支払いライフサイクルを定義します。
Draft → PendingApproval → Approved → Executing → Settled
→ Rejected
→ Cancelled
PaymentPolicy は、自動承認するか、所有者の承認を要求するかを評価します。
use awp_types::{PaymentPolicy, TrustLevel};
let policy = PaymentPolicy::default(); // $50 auto-approve, $500 require approval
let decision = policy.evaluate(2500, TrustLevel::Known);
// PaymentPolicyDecision::AutoApprove (amount $25 <= $50 threshold)
let decision = policy.evaluate(60_000, TrustLevel::Partner);
// PaymentPolicyDecision::RequireApproval (amount $600 > $500 threshold)
business.toml Schema
完全なスキーマは、豊富なビジネス設定をサポートします:
| セクション | フィールド | 必須 |
|---|---|---|
| (root) | site_name, site_description, domain, contact | はい(連絡先を除く) |
[business] | name, country, languages, currency, timezone | いいえ |
[brand_voice] | tone, greeting, escalation_message | いいえ |
[[products]] | sku, name, price, inventory, tags, description | いいえ |
[[capabilities]] | name, description, endpoint, method, access_level | はい |
[[policies]] | name, description, policy_type | はい |
[channels] | whatsapp, email, website, sms | いいえ |
[payments] | providers, auto_approve_threshold, require_approval_threshold | いいえ |
[support] | escalation_contacts, hours, sla | いいえ |
[content] | topics, auto_draft, publish_delay | いいえ |
[reviews] | platforms, auto_respond_threshold | いいえ |
[outreach] | follow_up_delay, require_consent | いいえ |
すべての拡張セクションは任意です。既存の最小限のbusiness.tomlファイルは引き続き動作します。
ホットリロード
BusinessContextLoader は ArcSwap を介したホットリロードをサポートしています:
let loader = BusinessContextLoader::from_file("business.toml".as_ref())?;
loader.watch("business.toml".into()).await?;
// Changes to business.toml are picked up automatically every 5 seconds
例の実行
完全な AWP エージェントの例が含まれています:
cd examples/awp_agent
cp .env.example .env # add your GOOGLE_API_KEY
cargo run
この例では:
- 製品、ポリシー、ブランドボイスを含む
business.tomlを読み込みます - ビジネスコンテキストから導出された指示を持つ LLM エージェントを作成します
- そのエージェントに認証済みの A2A ディスパッチをインストールします
- 別のデモ用認証情報の背後に管理ルートをマウントします
- すべてのエンドポイントを実行し、プロトコル検証を出力します
ベストプラクティス
- 最小限の
business.tomlから始める — 必要なのはsite_name、site_description、domain、機能、ポリシーのみです - 機能の認可を強制する —
access_levelはマニフェストのメタデータです。アプリケーションハンドラーがこれを強制する必要があります - 本番環境でホットリロードを有効にする — ゼロダウンタイムの設定更新のために
loader.watch()を呼び出します - カスタムの
TrustLevelAssignerを実装する — デフォルトは意図的にAnonymousのみを割り当てます - 管理ルートを認証する — 保護されていないルーターからサブスクリプション CRUD を決して公開しないでください
- 実際の A2A ディスパッチをインストールする — フェイルクローズのデフォルトは
503を返します - 耐久性のあるイベント配信を使用する — 宛先ポリシー、キューイング、制限付き再試行を実装します
- Webhook署名を検証する — 受信Webhookで
X-AWP-Signatureを検証します
関連項目
- A2A Protocol — エージェント間通信(AWP と補完関係)
- Server Deployment — HTTP サーバーとしてエージェントを実行する
- Access Control — ロールベースの権限
前へ: ← A2A Protocol | 次へ: Evaluation →