開発ガイドライン
このドキュメントは、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は依存関係の順序で公開する必要があります。
adk-core(内部依存関係なし)adk-telemetryadk-modeladk-tooladk-sessionadk-artifactadk-memoryadk-agentadk-runneradk-serveradk-cliadk-realtimeadk-graphadk-browseradk-evaladk-rust(アンブレラ)
コードスタイル
一般原則
- 巧妙さよりも明確さ: 読みやすく理解しやすいコードを書く
- 暗黙的よりも明示的: 明示的な型とエラー処理を優先する
- 小さな関数: 関数の焦点を絞り、可能であれば50行未満に保つ
- 意味のある名前: 説明的な変数名と関数名を使用する
フォーマット
デフォルト設定で rustfmt を使用します:
cargo fmt --all
CIパイプラインはフォーマットを強制します。コミットする前に常に cargo fmt を実行してください。
命名規則
| タイプ | 規則 | 例 |
|---|---|---|
| クレート | adk-* (ケバブケース) | adk-core, adk-agent |
| モジュール | snake_case | llm_agent, function_tool |
| 型/トレイト | PascalCase | LlmAgent, ToolContext |
| 関数 | snake_case | execute_tool, run_agent |
| 定数 | SCREAMING_SNAKE_CASE | KEY_PREFIX_APP |
| 型パラメータ | 単一大文字またはPascalCase | T, 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 が必要で、以下を含みます:
- 簡単な説明
- インストール手順
- 簡単な例
- 完全なドキュメントへのリンク
ドキュメントテスト
ドキュメントの例がコンパイルされることを確認します:
cargo test --doc --all
プルリクエストプロセス
提出前
-
全テストスイートを実行する:
cargo test --all -
clippyを実行する:
cargo clippy --all-targets --all-features -
コードをフォーマットする:
cargo fmt --all -
パブリックAPIを追加または変更する場合はドキュメントを更新する
-
新機能のテストを追加する
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を追加する
- ツールを作成する:
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 }))
}
}
- Agentに追加する:
let agent = LlmAgentBuilder::new("agent")
.model(model)
.tool(Arc::new(MyTool::new()))
.build()?;
新しいModel Providerを追加する
adk-model/src/にモジュールを作成する:
// adk-model/src/mymodel/mod.rs
mod client;
pub use client::MyModelClient;
- 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
}
}
adk-model/Cargo.tomlに機能フラグを追加する:
[features]
mymodel = ["dep:mymodel-sdk"]
- 条件付きでエクスポートする:
#[cfg(feature = "mymodel")]
pub mod mymodel;
#[cfg(feature = "mymodel")]
pub use mymodel::MyModelClient;
新しいAgentタイプを追加する
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
}
}
adk-agent/src/lib.rsでエクスポートする:
mod my_agent;
pub use my_agent::MyAgent;
デバッグのヒント
-
トレースを有効にする:
adk_telemetry::init_telemetry(); -
イベントを検査する:
while let Some(event) = stream.next().await { eprintln!("Event: {:?}", event); } -
RUST_LOGを使用する:
RUST_LOG=debug cargo run --example myexample
前へ: ← アクセス制御
質問がありますか? GitHubでissueを立ててください。