アーティファクト (Artifacts)¶
ADKにおいて、アーティファクト(Artifacts)は、特定のユーザーインタラクションセッションに関連付けられるか、複数のセッションにわたって永続的にユーザーに関連付けられる、名前付きでバージョン管理されたバイナリデータを管理するための重要なメカニズムです。これにより、エージェントやツールは単純なテキスト文字列を超えて、ファイル、画像、音声、その他のバイナリ形式を含む、よりリッチなインタラクションを処理できるようになります。
Note
プリミティブの特定のパラメータやメソッド名は、SDKの言語によって若干異なる場合があります(例:Pythonでは save_artifact、Javaでは saveArtifact)。詳細については、各言語固有のAPIドキュメントを参照してください。
アーティファクトとは?¶
-
定義: アーティファクトは、本質的に、特定のスコープ(セッションまたはユーザー)内で一意の
filename文字列によって識別されるバイナリデータの一部(ファイルの内容など)です。同じファイル名でアーティファクトを保存するたびに、新しいバージョンが作成されます。 -
表現: アーティファクトは、標準の
google.genai.types.Partオブジェクトを使用して一貫して表現されます。コアデータは通常、Partのインラインデータ構造内に格納され(inline_dataを介してアクセス)、この構造自体には以下が含まれます:data: バイト形式の生のバイナリコンテンツ。mime_type: データの種類を示す文字列(例:"image/png"、"application/pdf")。これは、後でデータを正しく解釈するために不可欠です。
# アーティファクトが types.Part として表現される方法の例
import google.genai.types as types
# 'image_bytes' にPNG画像のバイナリデータが含まれていると仮定
image_bytes = b'\x89PNG\r\n\x1a\n...' # 実際の画像バイトのプレースホルダー
image_artifact = types.Part(
inline_data=types.Blob(
mime_type="image/png",
data=image_bytes
)
)
# 便利なコンストラクタも使用できます:
# image_artifact_alt = types.Part.from_bytes(data=image_bytes, mime_type="image/png")
print(f"アーティファクトのMIMEタイプ: {image_artifact.inline_data.mime_type}")
print(f"アーティファクトデータ (最初の10バイト): {image_artifact.inline_data.data[:10]}...")
import (
"log"
"google.golang.org/genai"
)
// Create a byte slice with the image data.
imageBytes, err := os.ReadFile("image.png")
if err != nil {
log.Fatalf("Failed to read image file: %v", err)
}
// Create a new artifact with the image data.
imageArtifact := &genai.Part{
InlineData: &genai.Blob{
MIMEType: "image/png",
Data: imageBytes,
},
}
log.Printf("Artifact MIME Type: %s", imageArtifact.InlineData.MIMEType)
log.Printf("Artifact Data (first 8 bytes): %x...", imageArtifact.InlineData.Data[:8])
import com.google.genai.types.Part;
import java.nio.charset.StandardCharsets;
public class ArtifactExample {
public static void main(String[] args) {
// 'imageBytes' にPNG画像のバイナリデータが含まれていると仮定
byte[] imageBytes = {(byte) 0x89, (byte) 0x50, (byte) 0x4E, (byte) 0x47, (byte) 0x0D, (byte) 0x0A, (byte) 0x1A, (byte) 0x0A, (byte) 0x01, (byte) 0x02}; // 実際の画像バイトのプレースホルダー
// Part.fromBytes を使用して画像アーティファクトを作成
Part imageArtifact = Part.fromBytes(imageBytes, "image/png");
System.out.println("アーティファクトのMIMEタイプ: " + imageArtifact.inlineData().get().mimeType().get());
System.out.println(
"アーティファクトデータ (最初の10バイト): "
+ new String(imageArtifact.inlineData().get().data().get(), 0, 10, StandardCharsets.UTF_8)
+ "...");
}
}
- 永続化と管理: アーティファクトは、エージェントやセッションの状態に直接保存されません。その保存と取得は、専用の アーティファクトサービス(Artifact Service) (
google.adk.artifactsで定義されたBaseArtifactServiceの実装)によって管理されます。ADKは、次のようなさまざまな実装を提供します:- テストや一時的な保存のためのインメモリサービス(例:Pythonの
InMemoryArtifactService、google.adk.artifacts.in_memory_artifact_service.pyで定義)。 - Google Cloud Storage (GCS) を使用した永続的な保存のためのサービス(例:Pythonの
GcsArtifactService、google.adk.artifacts.gcs_artifact_service.pyで定義)。 選択されたサービス実装は、データを保存する際に自動的にバージョン管理を処理します。
- テストや一時的な保存のためのインメモリサービス(例:Pythonの
なぜアーティファクトを使用するのか?¶
セッションの state は、小さな設定情報や対話のコンテキスト(文字列、数値、ブール値、小さな辞書/リストなど)を保存するのに適していますが、アーティファクトはバイナリデータや大規模なデータを扱うシナリオ向けに設計されています。
- 非テキストデータの処理: エージェントの機能に関連する画像、音声クリップ、ビデオの一部、PDF、スプレッドシート、その他のファイル形式を簡単に保存・取得できます。
- 大規模データの永続化: セッション状態は一般的に大量のデータを保存するために最適化されていません。アーティファクトは、セッション状態を乱雑にすることなく、より大きなBLOBを永続化するための専用のメカニズムを提供します。
- ユーザーファイル管理: ユーザーがファイルをアップロード(アーティファクトとして保存可能)し、エージェントが生成したファイルを取得またはダウンロード(アーティファクトから読み込み)する機能を提供します。
- 出力の共有: ツールやエージェントが生成したバイナリ出力(PDFレポートや生成画像など)を
save_artifactを介して保存し、後でアプリケーションの他の部分や後続のセッションでも(ユーザー名前空間を使用している場合)アクセスできるようにします。 - バイナリデータのキャッシュ: 計算コストの高い操作(複雑なチャート画像のレンダリングなど)によって生成されるバイナリデータの結果をアーティファクトとして保存し、後続のリクエストで再生成するのを避けます。
本質的に、エージェントが永続化、バージョン管理、または共有が必要なファイルのようなバイナリデータを扱う必要がある場合、ArtifactService によって管理されるアーティファクトがADK内の適切なメカニズムとなります。
一般的なユースケース¶
アーティファクトは、ADKアプリケーション内でバイナリデータを柔軟に扱う方法を提供します。
以下は、それらが価値を発揮する典型的なシナリオです:
-
生成されたレポート/ファイル:
- ツールやエージェントがレポート(例:PDF分析、CSVデータエクスポート、画像チャート)を生成します。
-
ユーザーアップロードの処理:
- ユーザーがフロントエンドインターフェースを介してファイル(例:分析用の画像、要約用のドキュメント)をアップロードします。
-
中間バイナリ結果の保存:
- エージェントが複雑な複数ステップのプロセスを実行し、そのうちの一つのステップが中間バイナリデータ(例:音声合成、シミュレーション結果)を生成します。
-
永続的なユーザーデータ:
- 単純なキーバリューの状態ではない、ユーザー固有の設定やデータを保存します。
-
生成されたバイナリコンテンツのキャッシュ:
- エージェントが特定の入力に基づいて同じバイナリ出力(例:会社のロゴ画像、標準の音声挨拶)を頻繁に生成します。
コアコンセプト¶
アーティファクトを理解するには、いくつかの主要なコンポーネントを把握する必要があります:それらを管理するサービス、それらを保持するために使用されるデータ構造、そしてそれらがどのように識別され、バージョン管理されるかです。
アーティファクトサービス (BaseArtifactService)¶
-
役割: アーティファクトの実際の保存と取得ロジックを担当する中心的なコンポーネントです。アーティファクトがどのように、どこに永続化されるかを定義します。
-
インターフェース: 抽象基底クラス
BaseArtifactServiceによって定義されます。具体的な実装は、以下のメソッドを提供する必要があります:Save Artifact: アーティファクトデータを保存し、割り当てられたバージョン番号を返します。Load Artifact: アーティファクトの特定のバージョン(または最新版)を取得します。List Artifact keys: 指定されたスコープ内のアーティファクトの一意なファイル名をリストします。Delete Artifact: アーティファクトを削除します(実装によっては、すべてのバージョンが削除される場合があります)。List versions: 特定のアーティファクトファイル名で利用可能なすべてのバージョン番号をリストします。
-
設定:
Runnerの初期化時に、アーティファクトサービスのインスタンス(例:InMemoryArtifactService、GcsArtifactService)を提供します。Runnerは、InvocationContextを介してこのサービスをエージェントやツールで利用可能にします。
from google.adk.runners import Runner
from google.adk.artifacts import InMemoryArtifactService # または GcsArtifactService
from google.adk.agents import LlmAgent # 任意のエージェント
from google.adk.sessions import InMemorySessionService
# 例:Runnerにアーティファクトサービスを設定する
my_agent = LlmAgent(name="artifact_user_agent", model="gemini-2.0-flash")
artifact_service = InMemoryArtifactService() # 実装を選択
session_service = InMemorySessionService()
runner = Runner(
agent=my_agent,
app_name="my_artifact_app",
session_service=session_service,
artifact_service=artifact_service # ここでサービスインスタンスを提供する
)
# これで、このrunnerが管理する実行内のコンテキストでアーティファクトメソッドを使用できます
import (
"context"
"log"
"google.golang.org/adk/agent/llmagent"
"google.golang.org/adk/artifactservice"
"google.golang.org/adk/llm/gemini"
"google.golang.org/adk/runner"
"google.golang.org/adk/sessionservice"
"google.golang.org/genai"
)
// Create a new context.
ctx := context.Background()
// Set the app name.
const appName = "my_artifact_app"
// Create a new Gemini model.
model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
if err != nil {
log.Fatalf("Failed to create model: %v", err)
}
// Create a new LLM agent.
myAgent, err := llmagent.New(llmagent.Config{
Model: model,
Name: "artifact_user_agent",
Instruction: "You are an agent that describes images.",
BeforeModelCallbacks: []llmagent.BeforeModelCallback{
BeforeModelCallback,
},
})
if err != nil {
log.Fatalf("Failed to create agent: %v", err)
}
// Create a new in-memory artifact service.
artifactService := artifact.InMemoryService()
// Create a new in-memory session service.
sessionService := session.InMemoryService()
// Create a new runner.
r, err := runner.New(runner.Config{
Agent: myAgent,
AppName: appName,
SessionService: sessionService,
ArtifactService: artifactService, // Provide the service instance here
})
if err != nil {
log.Fatalf("Failed to create runner: %v", err)
}
log.Printf("Runner created successfully: %v", r)
import com.google.adk.agents.LlmAgent;
import com.google.adk.runner.Runner;
import com.google.adk.sessions.InMemorySessionService;
import com.google.adk.artifacts.InMemoryArtifactService;
// 例:Runnerにアーティファクトサービスを設定する
LlmAgent myAgent = LlmAgent.builder()
.name("artifact_user_agent")
.model("gemini-2.0-flash")
.build();
InMemoryArtifactService artifactService = new InMemoryArtifactService(); // 実装を選択
InMemorySessionService sessionService = new InMemorySessionService();
Runner runner = new Runner(myAgent, "my_artifact_app", artifactService, sessionService); // ここでサービスインスタンスを提供する
// これで、このrunnerが管理する実行内のコンテキストでアーティファクトメソッドを使用できます
アーティファクトデータ¶
-
標準表現: アーティファクトのコンテンツは、LLMメッセージのパーツに使用されるのと同じ構造である
google.genai.types.Partオブジェクトを使用して普遍的に表現されます。 -
主要属性 (
inline_data): アーティファクトにとって最も関連性の高い属性はinline_dataであり、これは以下を含むgoogle.genai.types.Blobオブジェクトです:data(bytes): アーティファクトの生のバイナリコンテンツ。mime_type(str): バイナリデータの性質を説明する標準のMIMEタイプ文字列(例:'application/pdf'、'image/png'、'audio/mpeg')。これはアーティファクトを読み込む際に正しく解釈するために非常に重要です。
import google.genai.types as types
# 例:生のバイトからアーティファクトPartを作成する
pdf_bytes = b'%PDF-1.4...' # あなたの生のPDFデータ
pdf_mime_type = "application/pdf"
# コンストラクタを使用
pdf_artifact_py = types.Part(
inline_data=types.Blob(data=pdf_bytes, mime_type=pdf_mime_type)
)
# 便利なクラスメソッドを使用(同等)
pdf_artifact_alt_py = types.Part.from_bytes(data=pdf_bytes, mime_type=pdf_mime_type)
print(f"作成されたPythonアーティファクトのMIMEタイプ: {pdf_artifact_py.inline_data.mime_type}")
import (
"log"
"os"
"google.golang.org/genai"
)
// Load imageBytes from a file
imageBytes, err := os.ReadFile("image.png")
if err != nil {
log.Fatalf("Failed to read image file: %v", err)
}
// genai.NewPartFromBytes is a convenience function that is a shorthand for
// creating a &genai.Part with the InlineData field populated.
// Create a new artifact from the image data.
imageArtifact := genai.NewPartFromBytes([]byte(imageBytes), "image/png")
log.Printf("Artifact MIME Type: %s", imageArtifact.InlineData.MIMEType)
import com.google.genai.types.Blob;
import com.google.genai.types.Part;
import java.nio.charset.StandardCharsets;
public class ArtifactDataExample {
public static void main(String[] args) {
// Example: Creating an artifact Part from raw bytes
byte[] pdfBytes = "%PDF-1.4...".getBytes(StandardCharsets.UTF_8); // Your raw PDF data
String pdfMimeType = "application/pdf";
// Using the Part.fromBlob() constructor with a Blob
Blob pdfBlob = Blob.builder()
.data(pdfBytes)
.mimeType(pdfMimeType)
.build();
Part pdfArtifactJava = Part.builder().inlineData(pdfBlob).build();
// Using the convenience static method Part.fromBytes() (equivalent)
Part pdfArtifactAltJava = Part.fromBytes(pdfBytes, pdfMimeType);
// Accessing mimeType, note the use of Optional
String mimeType = pdfArtifactJava.inlineData()
.flatMap(Blob::mimeType)
.orElse("unknown");
System.out.println("Created Java artifact with MIME type: " + mimeType);
// Accessing data
byte[] data = pdfArtifactJava.inlineData()
.flatMap(Blob::data)
.orElse(new byte[0]);
System.out.println("Java artifact data (first 10 bytes): "
+ new String(data, 0, Math.min(data.length, 10), StandardCharsets.UTF_8) + "...");
}
}
ファイル名 (Filename)¶
- 識別子: 特定の名前空間内でアーティファクトに名前を付けて取得するために使用される単純な文字列です。
- 一意性: ファイル名は、そのスコープ(セッションまたはユーザー名前空間)内で一意でなければなりません。
- ベストプラクティス: ファイル拡張子を含む、説明的な名前を使用することをお勧めします(例:
"monthly_report.pdf"、"user_avatar.jpg")。ただし、拡張子自体が動作を決定するのではなく、mime_typeが決定します。
バージョン管理 (Versioning)¶
- 自動バージョン管理: アーティファクトサービスは自動的にバージョン管理を処理します。
save_artifactを呼び出すと、サービスはそのファイル名とスコープに対して次に利用可能なバージョン番号(通常は0から始まり、インクリメントされる)を決定します。 save_artifactの戻り値:save_artifactメソッドは、新しく保存されたアーティファクトに割り当てられた整数のバージョン番号を返します。- 取得:
load_artifact(..., version=None)(デフォルト): アーティファクトの最新の利用可能なバージョンを取得します。load_artifact(..., version=N): 特定のバージョンNを取得します。- バージョンのリスト表示: (コンテキストではなく)サービスの
list_versionsメソッドを使用して、アーティファクトの既存のすべてのバージョン番号を見つけることができます。
名前空間 (セッション vs. ユーザー)¶
-
概念: アーティファクトは、特定のセッションにスコープを限定することも、アプリケーション内のすべてのセッションにわたってより広くユーザーにスコープを限定することもできます。このスコープ設定は
filenameの形式によって決定され、ArtifactServiceによって内部的に処理されます。 -
デフォルト (セッションスコープ):
"report.pdf"のようなプレーンなファイル名を使用すると、アーティファクトは特定のapp_name、user_id、およびsession_idに関連付けられます。その正確なセッションコンテキスト内でのみアクセス可能です。 -
ユーザースコープ (
"user:"プレフィックス): ファイル名の前に"user:"を付けて"user:profile.png"のようにすると、アーティファクトはapp_nameとuser_idにのみ関連付けられます。そのアプリ内の該当ユーザーに属する任意のセッションからアクセスまたは更新できます。
# 名前空間の違いを説明する例(概念的)
# セッション固有のアーティファクトファイル名
session_report_filename = "summary.txt"
# ユーザー固有のアーティファクトファイル名
user_config_filename = "user:settings.json"
# context.save_artifact を介して 'summary.txt' を保存すると、
# 現在の app_name, user_id, session_id に紐付けられます。
# context.save_artifact を介して 'user:settings.json' を保存すると、
# ArtifactService の実装は "user:" プレフィックスを認識し、
# app_name と user_id にスコープを限定して、そのユーザーのセッション間でアクセス可能にする必要があります。
import (
"log"
)
// Note: Namespacing is only supported when using the GCS ArtifactService implementation.
// A session-scoped artifact is only available within the current session.
sessionReportFilename := "summary.txt"
// A user-scoped artifact is available across all sessions for the current user.
userConfigFilename := "user:settings.json"
// When saving 'summary.txt' via ctx.Artifacts().Save,
// it's tied to the current app_name, user_id, and session_id.
// ctx.Artifacts().Save(sessionReportFilename, *artifact);
// When saving 'user:settings.json' via ctx.Artifacts().Save,
// the ArtifactService implementation should recognize the "user:" prefix
// and scope it to app_name and user_id, making it accessible across sessions for that user.
// ctx.Artifacts().Save(userConfigFilename, *artifact);
// 名前空間の違いを説明する例(概念的)
// セッション固有のアーティファクトファイル名
String sessionReportFilename = "summary.txt";
// ユーザー固有のアーティファクトファイル名
String userConfigFilename = "user:settings.json"; // "user:" プレフィックスが重要です
// context.save_artifact を介して 'summary.txt' を保存すると、
// 現在の app_name, user_id, session_id に紐付けられます。
// artifactService.saveArtifact(appName, userId, sessionId1, sessionReportFilename, someData);
// context.save_artifact を介して 'user:settings.json' を保存すると、
// ArtifactService の実装は "user:" プレフィックスを認識し、
// app_name と user_id にスコープを限定して、そのユーザーのセッション間でアクセス可能にする必要があります。
// artifactService.saveArtifact(appName, userId, sessionId1, userConfigFilename, someData);
これらのコアコンセプトは連携して、ADKフレームワーク内でバイナリデータを管理するための柔軟なシステムを提供します。
アーティファクトとの対話 (コンテキストオブジェクト経由)¶
エージェントのロジック内(特にコールバックやツール内)でアーティファクトと対話する主な方法は、CallbackContext および ToolContext オブジェクトによって提供されるメソッドを使用することです。これらのメソッドは、ArtifactService によって管理される基盤となるストレージの詳細を抽象化します。
前提条件: ArtifactService の設定¶
コンテキストオブジェクトを介してアーティファクトメソッドを使用する前に、Runner を初期化する際に、必ず BaseArtifactService の実装(InMemoryArtifactService や GcsArtifactService など)のインスタンスを提供する必要があります。
Pythonでは、Runner の初期化時にこのインスタンスを提供します。
from google.adk.runners import Runner
from google.adk.artifacts import InMemoryArtifactService # または GcsArtifactService
from google.adk.agents import LlmAgent
from google.adk.sessions import InMemorySessionService
# エージェントの定義
agent = LlmAgent(name="my_agent", model="gemini-2.0-flash")
# 目的のアーティファクトサービスをインスタンス化
artifact_service = InMemoryArtifactService()
# それを Runner に提供
runner = Runner(
agent=agent,
app_name="artifact_app",
session_service=InMemorySessionService(),
artifact_service=artifact_service # サービスはここで提供する必要があります
)
InvocationContext に artifact_service が設定されていない場合(Runner に渡されなかった場合)、コンテキストオブジェクトで save_artifact、load_artifact、または list_artifacts を呼び出すと ValueError が発生します。
import (
"context"
"log"
"google.golang.org/adk/agent/llmagent"
"google.golang.org/adk/artifactservice"
"google.golang.org/adk/llm/gemini"
"google.golang.org/adk/runner"
"google.golang.org/adk/sessionservice"
"google.golang.org/genai"
)
// Create a new context.
ctx := context.Background()
// Set the app name.
const appName = "my_artifact_app"
// Create a new Gemini model.
model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
if err != nil {
log.Fatalf("Failed to create model: %v", err)
}
// Create a new LLM agent.
myAgent, err := llmagent.New(llmagent.Config{
Model: model,
Name: "artifact_user_agent",
Instruction: "You are an agent that describes images.",
BeforeModelCallbacks: []llmagent.BeforeModelCallback{
BeforeModelCallback,
},
})
if err != nil {
log.Fatalf("Failed to create agent: %v", err)
}
// Create a new in-memory artifact service.
artifactService := artifact.InMemoryService()
// Create a new in-memory session service.
sessionService := session.InMemoryService()
// Create a new runner.
r, err := runner.New(runner.Config{
Agent: myAgent,
AppName: appName,
SessionService: sessionService,
ArtifactService: artifactService, // Provide the service instance here
})
if err != nil {
log.Fatalf("Failed to create runner: %v", err)
}
log.Printf("Runner created successfully: %v", r)
Javaでは、BaseArtifactService の実装をインスタンス化し、アーティファクトを管理するアプリケーションの部分からアクセスできるようにします。これは多くの場合、依存性注入を通じて、またはサービスインスタンスを明示的に渡すことによって行われます。
import com.google.adk.agents.LlmAgent;
import com.google.adk.artifacts.InMemoryArtifactService; // または GcsArtifactService
import com.google.adk.runner.Runner;
import com.google.adk.sessions.InMemorySessionService;
public class SampleArtifactAgent {
public static void main(String[] args) {
// エージェントの定義
LlmAgent agent = LlmAgent.builder()
.name("my_agent")
.model("gemini-2.0-flash")
.build();
// 目的のアーティファクトサービスをインスタンス化
InMemoryArtifactService artifactService = new InMemoryArtifactService();
// それを Runner に提供
Runner runner = new Runner(agent,
"APP_NAME",
artifactService, // サービスはここで提供する必要があります
new InMemorySessionService());
}
}
ArtifactService インスタンスが利用できない(例:null)場合、アプリケーションの構造に応じて、通常は NullPointerException またはカスタムエラーが発生します。堅牢なアプリケーションでは、サービスのライフサイクルを管理し、可用性を確保するために、依存性注入フレームワークがよく使用されます。
メソッドへのアクセス¶
アーティファクト対話メソッドは、CallbackContext(エージェントおよびモデルのコールバックに渡される)および ToolContext(ツールのコールバックに渡される)のインスタンスで直接利用できます。ToolContext は CallbackContext を継承していることを忘れないでください。
アーティファクトの保存¶
-
コード例:
import google.genai.types as types from google.adk.agents.callback_context import CallbackContext # または ToolContext async def save_generated_report_py(context: CallbackContext, report_bytes: bytes): """生成されたPDFレポートのバイトをアーティファクトとして保存します。""" report_artifact = types.Part.from_bytes( data=report_bytes, mime_type="application/pdf" ) filename = "generated_report.pdf" try: version = await context.save_artifact(filename=filename, artifact=report_artifact) print(f"Pythonアーティファクト '{filename}' をバージョン {version} として正常に保存しました。") # このコールバックの後に生成されるイベントには以下が含まれます: # event.actions.artifact_delta == {"generated_report.pdf": version} except ValueError as e: print(f"Pythonアーティファクトの保存エラー: {e}。RunnerにArtifactServiceが設定されていますか?") except Exception as e: # 潜在的なストレージエラー(例:GCSの権限)を処理 print(f"Pythonアーティファクトの保存中に予期せぬエラーが発生しました: {e}") # --- 使用例の概念 (Python) --- # async def main_py(): # callback_context: CallbackContext = ... # コンテキストを取得 # report_data = b'...' # PDFのバイトを保持していると仮定 # await save_generated_report_py(callback_context, report_data)import ( "log" "google.golang.org/adk/agent" "google.golang.org/adk/llm" "google.golang.org/genai" ) // saveReportCallback is a BeforeModel callback that saves a report from session state. func saveReportCallback(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMResponse, error) { // Get the report data from the session state. reportData, err := ctx.State().Get("report_bytes") if err != nil { log.Printf("No report data found in session state: %v", err) return nil, nil // No report to save, continue normally. } // Check if the report data is in the expected format. reportBytes, ok := reportData.([]byte) if !ok { log.Printf("Report data in session state was not in the expected byte format.") return nil, nil } // Create a new artifact with the report data. reportArtifact := &genai.Part{ InlineData: &genai.Blob{ MIMEType: "application/pdf", Data: reportBytes, }, } // Set the filename for the artifact. filename := "generated_report.pdf" // Save the artifact to the artifact service. _, err = ctx.Artifacts().Save(ctx, filename, reportArtifact) if err != nil { log.Printf("An unexpected error occurred during Go artifact save: %v", err) // Depending on requirements, you might want to return an error to the user. return nil, nil } log.Printf("Successfully saved Go artifact '%s'.", filename) // Return nil to continue to the next callback or the model. return nil, nil }import com.google.adk.agents.CallbackContext; import com.google.adk.artifacts.BaseArtifactService; import com.google.adk.artifacts.InMemoryArtifactService; import com.google.genai.types.Part; import java.nio.charset.StandardCharsets; public class SaveArtifactExample { public void saveGeneratedReport(CallbackContext callbackContext, byte[] reportBytes) { // 生成されたPDFレポートのバイトをアーティファクトとして保存します。 Part reportArtifact = Part.fromBytes(reportBytes, "application/pdf"); String filename = "generatedReport.pdf"; callbackContext.saveArtifact(filename, reportArtifact); System.out.println("Javaアーティファクト '" + filename + "' を正常に保存しました。"); // このコールバックの後に生成されるイベントには以下が含まれます: // event().actions().artifactDelta == {"generated_report.pdf": version} } // --- 使用例の概念 (Java) --- public static void main(String[] args) { BaseArtifactService service = new InMemoryArtifactService(); // または GcsArtifactService SaveArtifactExample myTool = new SaveArtifactExample(); byte[] reportData = "...".getBytes(StandardCharsets.UTF_8); // PDFのバイト CallbackContext callbackContext; // ... アプリからコールバックコンテキストを取得 myTool.saveGeneratedReport(callbackContext, reportData); // 非同期の性質上、実際のアプリでは、プログラムが完了を待つか処理するようにしてください。 } }
アーティファクトの読み込み¶
-
コード例:
import google.genai.types as types from google.adk.agents.callback_context import CallbackContext # または ToolContext async def process_latest_report_py(context: CallbackContext): """最新のレポートアーティファクトを読み込み、そのデータを処理します。""" filename = "generated_report.pdf" try: # 最新バージョンを読み込み report_artifact = await context.load_artifact(filename=filename) if report_artifact and report_artifact.inline_data: print(f"最新のPythonアーティファクト '{filename}' を正常に読み込みました。") print(f"MIMEタイプ: {report_artifact.inline_data.mime_type}") # report_artifact.inline_data.data (バイト) を処理 pdf_bytes = report_artifact.inline_data.data print(f"レポートサイズ: {len(pdf_bytes)} バイト。") # ... さらなる処理 ... else: print(f"Pythonアーティファクト '{filename}' が見つかりません。") # 例:特定のバージョンを読み込む(バージョン0が存在する場合) # specific_version_artifact = await context.load_artifact(filename=filename, version=0) # if specific_version_artifact: # print(f"'{filename}' のバージョン0を読み込みました。") except ValueError as e: print(f"Pythonアーティファクトの読み込みエラー: {e}。ArtifactServiceが設定されていますか?") except Exception as e: # 潜在的なストレージエラーを処理 print(f"Pythonアーティファクトの読み込み中に予期せぬエラーが発生しました: {e}") # --- 使用例の概念 (Python) --- # async def main_py(): # callback_context: CallbackContext = ... # コンテキストを取得 # await process_latest_report_py(callback_context)import ( "log" "google.golang.org/adk/agent" "google.golang.org/adk/llm" ) // loadArtifactsCallback is a BeforeModel callback that loads a specific artifact // and adds its content to the LLM request. func loadArtifactsCallback(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMResponse, error) { log.Println("[Callback] loadArtifactsCallback triggered.") // In a real app, you would parse the user's request to find a filename. // For this example, we'll hardcode a filename to demonstrate. const filenameToLoad = "generated_report.pdf" // Load the artifact from the artifact service. loadedPartResponse, err := ctx.Artifacts().Load(ctx, filenameToLoad) if err != nil { log.Printf("Callback could not load artifact '%s': %v", filenameToLoad, err) return nil, nil // File not found or error, continue to model. } loadedPart := loadedPartResponse.Part log.Printf("Callback successfully loaded artifact '%s'.", filenameToLoad) // Ensure there's at least one content in the request to append to. if len(req.Contents) == 0 { req.Contents = []*genai.Content{{Parts: []*genai.Part{ genai.NewPartFromText("SYSTEM: The following file is provided for context:\n"), }}} } // Add the loaded artifact to the request for the model. lastContent := req.Contents[len(req.Contents)-1] lastContent.Parts = append(lastContent.Parts, loadedPart) log.Printf("Added artifact '%s' to LLM request.", filenameToLoad) // Return nil to continue to the next callback or the model. return nil, nil // Continue to next callback or LLM call }import com.google.adk.artifacts.BaseArtifactService; import com.google.genai.types.Part; import io.reactivex.rxjava3.core.MaybeObserver; import io.reactivex.rxjava3.disposables.Disposable; import java.util.Optional; public class MyArtifactLoaderService { private final BaseArtifactService artifactService; private final String appName; public MyArtifactLoaderService(BaseArtifactService artifactService, String appName) { this.artifactService = artifactService; this.appName = appName; } public void processLatestReportJava(String userId, String sessionId, String filename) { // バージョンに Optional.empty() を渡して最新バージョンを読み込む artifactService .loadArtifact(appName, userId, sessionId, filename, Optional.empty()) .subscribe( new MaybeObserver<Part>() { @Override public void onSubscribe(Disposable d) { // オプション:サブスクリプションを処理 } @Override public void onSuccess(Part reportArtifact) { System.out.println( "最新のJavaアーティファクト '" + filename + "' を正常に読み込みました。"); reportArtifact .inlineData() .ifPresent( blob -> { System.out.println( "MIMEタイプ: " + blob.mimeType().orElse("N/A")); byte[] pdfBytes = blob.data().orElse(new byte[0]); System.out.println("レポートサイズ: " + pdfBytes.length + " バイト。"); // ... pdfBytes のさらなる処理 ... }); } @Override public void onError(Throwable e) { // 潜在的なストレージエラーやその他の例外を処理 System.err.println( "Javaアーティファクト '" + filename + "' の読み込み中にエラーが発生しました: " + e.getMessage()); } @Override public void onComplete() { // アーティファクト(最新バージョン)が見つからない場合に呼び出される System.out.println("Javaアーティファクト '" + filename + "' が見つかりません。"); } }); // 例:特定のバージョンを読み込む(例:バージョン0) /* artifactService.loadArtifact(appName, userId, sessionId, filename, Optional.of(0)) .subscribe(part -> { System.out.println("Javaアーティファクト '" + filename + "' のバージョン0を読み込みました。"); }, throwable -> { System.err.println("'" + filename + "' のバージョン0の読み込みエラー: " + throwable.getMessage()); }, () -> { System.out.println("Javaアーティファクト '" + filename + "' のバージョン0が見つかりません。"); }); */ } // --- 使用例の概念 (Java) --- public static void main(String[] args) { // BaseArtifactService service = new InMemoryArtifactService(); // または GcsArtifactService // MyArtifactLoaderService loader = new MyArtifactLoaderService(service, "myJavaApp"); // loader.processLatestReportJava("user123", "sessionABC", "java_report.pdf"); // 非同期の性質上、実際のアプリでは、プログラムが完了を待つか処理するようにしてください。 } }
アーティファクトファイル名のリスト表示¶
-
コード例:
from google.adk.tools.tool_context import ToolContext def list_user_files_py(tool_context: ToolContext) -> str: """ユーザーが利用可能なアーティファクトをリスト表示するツール。""" try: available_files = await tool_context.list_artifacts() if not available_files: return "保存されたアーティファクトはありません。" else: # ユーザー/LLM向けにリストをフォーマット file_list_str = "\n".join([f"- {fname}" for fname in available_files]) return f"利用可能なPythonアーティファクトは次のとおりです:\n{file_list_str}" except ValueError as e: print(f"Pythonアーティファクトのリスト表示エラー: {e}。ArtifactServiceが設定されていますか?") return "エラー: Pythonアーティファクトをリスト表示できませんでした。" except Exception as e: print(f"Pythonアーティファクトのリスト表示中に予期せぬエラーが発生しました: {e}") return "エラー: Pythonアーティファクトのリスト表示中に予期せぬエラーが発生しました。" # この関数は通常、FunctionToolでラップされます # from google.adk.tools import FunctionTool # list_files_tool = FunctionTool(func=list_user_files_py)import ( "fmt" "log" "strings" "google.golang.org/adk/agent" "google.golang.org/adk/llm" "google.golang.org/genai" ) // listUserFilesCallback is a BeforeModel callback that lists available artifacts // and adds the list as context to the LLM request. func listUserFilesCallback(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMResponse, error) { log.Println("[Callback] listUserFilesCallback triggered.") // List the available artifacts from the artifact service. listResponse, err := ctx.Artifacts().List(ctx) if err != nil { log.Printf("An unexpected error occurred during Go artifact list: %v", err) return nil, nil // Continue, but log the error. } availableFiles := listResponse.FileNames log.Printf("Found %d available files.", len(availableFiles)) // If there are available files, add them to the LLM request. if len(availableFiles) > 0 { var fileListStr strings.Builder fileListStr.WriteString("SYSTEM: The following files are available:\n") for _, fname := range availableFiles { fileListStr.WriteString(fmt.Sprintf("- %s\n", fname)) } // Prepend this information to the user's request for the model. if len(req.Contents) > 0 { lastContent := req.Contents[len(req.Contents)-1] if len(lastContent.Parts) > 0 { fileListStr.WriteString("\n") // Add a newline for separation. lastContent.Parts[0] = genai.NewPartFromText(fileListStr.String() + lastContent.Parts[0].Text) log.Println("Added file list to LLM request context.") } } log.Printf("Available files:\n%s", fileListStr.String()) } else { log.Println("No available files found to list.") } // Return nil to continue to the next callback or the model. return nil, nil // Continue to next callback or LLM call }import com.google.adk.artifacts.BaseArtifactService; import com.google.adk.artifacts.ListArtifactsResponse; import com.google.common.collect.ImmutableList; import io.reactivex.rxjava3.core.SingleObserver; import io.reactivex.rxjava3.disposables.Disposable; public class MyArtifactListerService { private final BaseArtifactService artifactService; private final String appName; public MyArtifactListerService(BaseArtifactService artifactService, String appName) { this.artifactService = artifactService; this.appName = appName; } // ツールやエージェントのロジックから呼び出される可能性のあるメソッドの例 public void listUserFilesJava(String userId, String sessionId) { artifactService .listArtifactKeys(appName, userId, sessionId) .subscribe( new SingleObserver<ListArtifactsResponse>() { @Override public void onSubscribe(Disposable d) { // オプション:サブスクリプションを処理 } @Override public void onSuccess(ListArtifactsResponse response) { ImmutableList<String> availableFiles = response.filenames(); if (availableFiles.isEmpty()) { System.out.println( "ユーザー " + userId + " (セッション " + sessionId + ") には保存されたJavaアーティファクトがありません。"); } else { StringBuilder fileListStr = new StringBuilder( "ユーザー " + userId + " (セッション " + sessionId + ") の利用可能なJavaアーティファクトは次のとおりです:\n"); for (String fname : availableFiles) { fileListStr.append("- ").append(fname).append("\n"); } System.out.println(fileListStr.toString()); } } @Override public void onError(Throwable e) { System.err.println( "ユーザー " + userId + " (セッション " + sessionId + ") のJavaアーティファクトのリスト表示エラー: " + e.getMessage()); // 実際のアプリケーションでは、ユーザー/LLMにエラーメッセージを返すことがあります } }); } // --- 使用例の概念 (Java) --- public static void main(String[] args) { // BaseArtifactService service = new InMemoryArtifactService(); // または GcsArtifactService // MyArtifactListerService lister = new MyArtifactListerService(service, "myJavaApp"); // lister.listUserFilesJava("user123", "sessionABC"); // 非同期の性質上、実際のアプリでは、プログラムが完了を待つか処理するようにしてください。 } }
これらの保存、読み込み、リスト表示のメソッドは、Pythonのコンテキストオブジェクトを使用するか、Javaで BaseArtifactService と直接対話するかにかかわらず、選択したバックエンドストレージの実装に関係なく、ADK内でバイナリデータの永続性を管理するための便利で一貫した方法を提供します。
利用可能な実装¶
ADKは、BaseArtifactService インターフェースの具体的な実装を提供し、さまざまな開発段階やデプロイニーズに適した異なるストレージバックエンドを提供します。これらの実装は、app_name、user_id、session_id、および filename(user: 名前空間プレフィックスを含む)に基づいてアーティファクトデータの保存、バージョン管理、取得の詳細を処理します。
InMemoryArtifactService¶
- ストレージメカニズム:
- Python: アプリケーションのメモリ内に保持されるPython辞書(
self.artifacts)を使用します。辞書のキーはアーティファクトのパスを表し、値はtypes.Partのリストで、各リスト要素が1つのバージョンです。 - Java: メモリ内に保持されるネストされた
HashMapインスタンス(private final Map<String, Map<String, Map<String, Map<String, List<Part>>>>> artifacts;)を使用します。各レベルのキーはそれぞれappName、userId、sessionId、filenameです。最も内側のList<Part>がアーティファクトのバージョンを保存し、リストのインデックスがバージョン番号に対応します。
- Python: アプリケーションのメモリ内に保持されるPython辞書(
- 主な特徴:
- シンプルさ: コアのADKライブラリ以外に外部のセットアップや依存関係は必要ありません。
- 高速: 操作はインメモリのマップ/辞書のルックアップとリスト操作を含むため、通常非常に高速です。
- 一時的: 保存されたすべてのアーティファクトは、アプリケーションプロセスが終了すると失われます。データはアプリケーションの再起動間で持続しません。
- ユースケース:
- 永続性が必要ないローカル開発やテストに最適です。
- 短期間のデモンストレーションや、アーティファクトデータがアプリケーションの単一実行内で純粋に一時的なシナリオに適しています。
-
インスタンス化:
import ( "google.golang.org/adk/artifactservice" ) // Simply instantiate the service artifactService := artifact.InMemoryService() log.Printf("InMemoryArtifactService (Go) instantiated: %T", artifactService) // Use the service in your runner // r, _ := runner.New(runner.Config{ // Agent: agent, // AppName: "my_app", // SessionService: sessionService, // ArtifactService: artifactService, // })import com.google.adk.artifacts.BaseArtifactService; import com.google.adk.artifacts.InMemoryArtifactService; public class InMemoryServiceSetup { public static void main(String[] args) { // 単にクラスをインスタンス化します BaseArtifactService inMemoryServiceJava = new InMemoryArtifactService(); System.out.println("InMemoryArtifactService (Java) がインスタンス化されました: " + inMemoryServiceJava.getClass().getName()); // このインスタンスは、Runnerに提供されます。 // Runner runner = new Runner( // /* other services */, // inMemoryServiceJava // ); } }
GcsArtifactService¶
- ストレージメカニズム: 永続的なアーティファクトストレージにGoogle Cloud Storage (GCS) を活用します。アーティファクトの各バージョンは、指定されたGCSバケット内に個別のオブジェクト(BLOB)として保存されます。
- オブジェクト命名規則: 階層的なパス構造を使用してGCSオブジェクト名(BLOB名)を構築します。
- 主な特徴:
- 永続性: GCSに保存されたアーティファクトは、アプリケーションの再起動やデプロイを越えて持続します。
- スケーラビリティ: Google Cloud Storageのスケーラビリティと耐久性を活用します。
- バージョン管理: 各バージョンを明確に個別のGCSオブジェクトとして保存します。
GcsArtifactServiceのsaveArtifactメソッド。 - 必要な権限: アプリケーション環境には、指定されたGCSバケットへの読み書きのための適切な認証情報(例:Application Default Credentials)とIAM権限が必要です。
- ユースケース:
- 永続的なアーティファクトストレージを必要とする本番環境。
- アーティファクトを異なるアプリケーションインスタンスやサービス間で共有する必要があるシナリオ(同じGCSバケットにアクセスすることによって)。
- ユーザーまたはセッションデータの長期的な保存と取得が必要なアプリケーション。
-
インスタンス化:
from google.adk.artifacts import GcsArtifactService # GCSバケット名を指定します gcs_bucket_name_py = "your-gcs-bucket-for-adk-artifacts" # あなたのバケット名に置き換えてください try: gcs_service_py = GcsArtifactService(bucket_name=gcs_bucket_name_py) print(f"Python GcsArtifactServiceがバケット {gcs_bucket_name_py} 用に初期化されました") # 環境がこのバケットにアクセスするための認証情報を持っていることを確認してください。 # 例:Application Default Credentials (ADC)経由 # そしてそれをRunnerに渡します # runner = Runner(..., artifact_service=gcs_service_py) except Exception as e: # GCSクライアント初期化中の潜在的なエラー(例:認証問題)をキャッチ print(f"Python GcsArtifactServiceの初期化エラー: {e}") # エラーを適切に処理します - InMemoryにフォールバックするか、例外を発生させるなどimport com.google.adk.artifacts.BaseArtifactService; import com.google.adk.artifacts.GcsArtifactService; import com.google.cloud.storage.Storage; import com.google.cloud.storage.StorageOptions; public class GcsServiceSetup { public static void main(String[] args) { // Specify the GCS bucket name String gcsBucketNameJava = "your-gcs-bucket-for-adk-artifacts"; // Replace with your bucket name try { // Initialize the GCS Storage client. // This will use Application Default Credentials by default. // Ensure the environment is configured correctly (e.g., GOOGLE_APPLICATION_CREDENTIALS). Storage storageClient = StorageOptions.getDefaultInstance().getService(); // Instantiate the GcsArtifactService BaseArtifactService gcsServiceJava = new GcsArtifactService(gcsBucketNameJava, storageClient); System.out.println( "Java GcsArtifactService initialized for bucket: " + gcsBucketNameJava); // This instance would then be provided to your Runner. // Runner runner = new Runner( // /* other services */, // gcsServiceJava // ); } catch (Exception e) { // Catch potential errors during GCS client initialization (e.g., auth, permissions) System.err.println("Error initializing Java GcsArtifactService: " + e.getMessage()); e.printStackTrace(); // Handle the error appropriately } } }
適切な ArtifactService 実装の選択は、アプリケーションのデータ永続性、スケーラビリティ、および運用環境の要件に依存します。
ベストプラクティス¶
アーティファクトを効果的かつ保守可能に使用するために:
- 適切なサービスの選択: ラピッドプロトタイピング、テスト、永続性が不要なシナリオには
InMemoryArtifactServiceを使用します。データ永続性とスケーラビリティが必要な本番環境では、GcsArtifactServiceを使用するか、(他のバックエンドのために)独自のBaseArtifactServiceを実装します。 - 意味のあるファイル名: 明確で説明的なファイル名を使用します。関連する拡張子(
.pdf、.png、.wav)を含めると、mime_typeがプログラム的な処理を決定するにもかかわらず、人間がコンテンツを理解しやすくなります。一時的なアーティファクト名と永続的なアーティファクト名の規約を確立します。 - 正しいMIMEタイプの指定:
save_artifactのためにtypes.Partを作成する際は、常に正確なmime_typeを提供します。これは、後でload_artifactをしてbytesデータを正しく解釈する必要があるアプリケーションやツールにとって非常に重要です。可能な限り、標準のIANA MIMEタイプを使用します。 - バージョン管理の理解: 特定の
version引数なしでload_artifact()を呼び出すと、最新のバージョンが取得されることを覚えておいてください。ロジックがアーティファクトの特定の過去のバージョンに依存する場合は、読み込む際に必ず整数のバージョン番号を指定します。 - 名前空間(
user:)の意図的な使用: データが真にユーザーに属し、すべてのセッションでアクセス可能であるべき場合にのみ、ファイル名に"user:"プレフィックスを使用します。単一の会話やセッションに固有のデータには、プレフィックスなしの通常のファイル名を使用します。 - エラーハンドリング:
- コンテキストメソッド(
save_artifact,load_artifact,list_artifacts)を呼び出す前に、artifact_serviceが実際に設定されているかを常に確認します。サービスがNoneの場合、ValueErrorが発生します。 load_artifactの戻り値を確認します。アーティファクトやバージョンが存在しない場合はNoneになります。常にPartが返されると仮定しないでください。- 特に
GcsArtifactServiceを使用する場合は、基盤となるストレージサービスからの例外(例:権限問題に対するgoogle.api_core.exceptions.Forbidden、バケットが存在しない場合のNotFound、ネットワークエラー)を処理する準備をしておきます。
- コンテキストメソッド(
- サイズの考慮: アーティファクトは一般的なファイルサイズに適していますが、特にクラウドストレージを使用する場合、非常に大きなファイルによる潜在的なコストとパフォーマンスへの影響に注意してください。
InMemoryArtifactServiceは、多数の大きなアーティファクトを保存すると、大量のメモリを消費する可能性があります。非常に大きなデータは、バイト配列全体をメモリ内で渡すのではなく、直接GCSリンクや他の特殊なストレージソリューションを介して処理する方が良いかどうかを評価します。 - クリーンアップ戦略:
GcsArtifactServiceのような永続ストレージでは、アーティファクトは明示的に削除されるまで残ります。アーティファクトが一時的なデータを表すか、寿命が限られている場合は、クリーンアップ戦略を実装します。これには以下が含まれる可能性があります:- バケットでGCSライフサイクルポリシーを使用する。
artifact_service.delete_artifactメソッドを利用する特定のツールや管理機能を構築する(注意:deleteは安全のためコンテキストオブジェクトを介しては公開されていません)。- 必要に応じてパターンベースの削除を可能にするために、ファイル名を慎重に管理する。