Artifacts
Artifacts 提供了一种在 Agent 应用程序中存储和检索二进制数据(图像、PDF、音频文件等)的方式。Artifact 系统处理数据的版本控制、命名空间范围和跨会话持久性。
概览
adk-rust 中的 Artifact 系统包括:
- Part:核心数据表示,可以包含带有 MIME 类型的文本或二进制数据
- ArtifactService:定义 Artifact 存储操作的 trait
- InMemoryArtifactService:用于开发和测试的内存实现
- ScopedArtifacts:一个包装器,通过自动处理会话上下文来简化 Artifact 操作
Artifact 按照应用程序、用户和会话进行范围划分,提供隔离和组织。文件可以是会话范围的(默认)或用户范围的(使用 user: 前缀)。
Part 表示
Part 枚举表示可以作为 Artifact 存储的数据:
pub enum Part {
Text { text: String },
InlineData { mime_type: String, data: Vec<u8> },
FunctionCall { name: String, args: serde_json::Value },
FunctionResponse { name: String, response: serde_json::Value },
}
对于 Artifact,您主要会使用:
Part::Text用于文本数据Part::InlineData用于带有 MIME 类型的二进制数据
基本用法
使用 Artifact 最简单的方法是通过 Artifacts trait,它在 Agent 上下文中可用:
use adk_rust::prelude::*;
// In an agent tool or callback
async fn save_report(ctx: &ToolContext) -> Result<Value> {
let artifacts = ctx.artifacts();
// Save text data
let version = artifacts.save(
"report.txt",
&Part::Text { text: "Report content".to_string() }
).await?;
// Save binary data
let image_data = vec![0xFF, 0xD8, 0xFF]; // JPEG header
artifacts.save(
"chart.jpg",
&Part::InlineData {
mime_type: "image/jpeg".to_string(),
data: image_data,
}
).await?;
Ok(json!({ "saved": true, "version": version }))
}
ArtifactService Trait
ArtifactService trait 定义了 artifact 管理的核心操作:
#[async_trait]
pub trait ArtifactService: Send + Sync {
async fn save(&self, req: SaveRequest) -> Result<SaveResponse>;
async fn load(&self, req: LoadRequest) -> Result<LoadResponse>;
async fn delete(&self, req: DeleteRequest) -> Result<()>;
async fn list(&self, req: ListRequest) -> Result<ListResponse>;
async fn versions(&self, req: VersionsRequest) -> Result<VersionsResponse>;
}
保存操作
保存具有自动或显式版本控制的 artifact:
use adk_artifact::{InMemoryArtifactService, SaveRequest};
use adk_core::Part;
let service = InMemoryArtifactService::new();
let response = service.save(SaveRequest {
app_name: "my_app".to_string(),
user_id: "user_123".to_string(),
session_id: "session_456".to_string(),
file_name: "document.pdf".to_string(),
part: Part::InlineData {
mime_type: "application/pdf".to_string(),
data: pdf_bytes,
},
version: None, // Auto-increment version
}).await?;
println!("Saved as version: {}", response.version);
加载操作
加载最新版本或特定版本:
use adk_artifact::LoadRequest;
// Load latest version
let response = service.load(LoadRequest {
app_name: "my_app".to_string(),
user_id: "user_123".to_string(),
session_id: "session_456".to_string(),
file_name: "document.pdf".to_string(),
version: None, // Load latest
}).await?;
// Load specific version
let response = service.load(LoadRequest {
app_name: "my_app".to_string(),
user_id: "user_123".to_string(),
session_id: "session_456".to_string(),
file_name: "document.pdf".to_string(),
version: Some(2), // Load version 2
}).await?;
match response.part {
Part::InlineData { mime_type, data } => {
println!("Loaded {} bytes of {}", data.len(), mime_type);
}
_ => {}
}
列表操作
列出 session 中的所有 artifact:
use adk_artifact::ListRequest;
let response = service.list(ListRequest {
app_name: "my_app".to_string(),
user_id: "user_123".to_string(),
session_id: "session_456".to_string(),
}).await?;
for file_name in response.file_names {
println!("Found artifact: {}", file_name);
}
删除操作
删除特定版本或所有版本:
use adk_artifact::DeleteRequest;
// Delete specific version
service.delete(DeleteRequest {
app_name: "my_app".to_string(),
user_id: "user_123".to_string(),
session_id: "session_456".to_string(),
file_name: "document.pdf".to_string(),
version: Some(1), // Delete version 1
}).await?;
// Delete all versions
service.delete(DeleteRequest {
app_name: "my_app".to_string(),
user_id: "user_123".to_string(),
session_id: "session_456".to_string(),
file_name: "document.pdf".to_string(),
version: None, // Delete all versions
}).await?;
版本操作
列出 artifact 的所有版本:
use adk_artifact::VersionsRequest;
let response = service.versions(VersionsRequest {
app_name: "my_app".to_string(),
user_id: "user_123".to_string(),
session_id: "session_456".to_string(),
file_name: "document.pdf".to_string(),
}).await?;
println!("Available versions: {:?}", response.versions);
// Output: [3, 2, 1] (按最新到最旧排序)
版本控制
Artifacts 支持自动版本控制:
- 在保存时不指定版本时,系统会从最新版本自动递增
- 首次保存时会分配版本 1
- 每次后续保存都会使版本号递增
- 您可以加载、删除或查询特定版本
// First save - becomes version 1
let v1 = service.save(SaveRequest {
file_name: "data.json".to_string(),
part: Part::Text { text: "v1 data".to_string() },
version: None,
// ... other fields
}).await?;
assert_eq!(v1.version, 1);
// Second save - becomes version 2
let v2 = service.save(SaveRequest {
file_name: "data.json".to_string(),
part: Part::Text { text: "v2 data".to_string() },
version: None,
// ... other fields
}).await?;
assert_eq!(v2.version, 2);
// Load latest (version 2)
let latest = service.load(LoadRequest {
file_name: "data.json".to_string(),
version: None,
// ... other fields
}).await?;
// Load specific version
let old = service.load(LoadRequest {
file_name: "data.json".to_string(),
version: Some(1),
// ... other fields
}).await?;
命名空间作用域
Artifacts 可以在两个级别上进行作用域划分:
会话作用域(默认)
默认情况下,artifacts 作用域限定在特定会话。每个会话都有自己独立的 artifact 命名空间:
// Session 1
service.save(SaveRequest {
session_id: "session_1".to_string(),
file_name: "notes.txt".to_string(),
// ... other fields
}).await?;
// Session 2 - different artifact with same name
service.save(SaveRequest {
session_id: "session_2".to_string(),
file_name: "notes.txt".to_string(),
// ... other fields
}).await?;
// These are two separate artifacts
用户作用域
带有 user: 前缀的 Artifacts 在用户的所有会话中共享:
// Save in session 1
service.save(SaveRequest {
session_id: "session_1".to_string(),
file_name: "user:profile.jpg".to_string(), // user: prefix
// ... other fields
}).await?;
// Load in session 2 - same artifact
let profile = service.load(LoadRequest {
session_id: "session_2".to_string(),
file_name: "user:profile.jpg".to_string(),
// ... other fields
}).await?;
user: 前缀支持:
- 在多个对话中共享数据
- 持久的用户偏好设置
- 用户级缓存
InMemoryArtifactService
InMemoryArtifactService 提供了一个适用于开发和测试的内存实现:
use adk_artifact::InMemoryArtifactService;
use std::sync::Arc;
let service = Arc::new(InMemoryArtifactService::new());
// Use with agents
let agent = LlmAgentBuilder::new("my_agent")
.model(model)
.build()?;
// The service can be passed to runners or used directly
注意:数据不会持久化到磁盘。对于生产用途,请考虑实现一个由数据库或云存储支持的自定义 ArtifactService。
ScopedArtifacts
ScopedArtifacts 包装器通过自动注入会话上下文来简化 artifact 操作:
use adk_artifact::{ScopedArtifacts, InMemoryArtifactService};
use std::sync::Arc;
let service = Arc::new(InMemoryArtifactService::new());
let artifacts = ScopedArtifacts::new(
service,
"my_app".to_string(),
"user_123".to_string(),
"session_456".to_string(),
);
// Simple API - no need to specify app/user/session
let version = artifacts.save("file.txt", &Part::Text {
text: "content".to_string()
}).await?;
let part = artifacts.load("file.txt").await?;
let files = artifacts.list().await?;
这与通过 ToolContext::artifacts() 和 CallbackContext::artifacts() 提供的接口相同。
常见模式
使用多模态模型进行图像分析
当您希望 LLM 分析作为 artifact 存储的图像时,您需要使用 BeforeModel callback 将图像直接注入到 LLM 请求中。这遵循了 adk-go 的模式。
为什么不使用 tool? LLM APIs 中的 Tool 响应是 JSON 文本。如果 tool 返回图像数据(即使是 base64-encoded 编码),模型也会将其视为文本,而不是实际图像。为了实现真正的多模态分析,图像必须作为 Part::InlineData 包含在 conversation content 中。
use adk_rust::prelude::*;
use adk_rust::artifact::{ArtifactService, InMemoryArtifactService, SaveRequest, LoadRequest};
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<()> {
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = Arc::new(GeminiModel::new(&api_key, "gemini-2.5-flash")?);
// Create artifact service and save an image
let artifact_service = Arc::new(InMemoryArtifactService::new());
let image_bytes = std::fs::read("photo.png")?;
artifact_service.save(SaveRequest {
app_name: "image_app".to_string(),
user_id: "user".to_string(),
session_id: "init".to_string(),
file_name: "user:photo.png".to_string(), // user-scoped for cross-session access
part: Part::InlineData {
data: image_bytes,
mime_type: "image/png".to_string(),
},
version: None,
}).await?;
// Clone for use in callback
let callback_service = artifact_service.clone();
let agent = LlmAgentBuilder::new("image_analyst")
.description("Analyzes images")
.instruction("You are an image analyst. Describe what you see in the image.")
.model(model)
// Use BeforeModel callback to inject image into the request
.before_model_callback(Box::new(move |_ctx, mut request| {
let service = callback_service.clone();
Box::pin(async move {
// Load the image artifact
if let Ok(response) = service.load(LoadRequest {
app_name: "image_app".to_string(),
user_id: "user".to_string(),
session_id: "init".to_string(),
file_name: "user:photo.png".to_string(),
version: None,
}).await {
// Inject image into the last user content
if let Some(last_content) = request.contents.last_mut() {
if last_content.role == "user" {
last_content.parts.push(response.part);
}
}
}
// Continue with the modified request
Ok(BeforeModelResult::Continue(request))
})
}))
.build()?;
// Now when users ask "What's in the image?", the model will see the actual image
Ok(())
}
要点:
- 使用
BeforeModelResult::Continue(request)将修改后的请求传递给model。 - 如果您想返回
cached response,请使用BeforeModelResult::Skip(response)。 - 图像作为
Part::InlineData注入,Gemini将其解释为实际图像数据。 - 对于应跨
session访问的artifact,请使用user:前缀。
PDF 文档分析
Gemini models 可以使用相同的 BeforeModel callback 模式原生处理 PDF 文档。PDF 以 MIME type application/pdf 注入:
// Save PDF as artifact
artifact_service.save(SaveRequest {
app_name: "my_app".to_string(),
user_id: "user".to_string(),
session_id: "init".to_string(),
file_name: "user:document.pdf".to_string(),
part: Part::InlineData {
data: pdf_bytes,
mime_type: "application/pdf".to_string(),
},
version: None,
}).await?;
// Use BeforeModel callback to inject PDF (same pattern as images)
.before_model_callback(Box::new(move |_ctx, mut request| {
let service = callback_service.clone();
Box::pin(async move {
if let Ok(response) = service.load(LoadRequest {
file_name: "user:document.pdf".to_string(),
// ... other fields
}).await {
if let Some(last_content) = request.contents.last_mut() {
if last_content.role == "user" {
last_content.parts.push(response.part);
}
}
}
Ok(BeforeModelResult::Continue(request))
})
}))
Gemini PDF 功能:
- 提取并分析文本内容
- 回答有关文档的问题
- 总结部分或整个文档
- 处理多达约 1000 页
- 支持扫描文档的
OCR
请参阅 examples/artifacts/chat_pdf.rs 获取完整的运行示例。
存储生成的图像
async fn generate_and_save_image(ctx: &ToolContext) -> Result<Value> {
let artifacts = ctx.artifacts();
// Generate image (pseudo-code)
let image_bytes = generate_image().await?;
let version = artifacts.save(
"generated_image.png",
&Part::InlineData {
mime_type: "image/png".to_string(),
data: image_bytes,
}
).await?;
Ok(json!({
"message": "Image saved",
"file": "generated_image.png",
"version": version
}))
}
加载和处理文档
async fn process_document(ctx: &ToolContext, filename: &str) -> Result<Value> {
let artifacts = ctx.artifacts();
// Load the document
let part = artifacts.load(filename).await?;
match part {
Part::InlineData { mime_type, data } => {
// Process based on MIME type
let result = match mime_type.as_str() {
"application/pdf" => process_pdf(&data)?,
"image/jpeg" | "image/png" => process_image(&data)?,
_ => return Err(AdkError::Artifact("Unsupported type".into())),
};
Ok(json!({ "result": result }))
}
_ => Err(AdkError::Artifact("Expected binary data".into())),
}
}
版本历史
async fn show_history(ctx: &ToolContext, filename: &str) -> Result<Value> {
let artifacts = ctx.artifacts();
// Get all files
let files = artifacts.list().await?;
if !files.contains(&filename.to_string()) {
return Ok(json!({ "error": "File not found" }));
}
// Note: versions() is not available on the simple Artifacts trait
// You would need access to the underlying ArtifactService
Ok(json!({
"file": filename,
"exists": true
}))
}
API 参考
有关完整的 API 文档,请参阅:
adk_core::Artifacts- 供 agent 使用的简单 traitadk_artifact::ArtifactService- 完整的服务 traitadk_artifact::InMemoryArtifactService- 内存实现adk_artifact::ScopedArtifacts- 作用域包装器