制品¶
在 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 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、电子表格或与你的智能体功能相关的任何其他文件格式。
- 持久化大数据: 会话状态通常不适合存储大量数据。制品提供了一种专用机制,可以在不使会话状态混乱的情况下持久化更大的二进制数据。
- 用户文件管理: 提供用户上传文件(可以作为制品保存)以及检索或下载由智能体生成的文件(从制品加载)的功能。
- 共享输出: 使工具或智能体能够生成二进制输出(如 PDF 报告或生成的图像),可以通过
save_artifact
保存,并在应用程序的其他部分甚至在后续会话中(如果使用用户命名空间)访问。 - 缓存二进制数据: 将产生二进制数据的计算密集型操作结果(例如,渲染复杂图表图像)存储为制品,以避免在后续请求中重新生成它们。
本质上,每当你的智能体需要处理需要持久化、版本控制或共享的类似文件的二进制数据时,由 ArtifactService
管理的制品是 ADK 中的适当机制。
常见用例¶
制品提供了在 ADK 应用程序中处理二进制数据的灵活方式。
以下是它们证明有价值的一些典型场景:
-
生成的报告/文件:
- 工具或智能体生成报告(如 PDF 分析、CSV 数据导出、图像图表)。
-
处理用户上传:
- 用户通过前端界面上传文件(如用于分析的图像、用于摘要的文档)。
-
存储中间二进制结果:
- 智能体执行复杂的多步流程,其中某一步生成中间二进制数据(如音频合成、仿真结果)。
-
持久化用户数据:
- 存储不适合简单键值状态的用户特定配置或数据。
-
缓存生成的二进制内容:
- 智能体经常基于某些输入生成相同的二进制输出(如公司 logo 图像、标准音频问候语)。
-
接口: 由抽象基类
BaseArtifactService
(google.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
时提供一个制品服务实例(如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
# 示例:用 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
对象,包含:data
(bytes
):制品的原始二进制内容。-
mime_type
(str
):标准 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_name
、user_id
和session_id
关联。它只能在该确切的会话上下文中访问。 -
内部路径(示例):
app_name/user_id/session_id/report.pdf/<version>
(在GcsArtifactService._get_blob_name
和InMemoryArtifactService._artifact_path
中可见) -
用户范围(
"user:"
前缀): 如果你在文件名前加上"user:"
,如"user:profile.png"
,则该制品只与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 框架内管理二进制数据。
与制品交互(通过上下文对象)¶
在智能体逻辑中(特别是在回调或工具中)与制品交互的主要方式是通过 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
。
在 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_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_name
、user_id
、session_id
和 filename
(包括 user:
命名空间前缀)处理制品数据的存储、版本控制和检索细节。
InMemoryArtifactService¶
- 存储机制:
- Python:使用 Python 字典(
self.artifacts
)存储在应用内存中。字典键表示制品路径,值为types.Part
列表,每个列表元素为一个版本。 - Java:使用嵌套的
HashMap
实例(private final Map<String, Map<String, Map<String, Map<String, List<Part>>>>> artifacts;
)存储在内存中。各级键分别为appName
、userId
、sessionId
和filename
。最内层的List<Part>
存储制品的各个版本,列表索引即为版本号。
- Python:使用 Python 字典(
- 主要特性:
- 简单: 除核心 ADK 库外无需额外设置或依赖。
- 速度快: 操作通常非常快,因为只涉及内存中的 map/dict 查找和列表操作。
- 临时性: 所有存储的制品在应用进程终止时丢失。数据不会在应用重启间持久化。
- 适用场景:
- 适合本地开发和测试,无需持久化。
- 适合短时演示或制品数据仅在单次应用运行中临时存在的场景。
-
实例化:
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 对象存储。
GcsArtifactService
的saveArtifact
方法实现。 - 权限要求: 应用环境需要有适当的凭据(如 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
实现取决于你的应用程序对数据持久性、可扩展性和操作环境的要求。
最佳实践¶
要有效且可维护地使用制品:
- Choose the Right Service: Use
InMemoryArtifactService
for rapid prototyping, testing, and scenarios where persistence isn't needed. UseGcsArtifactService
(or implement your ownBaseArtifactService
for other backends) for production environments requiring data persistence and scalability. - Meaningful Filenames: Use clear, descriptive filenames. Including relevant extensions (
.pdf
,.png
,.wav
) helps humans understand the content, even though themime_type
dictates programmatic handling. Establish conventions for temporary vs. persistent artifact names. - Specify Correct MIME Types: Always provide an accurate
mime_type
when creating thetypes.Part
forsave_artifact
. This is critical for applications or tools that laterload_artifact
to interpret thebytes
data correctly. Use standard IANA MIME types where possible. - Understand Versioning: Remember that
load_artifact()
without a specificversion
argument retrieves the latest version. If your logic depends on a specific historical version of an artifact, be sure to provide the integer version number when loading. - Use Namespacing (
user:
) Deliberately: Only use the"user:"
prefix for filenames when the data truly belongs to the user and should be accessible across all their sessions. For data specific to a single conversation or session, use regular filenames without the prefix. - Error Handling:
- Always check if an
artifact_service
is actually configured before calling context methods (save_artifact
,load_artifact
,list_artifacts
) – they will raise aValueError
if the service isNone
. - Check the return value of
load_artifact
, as it will beNone
if the artifact or version doesn't exist. Don't assume it always returns aPart
. - Be prepared to handle exceptions from the underlying storage service, especially with
GcsArtifactService
(e.g.,google.api_core.exceptions.Forbidden
for permission issues,NotFound
if the bucket doesn't exist, network errors).
- Always check if an
- Size Considerations: Artifacts are suitable for typical file sizes, but be mindful of potential costs and performance impacts with extremely large files, especially with cloud storage.
InMemoryArtifactService
can consume significant memory if storing many large artifacts. Evaluate if very large data might be better handled through direct GCS links or other specialized storage solutions rather than passing entire byte arrays in-memory. - Cleanup Strategy: For persistent storage like
GcsArtifactService
, artifacts remain until explicitly deleted. If artifacts represent temporary data or have a limited lifespan, implement a strategy for cleanup. This might involve:- Using GCS lifecycle policies on the bucket.
- Building specific tools or administrative functions that utilize the
artifact_service.delete_artifact
method (note: delete is not exposed via context objects for safety). - Carefully managing filenames to allow pattern-based deletion if needed.