Best Practices
Caching, batching, validation, cleanup, and naming conventions for artifact management in ADK-TS.
This page covers patterns that prevent common mistakes when working with artifacts at scale — from avoiding unnecessary service calls to safely cleaning up files after processing.
Cache frequently read artifacts
Every loadArtifact call hits the backing service. For GCS this means a network round-trip on every callback or tool invocation. If the same artifact is read many times per session and doesn't change often, wrap it in an in-process cache with a TTL:
import type { BaseArtifactService } from "@iqai/adk";
import type { Part } from "@google/genai";
const cache = new Map<string, { part: Part; ts: number }>();
const TTL_MS = 5 * 60 * 1000; // 5 minutes
async function loadWithCache(
svc: BaseArtifactService,
key: {
appName: string;
userId: string;
sessionId: string;
filename: string;
version?: number;
},
): Promise<Part | null> {
const k = `${key.appName}:${key.userId}:${key.sessionId}:${key.filename}:${key.version ?? "latest"}`;
const hit = cache.get(k);
if (hit && Date.now() - hit.ts < TTL_MS) return hit.part;
const part = await svc.loadArtifact(key);
if (part) cache.set(k, { part, ts: Date.now() });
return part;
}This is most useful with GcsArtifactService. InMemoryArtifactService has no network overhead and doesn't need caching.
Batch saves with Promise.all
Awaiting saves one at a time blocks each call until the previous one completes. When you need to save multiple artifacts in a single callback or tool, run them in parallel with Promise.all:
// ❌ Sequential — each save blocks the next
await ctx.saveArtifact("summary.txt", { text: "..." });
await ctx.saveArtifact("metadata.json", {
inlineData: { data: metaB64, mimeType: "application/json" },
});
await ctx.saveArtifact("chart.png", {
inlineData: { data: chartB64, mimeType: "image/png" },
});
// ✅ Parallel — all three saves run at the same time
await Promise.all([
ctx.saveArtifact("summary.txt", { text: "..." }),
ctx.saveArtifact("metadata.json", {
inlineData: { data: metaB64, mimeType: "application/json" },
}),
ctx.saveArtifact("chart.png", {
inlineData: { data: chartB64, mimeType: "image/png" },
}),
]);Validate before saving
When the content or filename comes from user input, validate it before calling saveArtifact. Without checks, a user can pass unsupported MIME types, very large files that exhaust memory or storage, or filenames with special characters that break downstream tooling.
import type { Part } from "@google/genai";
const ALLOWED_MIME = new Set([
"text/plain",
"text/csv",
"application/json",
"image/png",
"image/jpeg",
"application/pdf",
]);
const MAX_BYTES = 50 * 1024 * 1024; // 50 MB
function validatePart(
filename: string,
part: Part,
): { ok: boolean; error?: string } {
// Only allow safe filename characters (strip user: prefix before checking)
if (!/^[a-zA-Z0-9._-]+$/.test(filename.replace("user:", "")))
return { ok: false, error: "Invalid filename characters" };
// text and fileData parts have no binary payload to size-check
if (!part.inlineData) return { ok: true };
const { mimeType, data } = part.inlineData;
if (!ALLOWED_MIME.has(mimeType))
return { ok: false, error: `Unsupported MIME type: ${mimeType}` };
const size = Buffer.from(data, "base64").length;
if (size > MAX_BYTES)
return {
ok: false,
error: `File too large: ${size} bytes (max ${MAX_BYTES})`,
};
return { ok: true };
}Naming conventions
Consistent filename prefixes make it easy to list, filter, and clean up related artifacts programmatically:
| Prefix | Scope | Example | Notes |
|---|---|---|---|
| (none) | Session | "report.pdf", "summary.txt" | Default — discarded on session end |
user: | User | "user:avatar.png", "user:settings.json" | Persists across sessions |
temp_ | Session | "temp_upload.csv", "temp_parse_result.json" | Delete immediately after use |
cache_ | Either | "cache_analysis.json" | Safe to delete and regenerate |
Cleanup and deletion
deleteArtifact removes all versions of a filename permanently and is only available on the service directly — not on context objects.
Deletion is permanent and removes all versions. There is no undo. Test cleanup logic in a staging environment before running in production.
To delete a single file after processing:
import { GcsArtifactService } from "@iqai/adk";
const svc = new GcsArtifactService("my-bucket");
await svc.deleteArtifact({
appName: "my_app",
userId: "u1",
sessionId: "s1",
filename: "temp_upload.csv",
});To delete all artifacts matching a prefix — for example, cleaning up all temp_ files after a session:
import type { BaseArtifactService } from "@iqai/adk";
async function deleteByPrefix(
svc: BaseArtifactService,
args: { appName: string; userId: string; sessionId: string },
prefix: string,
) {
const keys = await svc.listArtifactKeys(args);
await Promise.all(
keys
.filter(k => k.startsWith(prefix))
.map(filename => svc.deleteArtifact({ ...args, filename })),
);
}
// Example: delete all temp_ artifacts for a session
await deleteByPrefix(
svc,
{ appName: "my_app", userId: "u1", sessionId: "s1" },
"temp_",
);For GCS, you can also configure a bucket lifecycle policy to auto-delete blobs older than a set number of days — useful for enforcing data retention limits without manual cleanup:
{
"lifecycle": {
"rule": [{ "action": { "type": "Delete" }, "condition": { "age": 90 } }]
}
}gsutil lifecycle set lifecycle.json gs://my-artifacts-bucketRetry transient errors
GCS calls can fail transiently due to network blips or temporary service unavailability. Rather than letting a single failure crash a callback, wrap GCS operations in exponential backoff. The delay doubles on each attempt, giving the service time to recover:
async function withRetry<T>(
op: () => Promise<T>,
attempts = 3,
baseDelayMs = 1000,
): Promise<T> {
let lastError: unknown;
for (let i = 0; i <= attempts; i++) {
try {
return await op();
} catch (e) {
lastError = e;
if (i < attempts) {
await new Promise(r => setTimeout(r, baseDelayMs * 2 ** i));
}
}
}
throw lastError;
}
// Usage
const artifact = await withRetry(() =>
svc.loadArtifact({
appName: "my_app",
userId: "u1",
sessionId: "s1",
filename: "report.pdf",
}),
);This approach works for any artifact service call — saveArtifact, loadArtifact, deleteArtifact, or listArtifactKeys.