Entwicklungsrichtlinien
Dieses Dokument enthält umfassende Richtlinien für Entwickler, die zu ADK-Rust beitragen. Die Einhaltung dieser Standards gewährleistet Codequalität, Konsistenz und Wartbarkeit im gesamten Projekt.
Inhaltsverzeichnis
- Erste Schritte
- Projektstruktur
- Codestil
- Fehlerbehandlung
- Async-Muster
- Testen
- Dokumentation
- Pull-Request-Prozess
- Häufige Aufgaben
Erste Schritte
Voraussetzungen
- Rust: 1.75 oder höher (Überprüfung mit
rustc --version) - Cargo: Neueste stabile Version
- Git: Für die Versionskontrolle
Umgebung einrichten
# Clone the repository
git clone https://github.com/zavora-ai/adk-rust.git
cd adk-rust
# Build the project
cargo build
# Run all tests
cargo test --all
# Check for lints
cargo clippy --all-targets --all-features
# Format code
cargo fmt --all
Umgebungsvariablen
Zum Ausführen von Beispielen und Tests, die API keys erfordern:
# Gemini (default provider)
export GOOGLE_API_KEY="your-api-key"
# OpenAI (optional)
export OPENAI_API_KEY="your-api-key"
# Anthropic (optional)
export ANTHROPIC_API_KEY="your-api-key"
Projektstruktur
ADK-Rust ist als Cargo workspace mit mehreren crates organisiert:
adk-rust/
├── adk-core/ # Foundational traits and types (Agent, Tool, Llm, Event)
├── adk-telemetry/ # OpenTelemetry integration
├── adk-model/ # LLM providers (Gemini, OpenAI, Anthropic)
├── adk-tool/ # Tool system (FunctionTool, MCP, AgentTool)
├── adk-session/ # Session management (in-memory, SQLite)
├── adk-artifact/ # Binary artifact storage
├── adk-memory/ # Long-term memory with search
├── adk-agent/ # Agent implementations (LlmAgent, workflow agents)
├── adk-runner/ # Execution runtime
├── adk-server/ # REST API and A2A protocol
├── adk-cli/ # Command-line launcher
├── adk-realtime/ # Voice/audio streaming agents
├── adk-graph/ # LangGraph-style workflows
├── adk-browser/ # Browser automation tools
├── adk-eval/ # Agent evaluation framework
├── adk-rust/ # Umbrella crate (re-exports all)
└── examples/ # Working examples
Crate-Abhängigkeiten
Crates müssen in der Reihenfolge ihrer Abhängigkeiten veröffentlicht werden:
adk-core(keine internen Abhängigkeiten)adk-telemetryadk-modeladk-tooladk-sessionadk-artifactadk-memoryadk-agentadk-runneradk-serveradk-cliadk-realtimeadk-graphadk-browseradk-evaladk-rust(umbrella)
Code-Stil
Allgemeine Prinzipien
- Klarheit vor Cleverness: Schreiben Sie Code, der leicht zu lesen und zu verstehen ist.
- Explizit vor Implizit: Bevorzugen Sie explizite Typen und Fehlerbehandlung.
- Kleine Funktionen: Halten Sie Funktionen fokussiert und nach Möglichkeit unter 50 Zeilen.
- Bedeutungsvolle Namen: Verwenden Sie beschreibende Variablen- und Funktionsnamen.
Formatierung
Verwenden Sie rustfmt mit Standardeinstellungen:
cargo fmt --all
Die CI-Pipeline erzwingt die Formatierung. Führen Sie cargo fmt immer vor dem Commit aus.
Namenskonventionen
| Typ | Konvention | Beispiel |
|---|---|---|
| Crates | adk-* (kebab-case) | adk-core, adk-agent |
| Module | snake_case | llm_agent, function_tool |
| Typen/Traits | PascalCase | LlmAgent, ToolContext |
| Funktionen | snake_case | execute_tool, run_agent |
| Konstanten | SCREAMING_SNAKE_CASE | KEY_PREFIX_APP |
| Typparameter | Einzelner Großbuchstabe oder PascalCase | T, State |
Importe
Organisieren Sie Importe in dieser Reihenfolge:
// 1. Standardbibliothek
use std::collections::HashMap;
use std::sync::Arc;
// 2. Externe Crates
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
// 3. Interne Crates (adk-*)
use adk_core::{Agent, Event, Result};
// 4. Lokale Module
use crate::config::Config;
use super::utils;
Clippy
Der gesamte Code muss Clippy ohne Warnungen bestehen:
cargo clippy --all-targets --all-features
Beheben Sie Clippy-Warnungen, anstatt sie zu unterdrücken. Wenn eine Unterdrückung notwendig ist, dokumentieren Sie den Grund:
#[allow(clippy::too_many_arguments)]
// Das Builder-Muster erfordert viele Parameter; eine Umgestaltung würde die Benutzerfreundlichkeit beeinträchtigen.
fn complex_builder(...) { }
Fehlerbehandlung
Verwendung von adk_core::AdkError
Alle Fehler sollten den zentralisierten Fehlertyp verwenden:
use adk_core::{AdkError, Result};
// Gibt Result<T> zurück (als Alias für Result<T, AdkError>)
pub async fn my_function() -> Result<String> {
// Verwenden Sie ? zur Fehlerweitergabe
let data = fetch_data().await?;
// Erstellen Sie Fehler mit entsprechenden Varianten
if data.is_empty() {
return Err(AdkError::Tool("No data found".into()));
}
Ok(data)
}
Fehlervarianten
Verwenden Sie die entsprechende Fehlervariante:
| Variante | Anwendungsfall |
|---|---|
AdkError::Agent(String) | Fehler bei der Agenten-Ausführung |
AdkError::Model(String) | LLM-Anbieterfehler |
AdkError::Tool(String) | Fehler bei der Tool-Ausführung |
AdkError::Session(String) | Fehler bei der Session-Verwaltung |
AdkError::Artifact(String) | Fehler bei der Artefaktspeicherung |
AdkError::Config(String) | Konfigurationsfehler |
AdkError::Network(String) | HTTP-/Netzwerkfehler |
Fehlermeldungen
Schreiben Sie klare, umsetzbare Fehlermeldungen:
// Gut: Spezifisch und umsetzbar
Err(AdkError::Config("API key not found. Set GOOGLE_API_KEY environment variable.".into()))
// Schlecht: Vage
Err(AdkError::Config("Invalid config".into()))
Asynchrone Muster
Tokio verwenden
Der gesamte asynchrone Code verwendet die Tokio-Laufzeitumgebung:
use tokio::sync::{Mutex, RwLock};
// Prefer RwLock for read-heavy data
let state: Arc<RwLock<State>> = Arc::new(RwLock::new(State::default()));
// Use Mutex for write-heavy or simple cases
let counter: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
Asynchrone Traits
Verwenden Sie async_trait für asynchrone Trait-Methoden:
use async_trait::async_trait;
#[async_trait]
pub trait MyTrait: Send + Sync {
async fn do_work(&self) -> Result<()>;
}
Streaming
Verwenden Sie EventStream für Streaming-Antworten:
use adk_core::EventStream;
use async_stream::stream;
use futures::Stream;
fn create_stream() -> EventStream {
let s = stream! {
yield Ok(Event::new("inv-1"));
yield Ok(Event::new("inv-2"));
};
Box::pin(s)
}
Threadsicherheit
Alle öffentlichen Typen müssen Send + Sync sein:
// Good: Thread-safe
pub struct MyAgent {
name: String,
tools: Vec<Arc<dyn Tool>>, // Arc for shared ownership
}
// Verify with compile-time checks
fn assert_send_sync<T: Send + Sync>() {}
fn _check() {
assert_send_sync::<MyAgent>();
}
Testen
Testorganisation
crate/
├── src/
│ ├── lib.rs # Unittests am Ende der Datei
│ └── module.rs # Modulspezifische Tests
└── tests/
└── integration.rs # Integrationstests
Unittests
Platzieren Sie Unittests in derselben Datei wie den Code:
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
#[tokio::test]
async fn test_async_function() {
let result = async_function().await;
assert!(result.is_ok());
}
}
Integrationstests
Platzieren Sie diese im Verzeichnis tests/:
// tests/integration_test.rs
use adk_core::*;
#[tokio::test]
async fn test_full_workflow() {
// Setup
let service = InMemorySessionService::new();
// Execute
let session = service.create(request).await.unwrap();
// Assert
assert_eq!(session.id(), "test-session");
}
Mock-Tests
Verwenden Sie MockLlm für Tests ohne API-Aufrufe:
use adk_model::MockLlm;
#[tokio::test]
async fn test_agent_with_mock() {
let mock = MockLlm::new(vec![
"First response".to_string(),
"Second response".to_string(),
]);
let agent = LlmAgentBuilder::new("test")
.model(Arc::new(mock))
.build()
.unwrap();
// Test agent behavior
}
Testbefehle
# Alle Tests ausführen
cargo test --all
# Tests für ein bestimmtes Crate ausführen
cargo test --package adk-core
# Mit Ausgabe ausführen
cargo test --all -- --nocapture
# Ignorierte Tests ausführen (erfordern API-Schlüssel)
cargo test --all -- --ignored
Dokumentation
Dokumentationskommentare
Verwenden Sie /// für öffentliche Elemente:
/// Erstellt einen neuen LLM-Agenten mit der angegebenen Konfiguration.
///
/// # Argumente
///
/// * `name` - Eine eindeutige Kennung für diesen Agenten
/// * `model` - Der LLM-Anbieter, der für die Argumentation verwendet werden soll
///
/// # Beispiele
///
/// ```rust
/// use adk_agent::LlmAgentBuilder;
///
/// let agent = LlmAgentBuilder::new("assistant")
/// .model(Arc::new(model))
/// .build()?;
/// ```
///
/// # Fehler
///
/// Gibt `AdkError::Agent` zurück, wenn das Modell nicht gesetzt ist.
pub fn new(name: impl Into<String>) -> Self {
// ...
}
Moduldokumentation
Fügen Sie Moduldokumentationen am Anfang von lib.rs hinzu:
//! # adk-core
//!
//! Kern-Typen und Traits für ADK-Rust.
//!
//! ## Überblick
//!
//! Dieses Crate bietet die grundlegenden Typen...
README-Dateien
Jedes Crate sollte eine README.md mit Folgendem haben:
- Kurze Beschreibung
- Installationsanweisungen
- Schnelles Beispiel
- Link zur vollständigen Dokumentation
Dokumentationstests
Stellen Sie sicher, dass die Dokumentationsbeispiele kompilieren:
cargo test --doc --all
Pull-Request-Prozess
Vor dem Absenden
-
Führen Sie die vollständige Testsuite aus:
cargo test --all -
Führen Sie clippy aus:
cargo clippy --all-targets --all-features -
Formatieren Sie den Code:
cargo fmt --all -
Aktualisieren Sie die Dokumentation, wenn Sie eine öffentliche API hinzufügen oder ändern
-
Fügen Sie Tests für neue Funktionalitäten hinzu
PR-Richtlinien
- Titel: Klare, prägnante Beschreibung der Änderung
- Beschreibung: Erklären Sie was und warum (nicht wie)
- Größe: Halten Sie PRs fokussiert; teilen Sie große Änderungen auf
- Tests: Fügen Sie Tests für neue Funktionalitäten hinzu
- Breaking changes: Klar in der Beschreibung dokumentieren
Commit-Nachrichten
Befolgen Sie die Konventionen für Commits:
feat: add OpenAI streaming support
fix: correct tool parameter validation
docs: update quickstart guide
refactor: simplify session state management
test: add integration tests for A2A protocol
Allgemeine Aufgaben
Hinzufügen eines neuen Tool
- Erstellen Sie das Tool:
use adk_core::{Tool, ToolContext, Result};
use async_trait::async_trait;
use serde_json::Value;
pub struct MyTool {
// fields
}
#[async_trait]
impl Tool for MyTool {
fn name(&self) -> &str {
"my_tool"
}
fn description(&self) -> &str {
"Does something useful"
}
fn parameters_schema(&self) -> Option<Value> {
Some(serde_json::json!({
"type": "object",
"properties": {
"input": { "type": "string" }
},
"required": ["input"]
}))
}
async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
let input = args["input"].as_str().unwrap_or_default();
Ok(serde_json::json!({ "result": input }))
}
}
- Fügen Sie es dem Agent hinzu:
let agent = LlmAgentBuilder::new("agent")
.model(model)
.tool(Arc::new(MyTool::new()))
.build()?;
Hinzufügen eines neuen Model Provider
- Erstellen Sie das Modul in
adk-model/src/:
// adk-model/src/mymodel/mod.rs
mod client;
pub use client::MyModelClient;
- Implementieren Sie den Llm trait:
use adk_core::{Llm, LlmRequest, LlmResponse, LlmResponseStream, Result};
pub struct MyModelClient {
api_key: String,
}
#[async_trait]
impl Llm for MyModelClient {
fn name(&self) -> &str {
"my-model"
}
async fn generate_content(
&self,
request: LlmRequest,
stream: bool,
) -> Result<LlmResponseStream> {
// Implementation
}
}
- Fügen Sie ein Feature-Flag hinzu in
adk-model/Cargo.toml:
[features]
mymodel = ["dep:mymodel-sdk"]
- Exportieren Sie bedingt:
#[cfg(feature = "mymodel")]
pub mod mymodel;
#[cfg(feature = "mymodel")]
pub use mymodel::MyModelClient;
Hinzufügen eines neuen Agent-Typs
- Erstellen Sie das Modul in
adk-agent/src/:
// adk-agent/src/my_agent.rs
use adk_core::{Agent, EventStream, InvocationContext, Result};
use async_trait::async_trait;
pub struct MyAgent {
name: String,
}
#[async_trait]
impl Agent for MyAgent {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
"My custom agent"
}
async fn run(&self, ctx: Arc<dyn InvocationContext>) -> Result<EventStream> {
// Implementation
}
}
- Exportieren Sie es in
adk-agent/src/lib.rs:
mod my_agent;
pub use my_agent::MyAgent;
Debugging-Tipps
-
Aktivieren Sie Tracing:
adk_telemetry::init_telemetry(); -
Überprüfen Sie Ereignisse:
while let Some(event) = stream.next().await { eprintln!("Event: {:?}", event); } -
Verwenden Sie RUST_LOG:
RUST_LOG=debug cargo run --example myexample
Zurück: ← Zugriffssteuerung
Fragen? Erstellen Sie ein Issue auf GitHub.