Agentic Web Protocol (AWP)
ADK-Rust bietet Agentic Web Protocol (AWP)-Typen und Axum-Integration, um Websites und Dienste für AI-Agenten zugänglich zu machen. Die Implementierung erstreckt sich über zwei Crates: awp-types (reine Protokolltypen) und adk-awp (Routen, Middleware und Service-Schnittstellen). Anwendungen stellen Agenten-Dispatch, Authentifizierung, Autorisierung und dauerhafte Webhook-Zustellung bereit.
Übersicht
AWP ermöglicht es jeder Website, ihre Fähigkeiten, Richtlinien und ihren Geschäftskontext in einem maschinenlesbaren Format zu deklarieren. AI-Agenten können diese Fähigkeiten entdecken, Protokollversionen aushandeln, Ereignisse abonnieren und über typisierte A2A-Nachrichten interagieren. adk-awp erzwingt an seiner HTTP-Grenze Body- und Ratenlimits; Anwendungs-Handler erzwingen Identitäts- und Fähigkeitsautorisierung.
Verwenden Sie AWP, wenn:
- Sie möchten, dass AI-Agenten Ihren Dienst programmatisch entdecken und mit ihm interagieren
- Sie Metadaten zum Vertrauensniveau und einen Hook für anwendungsseitig durchgesetzte Zugriffskontrolle benötigen
- Sie sowohl menschliche Besucher als auch AI-Agenten über dieselben Endpunkte bedienen möchten
- Sie Ereignisabonnements und HMAC-SHA256-Signaturprimitive benötigen
- Sie einen Zustandsautomaten für die Dienstüberwachung möchten
Architektur
AWP Request-Flow
Anwendungsaufbau
┌─────────────────────────────────────────────────┐
│ 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) │ │
│ └────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
Pakete
| Crate | Zweck | Abhängigkeiten |
|---|---|---|
awp-types | Protokolltypen (Enums, Structs, Fehler) | Keine adk-*-Abhängigkeiten — nur serde, uuid, chrono, thiserror |
adk-awp | Routen, Middleware, Service-Interfaces und In-Memory-Implementierungen | awp-types, adk-core, axum 0.8, tokio, dashmap |
Die Aufspaltung bedeutet, dass jedes Rust-Projekt von awp-types abhängen kann, ohne den ADK-Baum einzubeziehen.
Schnellstart
1. Erstelle ein 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. Lade und serviere AWP-Routen
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?;
Dies registriert die vier öffentlichen AWP-Endpunkte mit Versionsaushandlung, Ratenbegrenzung und einem Limit von 64 KiB A2A. Ohne einen AwpA2aHandler gibt POST /awp/a2a 503 zurück und bestätigt niemals Arbeit, die nicht weitergeleitet wurde. ConnectInfo stellt die Peer-Adresse bereit, die verwendet wird, um anonyme Ratenbegrenzungs-Buckets zu isolieren; ohne sie teilen unbekannte Aufrufer absichtlich einen Bucket.
Öffentliche AWP-Endpunkte
| Methode | Pfad | Beschreibung |
|---|---|---|
| GET | /.well-known/awp.json | Discovery-Dokument — Einstiegspunkt für Agenten |
| GET | /awp/manifest | JSON-LD Fähigkeitsmanifest |
| GET | /awp/health | Gesundheitsstatus (Healthy/Degrading/Degraded) |
| POST | /awp/a2a | Von der Anwendung bereitgestellter A2A-Dispatch |
Authentifizierte Verwaltungsendpunkte
awp_management_routes() gibt die Abonnementverwaltung separat und
ohne Authentifizierungsschicht zurück. Wenden Sie die Auth-Middleware der Anwendung an,
bevor Sie sie zusammenführen:
| Methode | Pfad | Beschreibung |
|---|---|---|
| POST | /awp/events/subscribe | Webhook-Abonnement erstellen |
| GET | /awp/events/subscriptions | Alle Abonnements auflisten |
| DELETE | /awp/events/subscriptions/{id} | Ein Abonnement löschen |
Erkennungsdokument
Das Erkennungsdokument unter /.well-known/awp.json wird automatisch aus deinem business.toml generiert:
{
"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"]
}
Fähigkeitsmanifest
Das Manifest unter /awp/manifest verwendet das JSON-LD-Format:
{
"@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"
}
]
}
Vertrauensstufen
AWP verwendet vier Vertrauensstufen mit zunehmendem Zugriff:
| Ebene | Diskriminante | Wie zugewiesen |
|---|---|---|
Anonymous | 0 | Keine Anmeldedaten |
Known | 1 | Gültiger API-Schlüssel oder JWT |
Partner | 2 | JWT mit partner-Bereich |
Internal | 3 | JWT mit internal-Bereich |
Vertrauensstufen sind geordnet: Anonymous < Known < Partner < Internal. Jede Fähigkeit in business.toml deklariert ihre minimale access_level.
DefaultTrustAssigner klassifiziert jede Anfrage als Anonymous. Ein Bearer- oder API-Key-Header wird erst dann als vertrauenswürdig angesehen, wenn ein Anwendungs-Verifizierer ihn validiert. Höhere Vertrauensstufen erfordern daher einen benutzerdefinierten Zuweiser.
Konfigurieren Sie .supported_trust_levels(...) zusammen mit diesem Zuweiser, damit die Erkennung nur Stufen bekannt gibt, die die Bereitstellung verifizieren kann.
Benutzerdefinierte Vertrauenszuweisung
Implementieren Sie das TrustLevelAssigner-Trait für benutzerdefinierte Logik:
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
}
}
}
Verwenden Sie dieselbe verifizierte Identitäts- und Scope-Quelle wie der Rest der Anwendung, wenn Sie Partner oder Internal zuweisen.
Ratenbegrenzung
Die integrierte InMemoryRateLimiter verwendet einen Sliding-Window-Algorithmus mit Limits pro Vertrauensstufe:
| Vertrauensstufe | Standardlimit |
|---|---|
| Anonym | 30 Anfragen/Minute |
| Bekannt | 120 Anfragen/Minute |
| Partner | 600 Anfragen/Minute |
| Intern | Unbegrenzt |
Abgelehnte Anfragen erhalten HTTP 429 mit einem Retry-After-Header.
Benutzerdefinierte Limits
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);
Versionsverhandlung
Alle AWP-Routen enthalten Middleware für die Versionsverhandlung:
- Clients senden den
AWP-Version: 1.1-Header (optional — standardmäßig die aktuelle Version) - Der Server prüft die Kompatibilität der Hauptversion
- Kompatible Anfragen werden weitergeleitet; inkompatible Anfragen erhalten HTTP 406
- Ungültige Versionswerte erhalten HTTP 400
- Die Antwort enthält den
AWP-Version: 1.0-Header
Ereignis-Abonnements
Die Verwaltung von Abonnements ist ein privilegierter Bereich. Mount
awp_management_routes() hinter der Authentifizierung, bevor diese
Anfragen akzeptiert werden. Die Callback-URLs muss absolut HTTPS URLs sein, und Signierungsgeheimnisse müssen
mindestens 32 Bytes enthalten:
# 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 signiert und protokolliert passende Zustellungen, führt aber
keine Netzwerk-E/A aus. Produktionsanwendungen implementieren
EventSubscriptionService mit Zielvalidierung, einer dauerhaften Warteschlange,
begrenzten Wiederholungen und ihrem HTTP-Client.
Eine HTTP-Zustellungsimplementierung kann einen X-AWP-Signature-Header mit der
HMAC-SHA256-Signatur tragen:
X-AWP-Signature: sha256=<hex_digest>
Überprüfen Sie Signaturen mit adk_awp::verify_signature(payload, secret, signature).
Zustandsmaschine für den Gesundheitsstatus
Der Gesundheitsendpunkt verfolgt den Dienststatus mit streng validierten Übergängen:
Healthy → Degrading → Degraded
↑ │ │
└─────────┘ │
└─────────────────────┘
Statusänderungen lösen health.changed-Ereignisse an alle passenden Abonnenten aus.
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?;
Ungültige Übergänge (z. B. Healthy → Degraded) geben einen Fehler zurück.
Einwilligungsspeicherung
AWP enthält eine Schnittstelle für die Einwilligungsspeicherung. Die Einhaltung gesetzlicher Vorgaben erfordert außerdem anwendungsspezifische Hinweise, rechtliche Grundlage, Aufbewahrung, Zugriffskontrollen und Löschrichtlinien; die Auswahl einer Speicherimplementierung begründet noch keine Compliance:
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?;
Erkennung des Anfragetypers
AWP erkennt, ob eine Anfrage von einem Menschen oder einem KI-Agenten stammt:
X-AWP-Channel: agent-Header → Agent (explizite Überschreibung)Accept: application/json+ Agent-User-Agent-Muster → Agent- Andernfalls → Mensch
Agent-User-Agent-Muster: 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-Nachrichtentypen
Über generische A2A-Nachrichten hinaus definiert AWP kategorisierte Nachrichtentypen für das Agenten-Routing:
| Typ | Beschreibung |
|---|---|
VisitorIntentSignal | Kauf- oder Dienstleistungsabsicht |
ContentGapSignal | Fehlender oder veralteter Inhalt erkannt |
PaymentIntent | Nachricht zum Zahlungslebenszyklus |
SupportEscalation | Weiterleitung an den menschlichen Support |
ReviewSignal | Bewertung oder Feedback von einer Plattform |
OperationsProposal | Bestand, Terminplanungsvorschlag |
InvokeCapability | Eine deklarierte Fähigkeit aufrufen |
RenderUi | UI-Rendering anfordern |
OutboundTrigger | Proaktive ausgehende Nachricht |
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}),
};
Zahlungsabsichten
AWP definiert einen vereinfachten Zahlungslebenszyklus für eigentümerrichtliniengesteuerte Zahlungen:
Draft → PendingApproval → Approved → Executing → Settled
→ Rejected
→ Cancelled
Das PaymentPolicy bewertet, ob automatisch genehmigt oder eine Genehmigung des Eigentümers erforderlich ist:
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)
Schema von business.toml
Das vollständige Schema unterstützt eine umfangreiche Geschäftskonfiguration:
| Abschnitt | Felder | Erforderlich |
|---|---|---|
| (Wurzel) | site_name, site_description, domain, contact | Ja (außer Kontakt) |
[business] | name, country, languages, currency, timezone | Nein |
[brand_voice] | tone, greeting, escalation_message | Nein |
[[products]] | sku, name, price, inventory, tags, description | Nein |
[[capabilities]] | name, description, endpoint, method, access_level | Ja |
[[policies]] | name, description, policy_type | Ja |
[channels] | whatsapp, email, website, sms | Nein |
[payments] | providers, auto_approve_threshold, require_approval_threshold | Nein |
[support] | escalation_contacts, hours, sla | Nein |
[content] | topics, auto_draft, publish_delay | Nein |
[reviews] | platforms, auto_respond_threshold | Nein |
[outreach] | follow_up_delay, require_consent | Nein |
Alle erweiterten Abschnitte sind optional — vorhandene minimale business.toml-Dateien funktionieren weiterhin.
Heißes Neuladen
Das BusinessContextLoader unterstützt Hot-Reload über 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
Beispiel ausführen
Ein vollständiges AWP-Agentenbeispiel ist enthalten:
cd examples/awp_agent
cp .env.example .env # add your GOOGLE_API_KEY
cargo run
Das Beispiel:
- Lädt
business.tomlmit Produkten, Richtlinien und Markenstimme - Erstellt einen LLM-Agenten mit Anweisungen, die aus dem Geschäftskontext abgeleitet sind
- Installiert authentifizierte A2A-Weiterleitung zu diesem Agenten
- Bindet Verwaltungsrouten hinter einem separaten Demo-Anmeldedatensatz ein
- Führt jeden Endpunkt aus und gibt die Protokollverifizierung aus
Bewährte Vorgehensweisen
- Mit einem minimalen
business.tomlbeginnen — nursite_name,site_description,domain, Fähigkeiten und Richtlinien sind erforderlich - Fähigkeitsautorisierung erzwingen —
access_levelsind Manifest-Metadaten; der Anwendungshandler muss sie durchsetzen - Hot-Reload in der Produktion aktivieren — rufe
loader.watch()für Konfigurationsaktualisierungen ohne Ausfallzeit auf - Benutzerdefiniertes
TrustLevelAssignerimplementieren — die Standardeinstellung weist absichtlich nurAnonymouszu - Verwaltungsrouten authentifizieren — Subscription-CRUD niemals von einem ungeschützten Router aus bereitstellen
- Echtes A2A-Dispatch installieren — die fail-closed-Standardeinstellung gibt
503zurück - Zuverlässige Ereigniszustellung verwenden — Zielrichtlinie, Warteschlangierung und begrenzte Wiederholungen implementieren
- Webhook-Signaturen überprüfen —
X-AWP-Signatureauf eingehenden Webhooks validieren
Verwandte Themen
- A2A Protocol — Agent-zu-Agent-Kommunikation (ergänzend zu AWP)
- Server Deployment — Agenten als HTTP-Server ausführen
- Access Control — Rollenbasierte Berechtigungen
Vorherig: ← A2A Protocol | Weiter: Bewertung →