开发指南
本文档为 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 必须按依赖顺序发布:
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 |
| Modules | snake_case | llm_agent, function_tool |
| Types/Traits | PascalCase | LlmAgent, ToolContext |
| Functions | snake_case | execute_tool, run_agent |
| Constants | SCREAMING_SNAKE_CASE | KEY_PREFIX_APP |
| Type parameters | 单个大写字母或 PascalCase | T, 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,其中包含:
- 简要描述
- 安装说明
- 快速示例
- 完整文档链接
文档测试
确保文档示例能够编译:
cargo test --doc --all
拉取请求流程
提交前
-
运行完整的测试套件:
cargo test --all -
运行 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
常见任务
添加新 Tool
- 创建 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 提供者
- 在
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 上提出问题。