Artifacts
Manage named, versioned binary data for rich agent interactions with files and media
Artifacts provide a mechanism for managing named, versioned binary data associated with user sessions. They enable agents to handle data beyond simple text strings, supporting rich interactions with files, images, audio, and other binary formats.
Overview
Artifacts are pieces of binary data identified by unique filenames within specific scopes, with automatic versioning for data evolution. They bridge the gap between simple session state and complex file management needs.
Core Characteristics
- Binary Data Storage: Handle any type of binary content (images, PDFs, audio, video)
- Named Identification: Use descriptive filenames for easy reference
- Automatic Versioning: Each save creates a new version automatically
- Scoped Access: Session-specific or user-wide accessibility
- Standard Representation: Consistent
Part
object format from@google/genai
Beyond Text Storage
While session state is perfect for configuration and conversational context, artifacts are designed for binary data, large files, and content that needs versioning.
Quick Start
Basic Artifact Operations
Here's how to work with artifacts in your agent callbacks:
import { LlmAgent, CallbackContext, Runner } from '@iqai/adk';
import { InMemoryArtifactService } from '@iqai/adk';
// Set up artifact service
const artifactService = new InMemoryArtifactService();
// Use artifacts in callbacks
const beforeAgentCallback = async (callbackContext: CallbackContext) => {
try {
// Save a simple text artifact
const textArtifact = {
inlineData: {
data: Buffer.from('Hello, World!').toString('base64'),
mimeType: 'text/plain'
}
};
const version = await callbackContext.saveArtifact('greeting.txt', textArtifact);
console.log(`Saved greeting.txt version ${version}`);
// Load an existing artifact
const loadedArtifact = await callbackContext.loadArtifact('greeting.txt');
if (loadedArtifact) {
const text = Buffer.from(loadedArtifact.inlineData.data, 'base64').toString();
console.log(`Loaded text: ${text}`);
}
} catch (error) {
console.warn('Artifact operation failed:', error);
}
return undefined;
};
// Create agent with artifact service
const agent = new LlmAgent({
name: "artifact_agent",
model: "gemini-2.5-flash",
description: "Agent that works with artifacts",
instruction: "You are helpful",
beforeAgentCallback
});
// Create runner with artifact service
const runner = new Runner({
appName: "my_app",
agent,
sessionService: new InMemorySessionService(),
artifactService // Configure artifact service
});
Working with Different Data Types
const mediaArtifactCallback = async (callbackContext: CallbackContext) => {
// Save an image artifact
const imageData = Buffer.from(/* your image data */);
const imageArtifact = {
inlineData: {
data: imageData.toString('base64'),
mimeType: 'image/png'
}
};
await callbackContext.saveArtifact('generated_chart.png', imageArtifact);
// Save a JSON data artifact
const jsonData = { results: [1, 2, 3], timestamp: new Date().toISOString() };
const jsonArtifact = {
inlineData: {
data: Buffer.from(JSON.stringify(jsonData)).toString('base64'),
mimeType: 'application/json'
}
};
await callbackContext.saveArtifact('analysis_results.json', jsonArtifact);
return undefined;
};
Artifact Scoping
Artifacts can be scoped to different access levels:
Session-Specific Artifacts
Artifacts tied to individual conversation sessions:
// Regular filename - session scoped
await callbackContext.saveArtifact('temp_processing.csv', csvArtifact);
Characteristics:
- Limited to current session context
- Automatically cleaned up when sessions end
- Ideal for temporary processing and conversation-specific files
User-Specific Artifacts
Artifacts persisting across all user sessions:
// "user:" prefix - user scoped
await callbackContext.saveArtifact('user:profile_picture.png', imageArtifact);
Characteristics:
- Available across all user sessions within the application
- Persist beyond individual conversations
- Enable long-term user data management
Namespace Selection
Choose appropriate artifact scoping based on data lifecycle requirements - session for temporary content, user for persistent data.
Versioning System
Artifacts automatically maintain version history:
const versioningExample = async (callbackContext: CallbackContext) => {
// First save - creates version 0
const v0 = await callbackContext.saveArtifact('document.txt', textArtifact1);
console.log(`Version: ${v0}`); // 0
// Second save - creates version 1
const v1 = await callbackContext.saveArtifact('document.txt', textArtifact2);
console.log(`Version: ${v1}`); // 1
// Load specific version
const oldVersion = await callbackContext.loadArtifact('document.txt', 0);
// Load latest version (default)
const latestVersion = await callbackContext.loadArtifact('document.txt');
return undefined;
};
Version Characteristics:
- Version numbers start at 0 and increment automatically
- Each save operation creates a new version
- Access latest version by default or specify version explicitly
- Complete version history maintained
Common Use Cases
📄 Generated Reports
Create and store PDF reports, CSV exports, and analysis documents
📁 File Processing
Handle user uploads for analysis, transformation, and processing
🎨 Media Generation
Generate and store images, audio, video, and other media content
⚡ Caching
Cache expensive computation results and frequently accessed content
File Upload and Processing
const fileProcessingCallback = async (callbackContext: CallbackContext) => {
// Simulate receiving an uploaded file
const uploadedFileData = Buffer.from('CSV data here');
const csvArtifact = {
inlineData: {
data: uploadedFileData.toString('base64'),
mimeType: 'text/csv'
}
};
// Save uploaded file
await callbackContext.saveArtifact('uploaded_data.csv', csvArtifact);
// Process and save results
const processedData = { summary: 'Analysis complete', rows: 100 };
const resultsArtifact = {
inlineData: {
data: Buffer.from(JSON.stringify(processedData)).toString('base64'),
mimeType: 'application/json'
}
};
await callbackContext.saveArtifact('processing_results.json', resultsArtifact);
return undefined;
};
Caching Expensive Operations
const cachingCallback = async (callbackContext: CallbackContext) => {
const cacheKey = 'expensive_computation_result.json';
// Check for cached result
const cachedResult = await callbackContext.loadArtifact(cacheKey);
if (cachedResult) {
console.log('Using cached result');
const data = JSON.parse(
Buffer.from(cachedResult.inlineData.data, 'base64').toString()
);
callbackContext.state.set('computation_result', data);
} else {
console.log('Computing new result');
// Perform expensive computation
const result = { value: Math.random(), timestamp: Date.now() };
// Cache the result
const artifact = {
inlineData: {
data: Buffer.from(JSON.stringify(result)).toString('base64'),
mimeType: 'application/json'
}
};
await callbackContext.saveArtifact(cacheKey, artifact);
callbackContext.state.set('computation_result', result);
}
return undefined;
};
Service Architecture
The artifact system is built around the BaseArtifactService
interface:
Core Operations
interface BaseArtifactService {
// Save artifact with automatic versioning
saveArtifact(args: {
appName: string;
userId: string;
sessionId: string;
filename: string;
artifact: Part;
}): Promise<number>;
// Load specific version or latest
loadArtifact(args: {
appName: string;
userId: string;
sessionId: string;
filename: string;
version?: number;
}): Promise<Part | null>;
// List available artifact filenames
listArtifactKeys(args: {
appName: string;
userId: string;
sessionId: string;
}): Promise<string[]>;
// Delete artifact and all versions
deleteArtifact(args: {
appName: string;
userId: string;
sessionId: string;
filename: string;
}): Promise<void>;
// List all versions for a filename
listVersions(args: {
appName: string;
userId: string;
sessionId: string;
filename: string;
}): Promise<number[]>;
}
Available Implementations
- InMemoryArtifactService: Fast temporary storage for development and testing
- GcsArtifactService: Google Cloud Storage backend for production scalability
- Custom Services: Implement your own storage backend
Documentation Structure
🏗️ Service Implementations
Different artifact service backends and their configurations
🔧 Context Integration
Using artifacts through CallbackContext and ToolContext
📋 Best Practices
Performance, security, versioning, and production considerations
Error Handling
Always implement proper error handling for artifact operations:
const robustArtifactCallback = async (callbackContext: CallbackContext) => {
try {
const artifact = await callbackContext.loadArtifact('user_config.json');
if (artifact) {
const config = JSON.parse(
Buffer.from(artifact.inlineData.data, 'base64').toString()
);
callbackContext.state.set('user_config', config);
} else {
console.log('No existing config found, using defaults');
callbackContext.state.set('user_config', getDefaultConfig());
}
} catch (error) {
console.error('Failed to load user config:', error);
// Fallback to defaults
callbackContext.state.set('user_config', getDefaultConfig());
// Optionally notify the user
return {
role: 'model',
parts: [{ text: 'Configuration could not be loaded. Using default settings.' }]
};
}
return undefined;
};
function getDefaultConfig() {
return {
theme: 'light',
language: 'en',
notifications: true
};
}
Integration Examples
With Tools
import { BaseTool, ToolContext } from '@iqai/adk';
class FileAnalysisTool extends BaseTool {
async runAsync(args: { filename: string }, context: ToolContext) {
try {
// List available artifacts
const availableFiles = await context.listArtifacts();
if (!availableFiles.includes(args.filename)) {
return { error: `File ${args.filename} not found` };
}
// Load and analyze the file
const artifact = await context.loadArtifact(args.filename);
if (!artifact) {
return { error: `Could not load ${args.filename}` };
}
// Perform analysis based on MIME type
const analysis = this.analyzeFile(artifact);
// Save analysis results
const resultArtifact = {
inlineData: {
data: Buffer.from(JSON.stringify(analysis)).toString('base64'),
mimeType: 'application/json'
}
};
await context.saveArtifact(`analysis_${args.filename}.json`, resultArtifact);
return {
analysis,
result_saved: `analysis_${args.filename}.json`
};
} catch (error) {
return { error: `Analysis failed: ${error.message}` };
}
}
private analyzeFile(artifact: Part) {
// Implementation based on MIME type
return {
size: artifact.inlineData.data.length,
type: artifact.inlineData.mimeType,
timestamp: new Date().toISOString()
};
}
}