Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
818 changes: 139 additions & 679 deletions apps/docs/content/docs/framework/artifacts/best-practices.mdx

Large diffs are not rendered by default.

483 changes: 228 additions & 255 deletions apps/docs/content/docs/framework/artifacts/context-integration.mdx

Large diffs are not rendered by default.

509 changes: 48 additions & 461 deletions apps/docs/content/docs/framework/artifacts/index.mdx

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion apps/docs/content/docs/framework/artifacts/meta.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"icon": "FileText",
"pages": [
"index",
"runner-configuration",
"context-integration",
"scoping-and-versioning",
Expand Down
120 changes: 91 additions & 29 deletions apps/docs/content/docs/framework/artifacts/recipes.mdx
Original file line number Diff line number Diff line change
@@ -1,90 +1,152 @@
---
title: Recipes
description: Practical patterns for saving, loading, and transforming artifacts
description: Copy-paste patterns for common artifact workflows in ADK-TS — upload processing, media generation, and result caching.
---

## Upload → Process → Save Results
import { Cards, Card } from "fumadocs-ui/components/card";

These are self-contained patterns you can drop into your own agents. Each recipe covers a common workflow — pick the one that matches your use case and adapt the filenames and logic to fit.

## Upload → Process → Save results

When a user uploads a file (e.g. via auto-save or a direct tool call), you often want to parse it, compute something, and save the result as a new artifact so the agent can reference it later without re-processing.

This recipe receives a CSV filename, parses it, and saves a JSON analysis artifact. Because it uses `createTool`, the agent can call it by name once the upload is in place:

```typescript
import { createTool } from "@iqai/adk";
import * as z from "zod";

export const analyzeCsv = createTool({
name: "analyze_csv",
description: "Analyze uploaded CSV",
description: "Analyze an uploaded CSV file and save the results as JSON",
schema: z.object({ filename: z.string() }),
fn: async ({ filename }, ctx) => {
// Confirm the file exists in this session before trying to load it
const files = await ctx.listArtifacts();
if (!files.includes(filename)) return { error: "File not found", files };
if (!files.includes(filename))
return { error: "File not found", available: files };

const artifact = await ctx.loadArtifact(filename);
if (!artifact?.inlineData) return { error: "Could not load file" };

// Decode and parse the CSV
const text = Buffer.from(artifact.inlineData.data, "base64").toString(
"utf-8",
);
const [header, ...rows] = text.split("\n").filter(Boolean);
const headers = header.split(",").map(h => h.trim());

const result = {
summary: "CSV analysis complete",
rowCount: rows.length,
columnCount: headers.length,
headers,
};

const resultArtifact = {
// Save the analysis as a new artifact
const outputFilename = `analysis_${filename.replace(/\.[^/.]+$/, "")}.json`;
await ctx.saveArtifact(outputFilename, {
inlineData: {
data: Buffer.from(JSON.stringify(result, null, 2)).toString("base64"),
mimeType: "application/json",
},
};
const out = `analysis_${filename.replace(/\.[^/.]+$/, "")}.json`;
await ctx.saveArtifact(out, resultArtifact);
return { output: out, ...result };
});

return { output: outputFilename, ...result };
},
});
```

## Generate Media → Save + Reuse
## Generate media → Save + reuse

If your agent generates an image, SVG, or other binary output that's expensive to produce, save it as an artifact on first generation and load the cached version on subsequent calls instead of regenerating it.

This recipe generates an SVG banner inside a callback and caches it — repeated invocations return the stored version immediately:

```typescript
import type { CallbackContext } from "@iqai/adk";

export async function generateSvg(ctx: CallbackContext) {
const svg = `<svg width="200" height="100" xmlns="http://www.w3.org/2000/svg"><rect width="200" height="100" fill="black"/></svg>`;
await ctx.saveArtifact("banner.svg", {
export async function generateAndCacheSvg(ctx: CallbackContext) {
const cacheKey = "banner.svg";

// Return the cached version if it already exists
const cached = await ctx.loadArtifact(cacheKey);
if (cached?.inlineData) {
return Buffer.from(cached.inlineData.data, "base64").toString("utf-8");
}

// Generate the SVG and save it for future calls
const svg = `<svg width="200" height="100" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="100" fill="black"/>
</svg>`;

await ctx.saveArtifact(cacheKey, {
inlineData: {
data: Buffer.from(svg).toString("base64"),
mimeType: "image/svg+xml",
},
});

// Later
const loaded = await ctx.loadArtifact("banner.svg");
if (loaded) {
const svgText = Buffer.from(loaded.inlineData.data, "base64").toString(
"utf-8",
);
// Use svgText (serve to UI, embed in response, etc.)
}
return svg;
}
```

## Cache Expensive Binary Output
## Cache expensive computation

Use the same check-before-compute pattern for any operation with a deterministic or memoizable output — model inference on a fixed input, a database aggregation, a report generation step.

This recipe checks for a cached result before running the expensive work, and saves it so the next call skips the computation entirely:

```typescript
import type { CallbackContext } from "@iqai/adk";

export async function cachedChart(ctx: CallbackContext) {
const cacheKey = "chart.png";
export async function cachedComputation(ctx: CallbackContext) {
const cacheKey = "result.json";

// Return early if the result is already cached
const cached = await ctx.loadArtifact(cacheKey);
if (cached) return { info: "hit", cacheKey };
if (cached) return { hit: true, cacheKey };

// Run the expensive operation and cache the result
const result = { value: Math.random(), computedAt: Date.now() };

const png = Buffer.from(/* expensive render */);
await ctx.saveArtifact(cacheKey, {
inlineData: { data: png.toString("base64"), mimeType: "image/png" },
inlineData: {
data: Buffer.from(JSON.stringify(result)).toString("base64"),
mimeType: "application/json",
},
});
return { info: "miss", cacheKey };

return { hit: false, cacheKey };
}
```

## Save and load plain text

For simple string content you don't need `inlineData` at all. The `text` Part format is shorter to write and easier to read back — no base64 encoding or decoding required:

```typescript
// Save
await ctx.saveArtifact("notes.txt", { text: "Meeting notes: ..." });

// Load — artifact.text is the string directly
const notes = await ctx.loadArtifact("notes.txt");
console.log(notes?.text);
```

Use this for log output, agent-generated summaries, plain JSON strings, or any content where binary encoding adds no value.

## Next steps

<Cards>
<Card
title="🔌 Context Integration"
description="Full API reference for CallbackContext and ToolContext artifact methods"
href="/docs/framework/artifacts/context-integration"
/>
<Card
title="🗂️ Scoping & Versioning"
description="Use user: prefix for cross-session persistence"
href="/docs/framework/artifacts/scoping-and-versioning"
/>
</Cards>
116 changes: 82 additions & 34 deletions apps/docs/content/docs/framework/artifacts/runner-configuration.mdx
Original file line number Diff line number Diff line change
@@ -1,71 +1,99 @@
---
title: Runner Configuration
description: Configure artifact behavior at run-time, including auto-saving user input blobs
description: Wire an artifact service to your ADK-TS Runner and enable automatic saving of user-uploaded blobs.
---

import { Callout } from "fumadocs-ui/components/callout";
import { Cards, Card } from "fumadocs-ui/components/card";

## Enable auto-save for user input blobs
Before any callback or tool can save or load an artifact, the `Runner` needs an `artifactService`. This page covers how to wire one up and how to enable auto-saving of binary uploads sent by the user.

When enabled, the runner automatically saves any incoming `inlineData` parts from the user's message as artifacts before appending the event. Each saved artifact is named `artifact_<invocationId>_<index>` and the original message part is replaced with a short text placeholder.
## Wiring the service

Requirements:
Without an `artifactService`, every `saveArtifact` and `loadArtifact` call will throw at runtime. The recommended way is to pass it through `AgentBuilder` using `.withArtifactService()`:

- An `artifactService` must be configured on the `Runner`
- Enable via `withRunConfig({ saveInputBlobsAsArtifacts: true })` on the agent builder, or per-run via `RunConfig`
```typescript
import { AgentBuilder, InMemoryArtifactService } from "@iqai/adk";

const { runner } = await AgentBuilder.create("my_agent")
.withModel("gemini-2.5-flash")
.withArtifactService(new InMemoryArtifactService())
.build();
```

If you're constructing the `Runner` directly, pass `artifactService` in the options:

```typescript
import {
agentBuilder,
Runner,
LlmAgent,
InMemorySessionService,
InMemoryArtifactService,
RunConfig,
} 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,
agent: new LlmAgent({
name: "my_agent",
model: "gemini-2.5-flash",
description: "An agent that does things",
}),
sessionService: new InMemorySessionService(),
artifactService: new InMemoryArtifactService(),
});
```

// Or enable per run
// await runner.runAsync({
// userId,
// sessionId,
// newMessage,
// runConfig: new RunConfig({ saveInputBlobsAsArtifacts: true })
// });
Use `InMemoryArtifactService` during development and switch to `GcsArtifactService` for production. Both implement the same interface, so no other code needs to change.

## Auto-save user uploads

Normally, binary content sent by the user (images, files) arrives as `inlineData` parts inside the message and the agent has to process them itself. When `saveInputBlobsAsArtifacts` is enabled, the runner automatically saves each `inlineData` part as an artifact before the agent sees the message. The original part is replaced with a short text placeholder so the agent receives a clean, text-only message.

Each saved artifact is named `artifact_<invocationId>_<index>`, where `index` is the position of the part in the message.

To enable this for every run, set it at build time via `withRunConfig`:

```typescript
import { AgentBuilder, InMemoryArtifactService } from "@iqai/adk";

const { runner } = await AgentBuilder.create("upload_agent")
.withModel("gemini-2.5-flash")
.withArtifactService(new InMemoryArtifactService())
.withRunConfig({ saveInputBlobsAsArtifacts: true })
.build();
```

<Callout type="info">
To enable it only for specific runs, pass a `RunConfig` to `runAsync` instead:

Artifacts saved by this mechanism use the incoming part's `inlineData` (base64
string + MIME type) as-is. Ensure your front-end encodes uploads as base64 and
sets a correct `mimeType`.
```typescript
import { RunConfig } from "@iqai/adk";

</Callout>
const message = {};

## Minimal example: sending an image upload
for await (const event of runner.runAsync({
userId: "u1",
sessionId: "s1",
newMessage: message,
runConfig: new RunConfig({ saveInputBlobsAsArtifacts: true }),
})) {
// handle events
}
```

