Skip to content

制品

在 ADK 中,制品代表一种关键机制,用于管理与特定用户交互会话相关联或持久化存储在用户跨多个会话中的命名、版本化二进制数据。它们允许你的智能体和工具处理简单文本字符串之外的数据,实现涉及文件、图像、音频和其他二进制格式的更丰富交互。

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
    )
)

# You can also use the convenience constructor:
# image_artifact_alt = types.Part.from_bytes(data=image_bytes, mime_type="image/png")

print(f"Artifact MIME Type: {image_artifact.inline_data.mime_type}")
print(f"Artifact Data (first 10 bytes): {image_artifact.inline_data.data[:10]}...")
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("Artifact MIME Type: " + imageArtifact.inlineData().get().mimeType().get());
        System.out.println(
            "Artifact Data (first 10 bytes): "
                + new String(imageArtifact.inlineData().get().data().get(), 0, 10, StandardCharsets.UTF_8)
                + "...");
    }
}
  • 持久化与管理: 制品不会直接存储在智能体或会话状态中。它们的存储和检索由专用的 Artifact Servicegoogle.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)。 所选服务实现会在你保存数据时自动处理版本控制。

为什么使用制品?

虽然会话 state 适用于存储小块配置或会话上下文(如字符串、数字、布尔值或小型字典/列表),但制品设计用于涉及二进制或大数据的场景:

  1. 处理非文本数据: 轻松存储和检索图像、音频剪辑、视频片段、PDF、电子表格或与你的智能体功能相关的任何其他文件格式。
  2. 持久化大数据: 会话状态通常不适合存储大量数据。制品提供了一种专用机制,可以在不使会话状态混乱的情况下持久化更大的二进制数据。
  3. 用户文件管理: 提供用户上传文件(可以作为制品保存)以及检索或下载由智能体生成的文件(从制品加载)的功能。
  4. 共享输出: 使工具或智能体能够生成二进制输出(如 PDF 报告或生成的图像),可以通过 save_artifact 保存,并在应用程序的其他部分甚至在后续会话中(如果使用用户命名空间)访问。
  5. 缓存二进制数据: 将产生二进制数据的计算密集型操作结果(例如,渲染复杂图表图像)存储为制品,以避免在后续请求中重新生成它们。

本质上,每当你的智能体需要处理需要持久化、版本控制或共享的类似文件的二进制数据时,由 ArtifactService 管理的制品是 ADK 中的适当机制。

常见用例

制品提供了在 ADK 应用程序中处理二进制数据的灵活方式。

以下是它们证明有价值的一些典型场景:

  • 生成的报告/文件:

    • 工具或智能体生成报告(如 PDF 分析、CSV 数据导出、图像图表)。
  • 处理用户上传:

    • 用户通过前端界面上传文件(如用于分析的图像、用于摘要的文档)。
  • 存储中间二进制结果:

    • 智能体执行复杂的多步流程,其中某一步生成中间二进制数据(如音频合成、仿真结果)。
  • 持久化用户数据:

    • 存储不适合简单键值状态的用户特定配置或数据。
  • 缓存生成的二进制内容:

    • 智能体经常基于某些输入生成相同的二进制输出(如公司 logo 图像、标准音频问候语)。
  • 接口: 由抽象基类 BaseArtifactServicegoogle.adk.artifacts.base_artifact_service.py)定义。任何具体实现都必须提供以下方法:

    • save_artifact(...) -> int:存储制品数据并返回其分配的版本号。
    • load_artifact(...) -> Optional[types.Part]:检索制品的特定版本(或最新版本)。
    • list_artifact_keys(...) -> list[str]:列出给定范围内制品的唯一文件名。
    • delete_artifact(...) -> None:删除制品(并可能删除所有版本,取决于实现)。
    • list_versions(...) -> list[int]:列出特定制品文件名的所有可用版本号。

