開発ガイドライン

このドキュメントは、ADK-Rustに貢献する開発者向けに包括的なガイドラインを提供します。これらの標準に従うことで、プロジェクト全体のコード品質、一貫性、保守性を確保します。

目次

開始方法

前提条件

  • Rust: 1.75以上 (rustc --versionで確認)
  • Cargo: 最新の安定版
  • Git: バージョン管理用

環境のセットアップ

# 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

環境変数

APIキーを必要とする例やテストを実行する場合:

# Gemini (デフォルトプロバイダ)
export GOOGLE_API_KEY="your-api-key"

# OpenAI (任意)
export OPENAI_API_KEY="your-api-key"

# Anthropic (任意)
export ANTHROPIC_API_KEY="your-api-key"

プロジェクト構造

adk-rustは、複数のcrateを持つCargo workspaceとして編成されています。

adk-rust/
├── adk-core/       # 基本的なトレイトと型(Agent, Tool, Llm, Event)
├── adk-telemetry/  # OpenTelemetry統合
├── adk-model/      # LLMプロバイダ(Gemini, OpenAI, Anthropic)
├── adk-tool/       # Toolシステム(FunctionTool, MCP, AgentTool)
├── adk-session/    # セッション管理(インメモリ、SQLite)
├── adk-artifact/   # バイナリ成果物のストレージ
├── adk-memory/     # 検索機能付きの長期メモリ
├── adk-agent/      # Agentの実装(LlmAgent, ワークフローAgent)
├── adk-runner/     # 実行ランタイム
├── adk-server/     # REST APIとA2Aプロトコル
├── adk-cli/        # コマンドラインランチャー
├── adk-realtime/   # 音声/オーディオストリーミングAgent
├── adk-graph/      # LangGraphスタイルのワークフロー
├── adk-browser/    # ブラウザ自動化ツール
├── adk-eval/       # Agent評価フレームワーク
├── adk-rust/       # アンブレラcrate(すべてを再エクスポート)
└── examples/       # 動作例

Crateの依存関係

Crateは依存関係の順序で公開する必要があります。

  1. adk-core(内部依存関係なし)
  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(アンブレラ)

コードスタイル

一般原則

  1. 巧妙さよりも明確さ: 読みやすく理解しやすいコードを書く
  2. 暗黙的よりも明示的: 明示的な型とエラー処理を優先する
  3. 小さな関数: 関数の焦点を絞り、可能であれば50行未満に保つ
  4. 意味のある名前: 説明的な変数名と関数名を使用する

フォーマット

デフォルト設定で rustfmt を使用します:

cargo fmt --all

CIパイプラインはフォーマットを強制します。コミットする前に常に cargo fmt を実行してください。

命名規則

タイプ規則
クレートadk-* (ケバブケース)adk-core, adk-agent
モジュールsnake_casellm_agent, function_tool
型/トレイトPascalCaseLlmAgent, ToolContext
関数snake_caseexecute_tool, run_agent
定数SCREAMING_SNAKE_CASEKEY_PREFIX_APP
型パラメータ単一大文字またはPascalCaseT, State

インポート

インポートを次の順序で整理します:

// 1. 標準ライブラリ
use std::collections::HashMap;
use std::sync::Arc;

// 2. 外部クレート
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;

// 3. 内部クレート (adk-*)
use adk_core::{Agent, Event, Result};

// 4. ローカルモジュール
use crate::config::Config;
use super::utils;

Clippy

すべてのコードは警告なしでclippyをパスする必要があります:

cargo clippy --all-targets --all-features

clippyの警告は抑制するのではなく対処してください。抑制が必要な場合は、その理由を文書化してください:

#[allow(clippy::too_many_arguments)]
// ビルダーパターンでは多くのパラメータが必要であり、リファクタリングすると使いやすさが損なわれるため
fn complex_builder(...) { }

エラー処理

adk_core::AdkError を使用する

すべてのエラーは、一元化されたエラータイプを使用する必要があります:

use adk_core::{AdkError, Result};

// Result<T> (Result<T, AdkError> のエイリアス) を返す
pub async fn my_function() -> Result<String> {
    // 伝播のために ? を使用
    let data = fetch_data().await?;

    // 適切なバリアントでエラーを作成
    if data.is_empty() {
        return Err(AdkError::Tool("No data found".into()));
    }

    Ok(data)
}

エラーバリアント

適切なエラーバリアントを使用します:

バリアントユースケース
AdkError::Agent(String)Agent実行エラー
AdkError::Model(String)LLMプロバイダーエラー
AdkError::Tool(String)Tool実行エラー
AdkError::Session(String)Session管理エラー
AdkError::Artifact(String)Artifactストレージエラー
AdkError::Config(String)設定エラー
AdkError::Network(String)HTTP/ネットワークエラー

エラーメッセージ

明確で実用的なエラーメッセージを記述します:

// 良い例: 具体性があり、対処可能
Err(AdkError::Config("API key not found. Set GOOGLE_API_KEY environment variable.".into()))

// 悪い例: 曖昧
Err(AdkError::Config("Invalid config".into()))

非同期パターン

Tokio の使用