## Sending a file upload

When auto-save is enabled, any message you send with `inlineData` parts will trigger it. Encode the raw bytes as base64 and set the correct `mimeType`:

```typescript
import { readFileSync } from "node:fs";
import type { Content } from "@google/genai";

const imageBytes = Buffer.from(/* raw PNG bytes */);
const message: Content = {
role: "user",
parts: [
{
inlineData: {
data: imageBytes.toString("base64"),
data: readFileSync("./upload.png").toString("base64"),
mimeType: "image/png",
},
},
Expand All @@ -77,8 +105,28 @@ for await (const event of runner.runAsync({
sessionId: "s1",
newMessage: message,
})) {
// Events stream
// The image is auto-saved as "artifact_<invocationId>_0"
// The agent receives a text placeholder instead of the raw bytes
}

// The image will be saved as an artifact, e.g. "artifact_<invocationId>_0"
```

<Callout type="info">
The artifact name follows the pattern `artifact_<invocationId>_<index>`. If
the user sends multiple files in one message, they are saved as `_0`, `_1`,
`_2`, and so on. Your callbacks and tools can then load them by name.
</Callout>

## Next steps

<Cards>
<Card
title="🔌 Context Integration"
description="Access saved artifacts inside callbacks and tools"
href="/docs/framework/artifacts/context-integration"
/>
<Card
title="💾 Service Implementations"
description="Switch from InMemory to GCS for production"
href="/docs/framework/artifacts/service-implementations"
/>
</Cards>
Loading
Loading