理解制品涉及把握几个关键组成部分:管理它们的服务、用于保存它们的数据结构,以及它们的标识和版本控制方式。

Artifact Service(BaseArtifactService

  • 作用: 负责制品实际存储和检索逻辑的核心组件。它定义了制品如何以及在哪里持久化。

  • 接口: 由抽象基类 BaseArtifactService 定义。任何具体实现都必须提供以下方法:

    • 保存制品:存储制品数据并返回其分配的版本号。
    • 加载制品:检索制品的特定版本(或最新版本)。
    • 列出制品键:列出给定范围内制品的唯一文件名。
    • 删除制品:删除制品(并可能删除所有版本,取决于实现)。
    • 列出版本:列出特定制品文件名的所有可用版本号。
  • 配置: 你在初始化 Runner 时提供一个制品服务实例(如 InMemoryArtifactServiceGcsArtifactService)。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

# 示例:用 Artifact Service 配置 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 com.google.adk.agents.LlmAgent;
import com.google.adk.runner.Runner;
import com.google.adk.sessions.InMemorySessionService;
import com.google.adk.artifacts.InMemoryArtifactService;

// 示例:用 Artifact Service 配置 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 管理的运行上下文可以使用制品方法

制品数据

  • 标准表示: 制品内容普遍使用 google.genai.types.Part 对象表示,这与 LLM 消息部分使用的结构相同。

  • 关键属性(inline_data): 对于制品,最相关的属性是 inline_data,它是一个 google.genai.types.Blob 对象,包含:

    • databytes):制品的原始二进制内容。
    • mime_typestr):标准 MIME 类型字符串(如 'application/pdf''image/png''audio/mpeg'),描述二进制数据的性质。这对于加载制品时的正确解释至关重要。

    • 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)
)

# Using the convenience class method (equivalent)
pdf_artifact_alt_py = types.Part.from_bytes(data=pdf_bytes, mime_type=pdf_mime_type)

print(f"Created Python artifact with MIME type: {pdf_artifact_py.inline_data.mime_type}")
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) + "...");
  }
}

文件名

  • 标识符: 用于在特定命名空间内命名和检索制品的简单字符串。
  • 唯一性: 文件名在其作用域(会话或用户命名空间)内必须唯一。
  • 最佳实践: 使用描述性名称,建议包含文件扩展名(如 "monthly_report.pdf""user_avatar.jpg"),但扩展名本身不决定行为——mime_type 才是关键。

版本控制

  • 自动版本控制: 制品服务自动处理版本控制。当你调用 save_artifact 时,服务会为该特定文件名和范围确定下一个可用的版本号(通常从 0 开始并递增)。
  • save_artifact 返回: save_artifact 方法返回分配给新保存制品的整数版本号。
  • 检索:
  • load_artifact(..., version=None)(默认):检索制品的最新可用版本。
  • load_artifact(..., version=N):检索特定版本 N
  • 列出版本: 可以使用 list_versions 方法(在服务上,而不是上下文)查找制品的所有现有版本号。

命名空间(会话与用户)

  • 概念: 制品可以限定在特定会话范围内,也可以更广泛地限定在应用程序内用户的所有会话中。这种限定由 filename 格式决定,并由 ArtifactService 内部处理。

  • 默认(会话范围): 如果你使用像 "report.pdf" 这样的普通文件名,制品将与特定的 app_nameuser_id session_id 关联。它只能在该确切的会话上下文中访问。

  • 内部路径(示例):app_name/user_id/session_id/report.pdf/<version>(在 GcsArtifactService._get_blob_nameInMemoryArtifactService._artifact_path 中可见)

  • 用户范围("user:" 前缀): 如果你在文件名前加上 "user:",如 "user:profile.png",则该制品只与 app_nameuser_id 关联。它可以在该用户的任何会话中访问或更新。

# 示例:命名空间差异(概念)

# 会话专属制品文件名
session_report_filename = "summary.txt"

# 用户专属制品文件名
user_config_filename = "user:settings.json"

