아티팩트
아티팩트는 에이전트 애플리케이션 내에서 이진 데이터(이미지, PDF, 오디오 파일 등)를 저장하고 검색하는 방법을 제공합니다. 아티팩트 시스템은 버전 관리, 네임스페이스 범위 지정 및 세션 간 데이터 영속성을 처리합니다.
개요
ADK-Rust의 아티팩트 시스템은 다음으로 구성됩니다:
- Part: MIME 타입으로 텍스트 또는 이진 데이터를 담을 수 있는 핵심 데이터 표현
- ArtifactService: 아티팩트 저장 작업을 정의하는 트레잇
- InMemoryArtifactService: 개발 및 테스트를 위한 인메모리 구현
- ScopedArtifacts: 세션 컨텍스트를 자동으로 처리하여 아티팩트 작업을 단순화하는 래퍼
아티팩트는 애플리케이션, 사용자, 세션별로 범위가 지정되어 격리 및 구성을 제공합니다. 파일은 세션 범위(기본값)이거나 사용자 범위( user: 접두사 사용)일 수 있습니다.
Part 표현
Part enum은 아티팩트로 저장될 수 있는 데이터를 나타냅니다:
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 },
}
아티팩트의 경우 주로 다음을 사용합니다:
Part::Text(텍스트 데이터용)Part::InlineData(MIME 타입이 있는 이진 데이터용)
기본 사용법
아티팩트를 사용하는 가장 간단한 방법은 에이전트 컨텍스트에서 사용할 수 있는 Artifacts 트레잇을 이용하는 것입니다:
use adk_rust::prelude::*;
// 에이전트 도구 또는 콜백 내에서
async fn save_report(ctx: &ToolContext) -> Result<Value> {
let artifacts = ctx.artifacts();
// 텍스트 데이터 저장
let version = artifacts.save(
"report.txt",
&Part::Text { text: "Report content".to_string() }
).await?;
// 이진 데이터 저장
let image_data = vec![0xFF, 0xD8, 0xFF]; // JPEG 헤더
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 트레이트는 아티팩트 관리를 위한 핵심 작업을 정의합니다:
#[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>;
}
저장 작업
자동 또는 명시적 버전 지정을 통해 아티팩트를 저장합니다:
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;
// 최신 버전 불러오기
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?;
// 특정 버전 불러오기
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), // 버전 2 불러오기
}).await?;
match response.part {
Part::InlineData { mime_type, data } => {
println!("Loaded {} bytes of {}", data.len(), mime_type);
}
_ => {}
}
목록 작업
세션의 모든 아티팩트를 나열합니다:
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;
// 특정 버전 삭제
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), // 버전 1 삭제
}).await?;
// 모든 버전 삭제
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, // 모든 버전 삭제
}).await?;
버전 목록 작업
아티팩트의 모든 버전을 나열합니다:
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);
// 출력: [3, 2, 1] (최신순 정렬)
버전 관리
아티팩트는 자동 버전 관리를 지원합니다:
- 버전을 지정하지 않고 저장하면, 시스템은 최신 버전에서 자동으로 버전을 증가시킵니다.
- 첫 저장 시 버전 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?;
네임스페이스 범위 지정
아티팩트는 두 가지 수준으로 범위를 지정할 수 있습니다:
세션 범위 (기본값)
기본적으로 아티팩트는 특정 Session에 범위가 지정됩니다. 각 Session은 자체적으로 격리된 아티팩트 네임스페이스를 가집니다:
// 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: 접두사가 붙은 아티팩트는 한 사용자의 모든 Session에서 공유됩니다:
// 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 래퍼는 Session 컨텍스트를 자동으로 주입하여 아티팩트 작업을 단순화합니다:
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이 아티팩트로 저장된 이미지를 분석하도록 하려면, BeforeModel callback을 사용하여 이미지를 LLM 요청에 직접 주입해야 합니다. 이는 adk-go 패턴을 따릅니다.
Tool을 사용하지 않는 이유는 무엇인가요? LLM API의 Tool 응답은 JSON 텍스트입니다. Tool이 이미지 데이터(심지어 base64로 인코딩된)를 반환하더라도 모델은 이를 실제 이미지가 아닌 텍스트로 인식합니다. 진정한 멀티모달 분석을 위해서는 이미지가 대화 내용에 Part::InlineData로 포함되어야 합니다.
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)를 사용하세요. - 대신 캐시된 응답을 반환하려면
BeforeModelResult::Skip(response)를 사용하세요. - 이미지는
Part::InlineData로 주입되며, 이는 Gemini가 실제 이미지 데이터로 해석합니다. - 세션 간에 액세스해야 하는 아티팩트에는
user:접두사를 사용하세요.
PDF 문서 분석
Gemini 모델은 동일한 BeforeModel callback 패턴을 사용하여 PDF 문서를 기본적으로 처리할 수 있습니다. PDF는 application/pdf MIME 유형으로 주입됩니다.
// 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- 에이전트 사용을 위한 간단한 트레이트adk_artifact::ArtifactService- 전체 서비스 트레이트adk_artifact::InMemoryArtifactService- 인메모리 구현adk_artifact::ScopedArtifacts- 스코프 지정 래퍼
관련
이전: ← Callbacks | 다음: Events →