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

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:

  1. adk-core (keine internen Abhängigkeiten)
  2. adk-telemetry
  3. adk-model
  4. adk-tool
  5. adk-session
  6. adk-artifact
  7. adk-memory
  8. adk-agent
  9. adk-runner
  10. adk-server
  11. adk-cli
  12. adk-realtime
  13. adk-graph
  14. adk-browser
  15. adk-eval
  16. adk-rust (umbrella)

Code-Stil

Allgemeine Prinzipien

  1. Klarheit vor Cleverness: Schreiben Sie Code, der leicht zu lesen und zu verstehen ist.
  2. Explizit vor Implizit: Bevorzugen Sie explizite Typen und Fehlerbehandlung.
  3. Kleine Funktionen: Halten Sie Funktionen fokussiert und nach Möglichkeit unter 50 Zeilen.
  4. 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

TypKonventionBeispiel
Cratesadk-* (kebab-case)adk-core, adk-agent
Modulesnake_casellm_agent, function_tool
Typen/TraitsPascalCaseLlmAgent, ToolContext
Funktionensnake_caseexecute_tool, run_agent
KonstantenSCREAMING_SNAKE_CASEKEY_PREFIX_APP
TypparameterEinzelner Großbuchstabe oder PascalCaseT, 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:

VarianteAnwendungsfall
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:

  1. Kurze Beschreibung
  2. Installationsanweisungen
  3. Schnelles Beispiel
  4. Link zur vollständigen Dokumentation

Dokumentationstests

Stellen Sie sicher, dass die Dokumentationsbeispiele kompilieren:

cargo test --doc --all

Pull-Request-Prozess

Vor dem Absenden

  1. Führen Sie die vollständige Testsuite aus:

    cargo test --all
  2. Führen Sie clippy aus:

    cargo clippy --all-targets --all-features
  3. Formatieren Sie den Code:

    cargo fmt --all
  4. Aktualisieren Sie die Dokumentation, wenn Sie eine öffentliche API hinzufügen oder ändern

  5. 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

  1. 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 }))
    }
}
  1. 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

  1. Erstellen Sie das Modul in adk-model/src/:
// adk-model/src/mymodel/mod.rs
mod client;
pub use client::MyModelClient;
  1. 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
    }
}
  1. Fügen Sie ein Feature-Flag hinzu in adk-model/Cargo.toml:
[features]
mymodel = ["dep:mymodel-sdk"]
  1. Exportieren Sie bedingt:
#[cfg(feature = "mymodel")]
pub mod mymodel;
#[cfg(feature = "mymodel")]
pub use mymodel::MyModelClient;

Hinzufügen eines neuen Agent-Typs

  1. 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
    }
}
  1. Exportieren Sie es in adk-agent/src/lib.rs:
mod my_agent;
pub use my_agent::MyAgent;

Debugging-Tipps

  1. Aktivieren Sie Tracing:

    adk_telemetry::init_telemetry();
  2. Überprüfen Sie Ereignisse:

    while let Some(event) = stream.next().await {
        eprintln!("Event: {:?}", event);
    }
  3. Verwenden Sie RUST_LOG:

    RUST_LOG=debug cargo run --example myexample

Zurück: ← Zugriffssteuerung

Fragen? Erstellen Sie ein Issue auf GitHub.