开发指南
本文档为参与 ADK-Rust 开发的开发者提供全面指南。遵循这些标准可确保整个项目的代码质量、一致性和可维护性。
目录
快速入门
前提条件
- Rust:1.95.0 或更高版本(2024 edition,使用
rustc --version检查) - Cargo:最新稳定版
- Git:用于版本控制
- sccache(推荐):编译缓存,可将重建时间缩短约 70%
配置你的环境
# Clone the repository
git clone https://github.com/zavora-ai/adk-rust.git
cd adk-rust
# Option A: Nix/devenv (reproducible — identical on Linux, macOS, CI)
devenv shell
# Option B: Setup script (installs sccache, cmake, etc.)
./scripts/setup-dev.sh
# Option C: Manual
cargo build
# Install cargo-nextest (parallel test runner, ~10x faster)
curl -LsSf https://get.nexte.st/latest/mac | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin
# Run all tests
cargo nextest run --workspace
# 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/ # 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 依赖关系
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。
命名约定
| 类型 | 约定 | 示例 |
|---|---|---|
| Crates | adk-*(kebab-case) | 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. Standard library
use std::collections::HashMap;
use std::sync::Arc;
// 2. External crates
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
// 3. Internal crates (adk-*)
use adk_core::{Agent, Event, Result};
// 4. Local modules
use crate::config::Config;
use super::utils;
Clippy
所有代码必须通过 clippy 且无警告:
cargo clippy --all-targets --all-features
应修复 clippy 警告,而不是抑制它们。如果必须抑制,请说明原因:
#[allow(clippy::too_many_arguments)]
// Builder pattern requires many parameters; refactoring would hurt usability
fn complex_builder(...) { }
错误处理
结构化错误信封
AdkError 是一种结构化错误类型,包含组件(位置)、类别(什么类型)、代码(机器键)、消息(人类文本)、重试提示以及可选详情:
use adk_core::{AdkError, ErrorComponent, ErrorCategory, Result};
// Return Result<T> (aliased to Result<T, AdkError>)
pub async fn my_function() -> Result<String> {
let data = fetch_data().await?;
if data.is_empty() {
return Err(AdkError::new(
ErrorComponent::Tool,
ErrorCategory::NotFound,
"tool.data.not_found",
"No data found for the given query",
));
}
Ok(data)
}
选择组件和类别
ErrorComponent 用于标识故障发生的位置(源子系统,而不是它所经过的 trait 边界):
| 组件 | 使用场景 |
|---|---|
Agent | Agent 编排,子 agent 分发 |
Model | LLM provider 调用,响应解析 |
Tool | 工具执行,参数验证 |
Session | 会话持久化,状态管理 |
Memory | 内存/RAG 操作 |
Graph | 图工作流执行 |
Auth | 身份验证、授权 |
Server | HTTP 服务器、配置 |
ErrorCategory 对出了什么问题进行分类:
| 分类 | HTTP | 适用时 |
|---|---|---|
InvalidInput | 400 | 参数、配置、请求正文错误 |
Unauthorized | 401 | 缺少或无效的凭据 |
Forbidden | 403 | 凭证有效,但权限不足 |
NotFound | 404 | 资源不存在 |
RateLimited | 429 | 上游速率限制(可重试) |
Timeout | 408 | 操作超出时间限制(可重试) |
Unavailable | 503 | 上游服务不可用(可重试) |
Cancelled | 499 | 由调用方或系统取消 |
Internal | 500 | Bug,违反不变量 |
Unsupported | 501 | 功能不受支持 |
便捷构造函数
对于常见模式:
// Structured (preferred for new code)
AdkError::not_found(ErrorComponent::Session, "session.not_found", "Session xyz not found")
AdkError::rate_limited(ErrorComponent::Model, "model.openai.rate_limited", "Too many requests")
AdkError::unauthorized(ErrorComponent::Auth, "auth.token_expired", "Bearer token expired")
AdkError::timeout(ErrorComponent::Tool, "tool.execution_timeout", "Tool timed out after 30s")
// Backward-compatible (for migration — produces .legacy codes)
AdkError::tool("No data found")
AdkError::model("Provider returned 500")
AdkError::session("Session not found")
构建器 API
附加结构化元数据,以提供更丰富的错误上下文:
let err = AdkError::new(
ErrorComponent::Model,
ErrorCategory::RateLimited,
"model.openai.rate_limited",
"OpenAI rate limit exceeded",
)
.with_provider("openai")
.with_upstream_status(429)
.with_request_id("req-abc123")
.with_retry(RetryHint {
should_retry: true,
retry_after_ms: Some(5000),
max_attempts: Some(3),
});
重试提示
可重试类别(RateLimited、Unavailable、Timeout)会自动设置 should_retry: true。使用 err.is_retryable() 检查是否可重试,它将 retry.should_retry 作为唯一事实来源:
if err.is_retryable() {
if let Some(delay) = err.retry.retry_after() {
tokio::time::sleep(delay).await;
}
// retry the operation
}
类别检查
err.is_retryable() // retry.should_retry (RateLimited, Unavailable, Timeout by default)
err.is_not_found() // category == NotFound
err.is_unauthorized() // category == Unauthorized
err.is_rate_limited() // category == RateLimited
err.is_timeout() // category == Timeout
组件检查(向后兼容)
err.is_model() // component == Model
err.is_tool() // component == Tool
err.is_session() // component == Session
err.is_config() // code == "config.legacy" (temporary bridge)
HTTP 状态和问题 JSON
AdkError 直接映射到 HTTP 响应:
let status = err.http_status_code(); // u16 based on category
let body = err.to_problem_json(); // structured JSON error body
// body: { "error": { "code", "message", "component", "category", "requestId", "retryAfter", ... } }
使用 From 实现的 crate 本地错误
具有领域特定错误的 crate 会实现 From<CrateLocalError> for AdkError:
// In your crate
#[derive(Debug, thiserror::Error)]
pub enum MyToolError {
#[error("connection failed: {0}")]
ConnectionFailed(String),
#[error("timeout after {0}ms")]
Timeout(u64),
}
impl From<MyToolError> for AdkError {
fn from(err: MyToolError) -> Self {
let (category, code) = match &err {
MyToolError::ConnectionFailed(_) => (ErrorCategory::Unavailable, "mytool.connection"),
MyToolError::Timeout(_) => (ErrorCategory::Timeout, "mytool.timeout"),
};
AdkError::new(ErrorComponent::Tool, category, code, err.to_string())
.with_source(err)
}
}
不要提供通用 From 实现
std::io::Error 和 serde_json::Error 跨越了太多子系统边界,无法进行单一的通用转换。请针对正确的组件使用显式 map_err:
// Good: explicit component and category
let data = std::fs::read_to_string(path)
.map_err(|e| AdkError::new(
ErrorComponent::Session,
ErrorCategory::Internal,
"session.io_read",
format!("failed to read session file: {e}"),
).with_source(e))?;
// Good: for quick migration
let data = serde_json::from_str(&raw)
.map_err(|e| AdkError::session(format!("JSON parse failed: {e}")))?;
错误信息
编写清晰、可操作的错误信息:
// Good: specific and actionable
AdkError::new(
ErrorComponent::Model,
ErrorCategory::InvalidInput,
"model.missing_api_key",
"API key not found. Set GOOGLE_API_KEY environment variable.",
)
// Bad: vague
AdkError::model("Invalid config")
库代码中不要 panic
库 crate 绝不能在可恢复错误上 panic。在 src/ 代码中避免使用 unwrap()、expect() 和 panic!()(测试代码除外)。
RwLock / Mutex:使用优雅降级来代替 unwrap()。锁被 poisoned 说明另一个线程发生了 panic——让当前线程也崩溃只会让情况更糟。
// Bad: panics if lock is poisoned
let state = self.state.write().unwrap();
// Good: log and return a safe default
let Ok(state) = self.state.write() else {
tracing::error!("state lock poisoned — returning default");
return Default::default();
};
// Good: recover through the poison (data may be stale but won't crash)
let state = self.state.read().unwrap_or_else(|e| e.into_inner());
构造函数:当初始化可能失败时返回 Result(例如连接外部服务)。
// Bad: panics if Docker is not running
pub fn new(config: Config) -> Self {
let client = connect().expect("connection failed");
Self { client }
}
// Good: caller decides how to handle the failure
pub fn new(config: Config) -> Result<Self, MyError> {
let client = connect().map_err(|e| MyError::Init(e.to_string()))?;
Ok(Self { client })
}
构建器方法:对于 Arc::get_mut(),使用 if let Some 而不是 expect():
// Bad: panics if Arc is shared
pub fn add_callback(mut self, cb: Callback) -> Self {
Arc::get_mut(&mut self.callbacks).expect("not shared").push(cb);
self
}
// Good: silent no-op (builder pattern guarantees single ownership)
pub fn add_callback(mut self, cb: Callback) -> Self {
if let Some(callbacks) = Arc::get_mut(&mut self.callbacks) {
callbacks.push(cb);
}
self
}
异步模式
使用 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));
Tokio 特性约定
库 crate(adk-*)必须只声明它们实际使用的最小 tokio 特性:
# Library crates — minimal features
tokio = { workspace = true, features = ["rt", "sync", "time"] }
# Binary crates only (adk-cli, examples) — full is acceptable
tokio = { workspace = true, features = ["full"] }
在库 crate 中绝不要使用 features = ["full"]。这会迫使所有下游使用方无论是否需要,都编译 tokio 的每个子系统。
异步 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:
// 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>();
}
测试
测试运行器
ADK-Rust 使用 cargo-nextest 执行测试。Nextest 会在独立进程中运行每个测试二进制文件,并进行并行调度,在这个工作区上相比 cargo test 可带来约 10 倍的加速。
# Install (one-time)
curl -LsSf https://get.nexte.st/latest/mac | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin
# Or via devenv (included automatically)
devenv shell
配置位于 .config/nextest.toml,包含两个配置档案:
default— 本地开发(快速失败、无重试)ci— CI 运行(对不稳定测试进行重试、慢测试警告)
测试组织
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() {
// Setup
let service = InMemorySessionService::new();
// Execute
let session = service.create(request).await.unwrap();
// Assert
assert_eq!(session.id(), "test-session");
}
Mock 测试
使用 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();
// Test agent behavior
}
测试命令
# Run all tests (nextest — parallel, fast)
cargo nextest run --workspace
# Run specific crate tests
cargo nextest run -p adk-core
# Run with CI profile (retries flaky tests)
cargo nextest run --workspace --profile ci
# Run doctests (nextest doesn't run these — use cargo test)
cargo test --workspace --doc
# Run ignored tests (require API keys)
cargo nextest run --workspace -- --run-ignored
# Run with output (nextest shows output for failing tests by default)
cargo nextest run --workspace --no-capture
# Devenv shortcuts
devenv shell ws-test # nextest, default profile
devenv shell ws-test-ci # nextest, CI profile
devenv shell ws-test-slow # cargo test fallback (includes doctests)
文档
文档注释
公共项使用 ///:
/// Creates a new LLM agent with the specified configuration.
///
/// # Arguments
///
/// * `name` - A unique identifier for this agent
/// * `model` - The LLM provider to use for reasoning
///
/// # Examples
///
/// ```rust
/// use adk_agent::LlmAgentBuilder;
///
/// let agent = LlmAgentBuilder::new("assistant")
/// .model(Arc::new(model))
/// .build()?;
/// ```
///
/// # Errors
///
/// Returns an error with component `Agent` if the model is not set.
pub fn new(name: impl Into<String>) -> Self {
// ...
}
模块文档
在 lib.rs 顶部添加模块级文档:
//! # adk-core
//!
//! Core types and traits for ADK-Rust.
//!
//! ## Overview
//!
//! This crate provides the foundational types...
README 文件
每个 crate 都应有一个 README.md,其中包括:
- 简要说明
- 安装说明
- 快速示例
- 指向完整文档的链接
文档测试
确保文档示例可编译:
cargo test --doc --all
Pull Request 流程
提交前
-
运行完整测试套件:
cargo nextest run --workspace -
运行 clippy:
cargo clippy --all-targets --all-features -
格式化代码:
cargo fmt --all -
如果添加/更改了公共 API,请更新文档
-
为新功能添加测试
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
项目脚手架
使用 cargo adk new 和 Composable Template System 来搭建新项目。内置注册表提供 12 个模板、9 个附加组件和 5 种企业模式:
# Basic agent (default)
cargo adk new my-agent
# Agent with tools and Docker support
cargo adk new my-agent --template tools --addon docker
# A2A protocol agent with CI and telemetry
cargo adk new my-agent --template a2a --addon ci --addon telemetry
# Graph workflow with enterprise observability
cargo adk new my-agent --template graph --addon telemetry --addon docker --addon ci
--addon 标志是可组合的——将任意基础模板与任意数量的 addons 组合。有关模板、addons 和企业模式的完整列表,请参阅 Composable Templates 文档。
常见任务
添加新工具
- 创建工具:
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()?;
添加新的模型提供方
- 在
adk-model/src/中创建模块:
// adk-model/src/mymodel/mod.rs
mod client;
pub use client::MyModelClient;
- 实现 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
}
}
- 在
adk-model/Cargo.toml中添加 feature flag:
[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;
调试提示
-
启用 tracing:
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。