すべての非同期コードは Tokio ランタイムを使用します:

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));

非同期トレイト

非同期トレイトメソッドには async_trait を使用します:

use async_trait::async_trait;

#[async_trait]
pub trait MyTrait: Send + Sync {
    async fn do_work(&self) -> Result<()>;
}

ストリーミング

ストリーミング応答には EventStream を使用します:

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

スレッドセーフティ

すべてのパブリック型は Send + Sync である必要があります:

// 良い例: スレッドセーフ
pub struct MyAgent {
    name: String,
    tools: Vec<Arc<dyn Tool>>,  // 共有所有権のための Arc
}

// コンパイル時チェックで検証
fn assert_send_sync<T: Send + Sync>() {}
fn _check() {
    assert_send_sync::<MyAgent>();
}

テスト

テストの構成

crate/
├── src/
│   ├── lib.rs          # Unit tests at bottom of file
│   └── module.rs       # Module-specific tests
└── tests/
    └── integration.rs  # Integration tests

単体テスト

単体テストはコードと同じファイルに配置します:

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());
    }
}

結合テスト

tests/ ディレクトリに配置します:

// tests/integration_test.rs
use adk_core::*;

#[tokio::test]
async fn test_full_workflow() {
    // セットアップ
    let service = InMemorySessionService::new();

    // 実行
    let session = service.create(request).await.unwrap();

    // アサート
    assert_eq!(session.id(), "test-session");
}

モックテスト

API 呼び出しなしでテストするには MockLlm を使用します:

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();

    // Agent の振る舞いをテスト
}

テストコマンド

# すべてのテストを実行
cargo test --all

# 特定のクレートのテストを実行
cargo test --package adk-core

# 出力付きで実行
cargo test --all -- --nocapture

# 無視されたテストを実行 (API キーが必要)
cargo test --all -- --ignored

ドキュメント

ドキュメントコメント

パブリック項目には /// を使用します:

/// 指定された設定で新しい LLM agent を作成します。
///
/// # 引数
///
/// * `name` - この agent の一意な識別子
/// * `model` - 推論に使用する LLM プロバイダー
///
/// # 例
///
/// ```rust
/// use adk_agent::LlmAgentBuilder;
///
/// let agent = LlmAgentBuilder::new("assistant")
///     .model(Arc::new(model))
///     .build()?;
/// ```
///
/// # エラー
///
/// モデルが設定されていない場合、`AdkError::Agent` を返します。
pub fn new(name: impl Into<String>) -> Self {
    // ...
}

モジュールドキュメント

lib.rs の先頭にモジュールレベルのドキュメントを追加します:

//! # adk-core
//!
//! ADK-Rust のコア型とトレイト。
//!
//! ## 概要
//!
//! このクレートは基本的な型を提供します...

README ファイル

各クレートには README.md が必要で、以下を含みます:

  1. 簡単な説明
  2. インストール手順
  3. 簡単な例
  4. 完全なドキュメントへのリンク

ドキュメントテスト

ドキュメントの例がコンパイルされることを確認します:

cargo test --doc --all

プルリクエストプロセス

提出前

  1. 全テストスイートを実行する:

    cargo test --all
  2. clippyを実行する:

    cargo clippy --all-targets --all-features
  3. コードをフォーマットする:

    cargo fmt --all
  4. パブリックAPIを追加または変更する場合はドキュメントを更新する

  5. 新機能のテストを追加する

PRガイドライン

  • タイトル: 変更点の明確で簡潔な説明
  • Description: 何を、なぜ行ったかを説明する(方法ではない)
  • サイズ: PRは焦点を絞り、大規模な変更は分割する
  • テスト: 新機能のテストを含める
  • 破壊的変更: 説明文に明確に記載する

コミットメッセージ

規約に従ったコミット(Conventional 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

一般的なタスク

新しいToolを追加する

  1. ツールを作成する:
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. Agentに追加する:
let agent = LlmAgentBuilder::new("agent")
    .model(model)
    .tool(Arc::new(MyTool::new()))
    .build()?;

新しいModel Providerを追加する

  1. adk-model/src/モジュールを作成する:
// adk-model/src/mymodel/mod.rs
mod client;
pub use client::MyModelClient;
  1. Llmトレイトを実装する:
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. adk-model/Cargo.toml機能フラグを追加する:
[features]
mymodel = ["dep:mymodel-sdk"]
  1. 条件付きでエクスポートする:
#[cfg(feature = "mymodel")]
pub mod mymodel;
#[cfg(feature = "mymodel")]
pub use mymodel::MyModelClient;

新しいAgentタイプを追加する

  1. 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. adk-agent/src/lib.rsエクスポートする:
mod my_agent;
pub use my_agent::MyAgent;

デバッグのヒント

  1. トレースを有効にする:

    adk_telemetry::init_telemetry();
  2. イベントを検査する:

    while let Some(event) = stream.next().await {
        eprintln!("Event: {:?}", event);
    }
  3. RUST_LOGを使用する:

    RUST_LOG=debug cargo run --example myexample

前へ: ← アクセス制御

質問がありますか? GitHubでissueを立ててください。