开发指南

本文档为 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 (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"

项目结构

ADK-Rust 是一个包含多个 crate 的 Cargo 工作区:

adk-rust/
├── adk-core/       # 基础 trait 和类型 (Agent, Tool, Llm, Event)
├── adk-telemetry/  # OpenTelemetry 集成
├── adk-model/      # LLM 提供商 (Gemini, OpenAI, Anthropic)
├── adk-tool/       # 工具系统 (FunctionTool, MCP, AgentTool)
├── adk-session/    # 会话管理(内存、SQLite)
├── adk-artifact/   # 二进制工件存储
├── adk-memory/     # 带有搜索功能的长期记忆
├── adk-agent/      # Agent 实现 (LlmAgent, 工作流 agents)
├── adk-runner/     # 执行运行时
├── adk-server/     # REST API 和 A2A 协议
├── adk-cli/        # 命令行启动器
├── adk-realtime/   # 语音/音频流 agents
├── 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

命名约定

类型约定示例
Cratesadk-* (kebab-case)adk-core, adk-agent
Modulessnake_casellm_agent, function_tool
Types/TraitsPascalCaseLlmAgent, ToolContext
Functionssnake_caseexecute_tool, run_agent
ConstantsSCREAMING_SNAKE_CASEKEY_PREFIX_APP
Type parameters单个大写字母或 PascalCaseT, State

导入

按以下顺序组织导入:

// 1. 标准库
use std::collections::HashMap;
use std::sync::Arc;

// 2. 外部 crate
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;

// 3. 内部 crate (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)]
// Builder 模式需要很多参数;重构会降低可用性
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};

// 对于读密集型数据,优先使用 RwLock
let state: Arc<RwLock<State>> = Arc::new(RwLock::new(State::default()));

// 对于写密集型或简单情况,使用 Mutex
let counter: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));

异步 Trait

对于异步 trait 方法,使用 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          # 单元测试位于文件底部
│   └── module.rs       # 模块特定测试
└── tests/
    └── integration.rs  # 集成测试

单元测试

将单元测试放在与代码相同的文件中:

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

模拟测试

使用 MockLlm 进行无需 API 调用的测试:

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

# 运行特定 crate 的测试
cargo test --package adk-core

# 运行并显示输出
cargo test --all -- --nocapture

# 运行被忽略的测试(需要 API 密钥)
cargo test --all -- --ignored

文档

Doc Comments

对公共项使用 ///

/// 使用指定的配置创建一个新的 LLM Agent。
///
/// # Arguments
///
/// * `name` - 此 Agent 的唯一标识符
/// * `model` - 用于推理的 LLM 提供者
///
/// # Examples
///
/// ```rust
/// use adk_agent::LlmAgentBuilder;
///
/// let agent = LlmAgentBuilder::new("assistant")
///     .model(Arc::new(model))
///     .build()?;
/// ```
///
/// # Errors
///
/// 如果未设置模型,则返回 `AdkError::Agent`。
pub fn new(name: impl Into<String>) -> Self {
    // ...
}

模块文档

lib.rs 文件顶部添加模块级文档:

//! # adk-core
//!
//! ADK-Rust 的核心类型和 trait。
//!
//! ## 概述
//!
//! 此 crate 提供了基础类型...

README 文件

每个 crate 都应包含一个 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 指南

  • 标题:清晰、简洁地描述更改
  • 描述:解释做了什么以及为什么(而不是如何做)
  • 大小:保持 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. 创建 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. 添加到 Agent
let agent = LlmAgentBuilder::new("agent")
    .model(model)
    .tool(Arc::new(MyTool::new()))
    .build()?;

添加新的 Model 提供者

  1. adk-model/src/创建模块
// adk-model/src/mymodel/mod.rs
mod client;
pub use client::MyModelClient;
  1. 实现 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. adk-model/Cargo.toml添加 feature flag
[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. 启用 tracing

    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 上提出问题。