# 保存 'summary.txt' 时,
# 它绑定到当前 app_name、user_id 和 session_id。

# 保存 'user:settings.json' 时,
# ArtifactService 实现应识别 "user:" 前缀
# 并将其限定到 app_name 和 user_id,使其可跨该用户所有会话访问。
// 示例:命名空间差异(概念)

// 会话专属制品文件名
String sessionReportFilename = "summary.txt";

// 用户专属制品文件名
String userConfigFilename = "user:settings.json"; // "user:" 前缀是关键

// 保存 'summary.txt' 时,
// 它绑定到当前 app_name、user_id 和 session_id。
// artifactService.saveArtifact(appName, userId, sessionId1, sessionReportFilename, someData);

// 保存 'user:settings.json' 时,
// ArtifactService 实现应识别 "user:" 前缀
// 并将其限定到 app_name 和 user_id,使其可跨该用户所有会话访问。
// artifactService.saveArtifact(appName, userId, sessionId1, userConfigFilename, someData);

这些核心概念共同提供了一个灵活的系统,用于在 ADK 框架内管理二进制数据。

与制品交互(通过上下文对象)

在智能体逻辑中(特别是在回调或工具中)与制品交互的主要方式是通过 CallbackContextToolContext 对象提供的方法。这些方法抽象了由 ArtifactService 管理的底层存储细节。

先决条件:配置 ArtifactService

在通过上下文对象使用任何制品方法之前,你必须在初始化 Runner 时提供 BaseArtifactService 实现(如 InMemoryArtifactServiceGcsArtifactService)的实例。

在 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_artifactload_artifactlist_artifacts 会抛出 ValueError

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

  }
}
在 Java 中,如果在尝试制品操作时没有可用的 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_data(
            data=report_bytes,
            mime_type="application/pdf"
        )
        filename = "generated_report.pdf"
    
        try:
            version = await context.save_artifact(filename=filename, artifact=report_artifact)
            print(f"Successfully saved Python artifact '{filename}' as version {version}.")
            # The event generated after this callback will contain:
            # 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 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:
            # Load the latest version
            report_artifact = await context.load_artifact(filename=filename)
    
            if report_artifact and report_artifact.inline_data:
                print(f"成功加载最新 Python 制品 '{filename}'。")
                print(f"MIME Type: {report_artifact.inline_data.mime_type}")
                # 处理 report_artifact.inline_data.data(字节)
                pdf_bytes = report_artifact.inline_data.data
                print(f"报告大小:{len(pdf_bytes)} bytes.")
                # ... 进一步处理 ...
            else:
                print(f"未找到 Python 制品 '{filename}'。")
    
            # Example: Load a specific version (if version 0 exists)
            # 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 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 Type: " + blob.mimeType().orElse("N/A"));
                                                        byte[] pdfBytes = blob.data().orElse(new byte[0]);
                                                        System.out.println("报告大小:" + pdfBytes.length + " bytes.");
                                                        // ... 进一步处理 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 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");
            // 由于异步特性,在真实应用中请确保程序等待或处理完成。
        }
    }
    

这些保存、加载和列出的方法为在 ADK 内部管理二进制数据持久化提供了便捷且一致的方式,无论你是使用 Python 的上下文对象,还是直接与 Java 的 BaseArtifactService 交互,无论底层存储实现如何。

可用实现

ADK 提供了 BaseArtifactService 接口的具体实现,提供适用于不同开发阶段和部署需求的各种存储后端。这些实现根据 app_nameuser_idsession_idfilename(包括 user: 命名空间前缀)处理制品数据的存储、版本控制和检索细节。

