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
Partobject 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;
};Auto-save input blobs from user messages
When enabled, the runner will automatically save any incoming inlineData parts from the user's message as artifacts before appending the event. Each saved file is named using the pattern artifact_<invocationId>_<index> and the corresponding message part is replaced with a short text placeholder.
- Requires an
artifactServiceto be configured on theRunner - Toggle via
runConfig.saveInputBlobsAsArtifacts - Can be set at agent build time using
withRunConfigor per run
import { agentBuilder, Runner, InMemorySessionService, InMemoryArtifactService } from '@iqai/adk';
// Configure at agent build time
const agent = agentBuilder()
.name('artifact_agent')
.model('gemini-2.5-flash')
.withRunConfig({ saveInputBlobsAsArtifacts: true })
.build();
const runner = new Runner({
appName: 'my_app',
agent,
sessionService: new InMemorySessionService(),
artifactService: new InMemoryArtifactService(),
});
// Or enable per run
// await runner.runAsync({ userId, sessionId, newMessage, runConfig: new RunConfig({ saveInputBlobsAsArtifacts: true }) });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
Where to start
Runner Configuration
Enable auto-save of uploads and wire services
Using in Contexts
Save/load via CallbackContext and ToolContext
Scoping & Versioning
Session vs user filenames and version behavior
Service Implementations
In-memory vs GCS and setup notes
Recipes
Upload → process → save, media, caching
Troubleshooting
Quick fixes for common issues
Best Practices
Performance, security, and ops guidance
File Upload and Processing
Save an uploaded file as an artifact, then persist derived results (e.g., analysis output). This is a minimal end-to-end flow; see the full tool-based version in Recipes.
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
Avoid recomputing expensive results by saving a JSON artifact as a cache entry and reading it on subsequent runs. Use this for deterministic or memoizable outputs.
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
See interfaces and backends in:
- Scoping & Versioning: behavior overview
- Service Implementations: concrete setup and usage
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()
};
}
}Related Topics
How is this guide?