TypeScriptADK-TS
Artifacts

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

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

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()
    };
  }
}