InMemoryArtifactService

  • 存储机制:
    • Python:使用 Python 字典(self.artifacts)存储在应用内存中。字典键表示制品路径,值为 types.Part 列表,每个列表元素为一个版本。
    • Java:使用嵌套的 HashMap 实例(private final Map<String, Map<String, Map<String, Map<String, List<Part>>>>> artifacts;)存储在内存中。各级键分别为 appNameuserIdsessionIdfilename。最内层的 List<Part> 存储制品的各个版本,列表索引即为版本号。
  • 主要特性:
    • 简单: 除核心 ADK 库外无需额外设置或依赖。
    • 速度快: 操作通常非常快,因为只涉及内存中的 map/dict 查找和列表操作。
    • 临时性: 所有存储的制品在应用进程终止时丢失。数据不会在应用重启间持久化。
  • 适用场景:
    • 适合本地开发和测试,无需持久化。
    • 适合短时演示或制品数据仅在单次应用运行中临时存在的场景。
  • 实例化:

    from google.adk.artifacts import InMemoryArtifactService
    
    # 直接实例化类
    in_memory_service_py = InMemoryArtifactService()
    
    # 然后传递给 Runner
    # runner = Runner(..., artifact_service=in_memory_service_py)
    
    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(
            //     /* 其他服务 */,
            //     inMemoryServiceJava
            // );
        }
    }
    

GcsArtifactService

  • 存储机制: 利用 Google Cloud Storage (GCS) 进行持久化制品存储。每个制品的每个版本作为单独对象(blob)存储在指定的 GCS bucket 中。
  • 对象命名规范: 使用分层路径结构构建 GCS 对象名(blob 名)。
  • 主要特性:
    • 持久化: 存储在 GCS 的制品在应用重启和部署间持久存在。
    • 可扩展性: 利用 Google Cloud Storage 的可扩展性和持久性。
    • 版本控制: 明确将每个版本作为独立的 GCS 对象存储。GcsArtifactServicesaveArtifact 方法实现。
    • 权限要求: 应用环境需要有适当的凭据(如 Application Default Credentials)和 IAM 权限以读写指定的 GCS bucket。
  • 适用场景:
    • 需要持久化制品存储的生产环境。
    • 需要在不同应用实例或服务间共享制品的场景(通过访问同一 GCS bucket)。
    • 需要长期存储和检索用户或会话数据的应用。
  • 实例化:

    from google.adk.artifacts import GcsArtifactService
    
    # 指定 GCS bucket 名称
    gcs_bucket_name_py = "your-gcs-bucket-for-adk-artifacts" # 替换为你的 bucket 名称
    
    try:
        gcs_service_py = GcsArtifactService(bucket_name=gcs_bucket_name_py)
        print(f"Python GcsArtifactService 已初始化,bucket: {gcs_bucket_name_py}")
        # 确保你的环境有访问该 bucket 的凭据。
        # 如通过 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_artifactload_artifactlist_artifacts)之前,始终检查 artifact_service 是否实际配置——如果服务为 None,它们会抛出 ValueError
    • 检查 load_artifact 的返回值,因为如果制品或版本不存在,它将为 None。不要假设它总是返回 Part
    • 准备好处理来自底层存储服务的异常,特别是使用 GcsArtifactService 时(例如,权限问题的 google.api_core.exceptions.Forbidden,桶不存在的 NotFound,网络错误)。
  • 大小考虑: 制品适用于典型的文件大小,但要注意极大文件可能带来的成本和性能影响,特别是云存储。InMemoryArtifactService 在存储许多大制品时可能消耗大量内存。评估是否应该通过直接 GCS 链接或其他专门的存储解决方案而不是在内存中传递整个字节数组来更好地处理非常大的数据。
  • 清理策略: 对于像 GcsArtifactService 这样的持久存储,制品会保留直到显式删除。如果制品代表临时数据或有有限的生命周期,请实施清理策略。这可能涉及:
    • 在桶上使用 GCS 生命周期策略。
    • 构建利用 artifact_service.delete_artifact 方法的特定工具或管理功能(注意:为了安全起见,删除操作通过上下文对象暴露)。
    • 谨慎管理文件名以允许基于模式的删除(如果需要)。