何の話かと言うと
ADK 1.14.0 で、root agent と plugins をまとめるコンテナの役割をする App クラスが提供されました。一方、root agent を Agent Engine にデプロイする際に使用する AdkApp クラスを用いて、root agent と plugins をまとめる事もできます。
この記事では、これらの違いを説明します。
環境準備
Vertex AI workbench のノートブック上で実装しながら説明するために、まずは、ノートブックの実行環境を用意しましょう。Google Cloud の新しいプロジェクトを作成したら、Cloud Shell のコマンド端末を開いて、必要な API を有効化します。
gcloud services enable \ aiplatform.googleapis.com \ notebooks.googleapis.com \ cloudresourcemanager.googleapis.com
続いて、Workbench のインスタンスを作成します。
PROJECT_ID=$(gcloud config list --format 'value(core.project)') gcloud workbench instances create agent-development \ --project=$PROJECT_ID \ --location=us-central1-a \ --machine-type=e2-standard-2
クラウドコンソールのナビゲーションメニューから「Vertex AI」→「Workbench」を選択すると、作成したインスタンス agent-development があります。インスタンスの起動が完了するのを待って、「JUPYTERLAB を開く」をクリックしたら、「Python 3(ipykernel)」の新規ノートブックを作成します。
この後は、ノートブックのセルでコードを実行していきます。
プラグインを使用するサンプルアプリ
ここでは説明用のサンプルとして、次の処理を行う画像分析エージェントを実装します。
- ユーザーは、分析内容を指示するテキストと inline_image の画像データを入力
- プラグイン
SaveFilesAsArtifactsPluginが画像データを Artifact として保存 - root agent は、(必要な際は)
load_artifactsツールを用いて、Artifact から画像データを取得して分析する
この後で触れるように、load_artifacts ツールは、取得したデータをセッションに残さないので、バイナリデータでセッションのデータ量が増大することを防止できます。
App クラスを使用する例
コードの全体は、下記のノートブックを参照してください。
最新版の ADK パッケージをインストールします。
%pip install --upgrade --user google-adk
パッケージを反映するために Kernel をリスタートします。
# Reboot kernel import IPython app = IPython.Application.instance() _ = app.kernel.do_shutdown(True)
必要なモジュールをインポートして、環境を初期化します。
import base64 import os import vertexai from google.adk.agents import LlmAgent from google.adk.apps import App from google.adk.artifacts import InMemoryArtifactService from google.adk.planners import BuiltInPlanner from google.adk.plugins.save_files_as_artifacts_plugin import SaveFilesAsArtifactsPlugin from google.adk.runners import Runner from google.adk.sessions import InMemorySessionService from google.adk.tools import load_artifacts from google.genai.types import Part, Content, Blob, ThinkingConfig [PROJECT_ID] = !gcloud config list --format 'value(core.project)' LOCATION = 'us-central1' vertexai.init(project=PROJECT_ID, location=LOCATION) os.environ['GOOGLE_CLOUD_PROJECT'] = PROJECT_ID os.environ['GOOGLE_CLOUD_LOCATION'] = LOCATION os.environ['GOOGLE_GENAI_USE_VERTEXAI'] = 'True'
root agent を定義して、plungins と共に App クラスにまとめます。
root_agent = LlmAgent(
name='image_analyst_agent',
model='gemini-2.5-flash',
instruction='''
Your role is to analyze given image files.
1. When the user mentions some "images", identify the exact artifact for each image that the user intends from the given conversation history.
2. Use load_artifacts() to get the actual contents of artifacts that you identified on step 1.
3. Analyze the images according to the user's direction.
''',
tools=[load_artifacts],
planner=BuiltInPlanner(
thinking_config=ThinkingConfig(
include_thoughts=False,
thinking_budget=0,
)
),
)
app = App(
root_agent=root_agent,
name='iamge_analyzer_app',
plugins=[SaveFilesAsArtifactsPlugin()],
)
そして、App クラスのオブジェクトを実際に利用する ChatClient クラスを定義します。
# Chat client to test app class ChatClient: def __init__(self, app, user_id='default_user'): self._app = app self._user_id = user_id self._runner = Runner( app=self._app, artifact_service=InMemoryArtifactService(), session_service=InMemorySessionService(), ) self._session = None async def stream(self, content): if not self._session: self._session = await self._runner.session_service.create_session( app_name=self._app.name, user_id=self._user_id, ) if isinstance(content, str): content = Content(role='user', parts=[Part.from_text(text=content)]) async_events = self._runner.run_async( user_id=self._user_id, session_id=self._session.id, new_message=content, ) result = [] async for event in async_events: print('====') print(event) print('====') if (event.content and event.content.parts): response = '\n'.join([p.text for p in event.content.parts if p.text]) if response: print(response) result.append(response) return result
ここでのポイントは、次の部分です。Runner クラスのインスタンスを生成する際に、app オプションに App オブジェクトを渡すと、App オブジェクトに含まれた plugins を含んだ Runner が用意されます。
self._runner = Runner(
app=self._app,
artifact_service=InMemoryArtifactService(),
session_service=InMemorySessionService(),
)
この Runner オブジェクトの run_async() メソッドを呼び出すと、自動的に plugins が適用されます。適当な画像ファイル testimage.png を workbench 環境にアップロードしておいて、次のコードを実行します。
def get_image_data(file_path: str): with open(file_path, 'rb') as f: image_bytes = f.read() return base64.b64encode(image_bytes).decode('utf-8') client = ChatClient(app) image_base64 = get_image_data('testimage.png') parts = [ Part.from_text(text='Count the number of people in the image.'), Part( inline_data=Blob( mime_type='image/png', data=image_base64, ), ), ] content = Content(role='user', parts=parts) _ = await client.stream(content)
発生したイベントを出力するので少し長くなりますが、実行結果は次のようになります。
====
model_version='gemini-2.5-flash' content=Content(
parts=[
Part(
function_call=FunctionCall(
args={
'artifact_names': [
'artifact_e-64b2ed1d-7960-43ee-967b-c8302232333d_1',
]
},
id='adk-edc8d038-7c64-4a5a-b63c-56e8d67fb3eb',
name='load_artifacts'
)
),
],
role='model'
) grounding_metadata=None partial=None turn_complete=None finish_reason=<FinishReason.STOP: 'STOP'> error_code=None error_message=None interrupted=None custom_metadata=None usage_metadata=GenerateContentResponseUsageMetadata(
candidates_token_count=46,
candidates_tokens_details=[
ModalityTokenCount(
modality=<MediaModality.TEXT: 'TEXT'>,
token_count=46
),
],
prompt_token_count=262,
prompt_tokens_details=[
ModalityTokenCount(
modality=<MediaModality.TEXT: 'TEXT'>,
token_count=262
),
],
total_token_count=308,
traffic_type=<TrafficType.ON_DEMAND: 'ON_DEMAND'>
) live_session_resumption_update=None input_transcription=None output_transcription=None avg_logprobs=-0.0003888878728384557 logprobs_result=None cache_metadata=None citation_metadata=None interaction_id=None invocation_id='e-64b2ed1d-7960-43ee-967b-c8302232333d' author='image_analyst_agent' actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None) long_running_tool_ids=set() branch=None id='801e2c45-4afa-41af-96e6-0b28a3e3670e' timestamp=1767208819.800647
====
====
model_version=None content=Content(
parts=[
Part(
function_response=FunctionResponse(
id='adk-edc8d038-7c64-4a5a-b63c-56e8d67fb3eb',
name='load_artifacts',
response={
'artifact_names': [
'artifact_e-64b2ed1d-7960-43ee-967b-c8302232333d_1',
],
'status': 'artifact contents temporarily inserted and removed. to access these artifacts, call load_artifacts tool again.'
}
)
),
],
role='user'
) grounding_metadata=None partial=None turn_complete=None finish_reason=None error_code=None error_message=None interrupted=None custom_metadata=None usage_metadata=None live_session_resumption_update=None input_transcription=None output_transcription=None avg_logprobs=None logprobs_result=None cache_metadata=None citation_metadata=None interaction_id=None invocation_id='e-64b2ed1d-7960-43ee-967b-c8302232333d' author='image_analyst_agent' actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None) long_running_tool_ids=None branch=None id='7ee0eece-f911-42b8-a42e-6fcaf88e6e1a' timestamp=1767208821.357935
====
====
model_version='gemini-2.5-flash' content=Content(
parts=[
Part(
text='There are three people in the image.'
),
],
role='model'
) grounding_metadata=None partial=None turn_complete=None finish_reason=<FinishReason.STOP: 'STOP'> error_code=None error_message=None interrupted=None custom_metadata=None usage_metadata=GenerateContentResponseUsageMetadata(
candidates_token_count=8,
candidates_tokens_details=[
ModalityTokenCount(
modality=<MediaModality.TEXT: 'TEXT'>,
token_count=8
),
],
prompt_token_count=1707,
prompt_tokens_details=[
ModalityTokenCount(
modality=<MediaModality.TEXT: 'TEXT'>,
token_count=417
),
ModalityTokenCount(
modality=<MediaModality.IMAGE: 'IMAGE'>,
token_count=1290
),
],
total_token_count=1715,
traffic_type=<TrafficType.ON_DEMAND: 'ON_DEMAND'>
) live_session_resumption_update=None input_transcription=None output_transcription=None avg_logprobs=-0.3051603138446808 logprobs_result=None cache_metadata=None citation_metadata=None interaction_id=None invocation_id='e-64b2ed1d-7960-43ee-967b-c8302232333d' author='image_analyst_agent' actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None) long_running_tool_ids=None branch=None id='a0889355-4449-4bbf-81b6-6221d313701f' timestamp=1767208821.359781
====
There are three people in the image.
load_artifacts ツールで、Artifact から画像ファイルを取得して処理していることがわかります。
なお、イベント内に 'status': 'artifact contents temporarily inserted and removed. to access these artifacts, call load_artifacts tool again.' というメッセージがあるように、load_artifacts ツールは、取得した Artifact のデータを LLM のプロンプトに挿入しますが、セッションにはそのデータを残しません。これにより、Artifact のバイナリデータでセッションのデータ量が増大することを防ぎます。
同じセッションで、さらに質問をしてみます。
_ = await client.stream('Count the number of dogs in the previous image.')
出力結果は次のようになります。
====
model_version='gemini-2.5-flash' content=Content(
parts=[
Part(
function_call=FunctionCall(
args={
'artifact_names': [
'artifact_e-64b2ed1d-7960-43ee-967b-c8302232333d_1',
]
},
id='adk-fb383601-bb8c-469c-8501-b84d4f860706',
name='load_artifacts'
)
),
],
role='model'
) grounding_metadata=None partial=None turn_complete=None finish_reason=<FinishReason.STOP: 'STOP'> error_code=None error_message=None interrupted=None custom_metadata=None usage_metadata=GenerateContentResponseUsageMetadata(
candidates_token_count=46,
candidates_tokens_details=[
ModalityTokenCount(
modality=<MediaModality.TEXT: 'TEXT'>,
token_count=46
),
],
prompt_token_count=392,
prompt_tokens_details=[
ModalityTokenCount(
modality=<MediaModality.TEXT: 'TEXT'>,
token_count=392
),
],
total_token_count=438,
traffic_type=<TrafficType.ON_DEMAND: 'ON_DEMAND'>
) live_session_resumption_update=None input_transcription=None output_transcription=None avg_logprobs=-0.009726571000140646 logprobs_result=None cache_metadata=None citation_metadata=None interaction_id=None invocation_id='e-888bcd6d-965d-4877-bbd4-ce8ea46b063d' author='image_analyst_agent' actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None) long_running_tool_ids=set() branch=None id='67f0094f-2415-4535-9fa9-1208d8b8a463' timestamp=1767208823.628544
====
====
model_version=None content=Content(
parts=[
Part(
function_response=FunctionResponse(
id='adk-fb383601-bb8c-469c-8501-b84d4f860706',
name='load_artifacts',
response={
'artifact_names': [
'artifact_e-64b2ed1d-7960-43ee-967b-c8302232333d_1',
],
'status': 'artifact contents temporarily inserted and removed. to access these artifacts, call load_artifacts tool again.'
}
)
),
],
role='user'
) grounding_metadata=None partial=None turn_complete=None finish_reason=None error_code=None error_message=None interrupted=None custom_metadata=None usage_metadata=None live_session_resumption_update=None input_transcription=None output_transcription=None avg_logprobs=None logprobs_result=None cache_metadata=None citation_metadata=None interaction_id=None invocation_id='e-888bcd6d-965d-4877-bbd4-ce8ea46b063d' author='image_analyst_agent' actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None) long_running_tool_ids=None branch=None id='e1e8c0cb-ffea-4874-9eb4-422706de3599' timestamp=1767208825.20606
====
====
model_version='gemini-2.5-flash' content=Content(
parts=[
Part(
text='There are no dogs in the image.'
),
],
role='model'
) grounding_metadata=None partial=None turn_complete=None finish_reason=<FinishReason.STOP: 'STOP'> error_code=None error_message=None interrupted=None custom_metadata=None usage_metadata=GenerateContentResponseUsageMetadata(
candidates_token_count=8,
candidates_tokens_details=[
ModalityTokenCount(
modality=<MediaModality.TEXT: 'TEXT'>,
token_count=8
),
],
prompt_token_count=1837,
prompt_tokens_details=[
ModalityTokenCount(
modality=<MediaModality.TEXT: 'TEXT'>,
token_count=547
),
ModalityTokenCount(
modality=<MediaModality.IMAGE: 'IMAGE'>,
token_count=1290
),
],
total_token_count=1845,
traffic_type=<TrafficType.ON_DEMAND: 'ON_DEMAND'>
) live_session_resumption_update=None input_transcription=None output_transcription=None avg_logprobs=-0.028248507529497147 logprobs_result=None cache_metadata=None citation_metadata=None interaction_id=None invocation_id='e-888bcd6d-965d-4877-bbd4-ce8ea46b063d' author='image_analyst_agent' actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None) long_running_tool_ids=None branch=None id='5d4d22d6-3d4d-4ddb-91c8-7f89adb97f1b' timestamp=1767208825.207937
====
There are no dogs in the image.
セッションに Artifact のデータが残っていないので、再度、load_artifacts ツールでデータを取得しています。
AdkApp クラスを使用する例
新しいノートブックを作成して、実行していきます。コードの全体は、下記のノートブックを参照してください。
必要なモジュールをインポートして、環境を初期化します。
import base64 import os import vertexai from vertexai.agent_engines import AdkApp from google.adk.agents import LlmAgent from google.adk.artifacts import GcsArtifactService from google.adk.planners import BuiltInPlanner from google.adk.plugins.save_files_as_artifacts_plugin import SaveFilesAsArtifactsPlugin from google.adk.tools import load_artifacts from google.genai.types import Part, Content, Blob, ThinkingConfig [PROJECT_ID] = !gcloud config list --format 'value(core.project)' LOCATION = 'us-central1' vertexai.init(project=PROJECT_ID, location=LOCATION) os.environ['GOOGLE_CLOUD_PROJECT'] = PROJECT_ID os.environ['GOOGLE_CLOUD_LOCATION'] = LOCATION os.environ['GOOGLE_GENAI_USE_VERTEXAI'] = 'True' BUCKET = f'{PROJECT_ID}_artifacts' !gsutil ls -b gs://{BUCKET} 2>/dev/null || \ gsutil mb -l {LOCATION} gs://{BUCKET}
今回は、作成したエージェントを Agent Engine にデプロイするために、Artifact サービスに GcsArtifactService を使用して、Artifact を Cloud Storage のバケットに永続化します。
root agent を定義して、plugins と共に AdkApp クラスにまとめます。artifact_service_builder オプションで GcsArtifactService の使用を指定しています。
root_agent = LlmAgent(
name='image_analyst_agent',
model='gemini-2.5-flash',
instruction='''
Your role is to analyze given image files.
Use load_artifacts() if the image content is not in the context.
''',
tools=[load_artifacts],
planner=BuiltInPlanner(
thinking_config=ThinkingConfig(
include_thoughts=False,
thinking_budget=0,
)
),
)
def artifact_builder():
return GcsArtifactService(bucket_name=BUCKET)
app = AdkApp(
agent=root_agent,
app_name='iamge_analyzer_app',
artifact_service_builder=artifact_builder,
plugins=[SaveFilesAsArtifactsPlugin()],
)
先ほどの App クラスとの大きな違いとして、AdkApp クラスは、Runner オブジェクトを内包しており、AdkApp クラスのオブジェクトが提供する async_create_session メソッドによって、エージェントが実行できます。実際にエージェントを利用する ChatClient クラスは、次のように定義されます。
# Chat client to test AdkApp class ChatClient: def __init__(self, app, user_id='default_user'): self._app = app self._user_id = user_id self._session_id = None async def async_stream_query(self, message): if not self._session_id: session = await self._app.async_create_session( user_id=self._user_id, ) self._session_id = getattr(session, 'id', None) or session['id'] result = [] async for event in self._app.async_stream_query( user_id=self._user_id, session_id=self._session_id, message=message, ): print('====') print(event) print('====') if ('content' in event and 'parts' in event['content']): response = '\n'.join( [p['text'] for p in event['content']['parts'] if 'text' in p] ) if response: print(response) result.append(response) return result
実行例は次のようになります。
def get_image_data(file_path: str): with open(file_path, 'rb') as f: image_bytes = f.read() return base64.b64encode(image_bytes).decode('utf-8') client = ChatClient(app) image_base64 = get_image_data('testimage.png') message_input = { 'role': 'user', 'parts': [ {'text': 'Count the number of people in the image.'}, { 'inline_data': { 'mime_type': 'image/png', 'data': image_base64 } } ] } # The agent loads the image content from artifacts and insert into the prompt. _ = await client.async_stream_query(message_input)
Runner オブジェクトの run_async() メソッドには、Content オブジェクトを渡しましたが、こちらは、通常の Dict オブジェクトになっています。Agent Engine にデプロイした際は、シリアライズ可能な Dict オブジェクトを利用する必要があるためです。テキストのみを入力する際は、テキストストリングを直接渡すこともできます。
実行結果は、次のようになります。
====
{'model_version': 'gemini-2.5-flash', 'content': {'parts': [{'function_call': {'id': 'adk-0966c56e-6661-4c75-b083-d0feda8c99a6', 'args': {'artifact_names': ['artifact_e-b3ca3385-c4b7-46d6-a22d-12105fa5351d_1']}, 'name': 'load_artifacts'}}], 'role': 'model'}, 'finish_reason': 'STOP', 'usage_metadata': {'candidates_token_count': 46, 'candidates_tokens_details': [{'modality': 'TEXT', 'token_count': 46}], 'prompt_token_count': 1552, 'prompt_tokens_details': [{'modality': 'TEXT', 'token_count': 262}, {'modality': 'IMAGE', 'token_count': 1290}], 'total_token_count': 1598, 'traffic_type': 'ON_DEMAND'}, 'avg_logprobs': -0.0021358010885508165, 'invocation_id': 'e-b3ca3385-c4b7-46d6-a22d-12105fa5351d', 'author': 'image_analyst_agent', 'actions': {'state_delta': {}, 'artifact_delta': {}, 'requested_auth_configs': {}, 'requested_tool_confirmations': {}}, 'long_running_tool_ids': [], 'id': 'ee593517-0faa-45d5-96c7-ac11d8f46271', 'timestamp': 1767211209.821492}
====
====
{'content': {'parts': [{'function_response': {'id': 'adk-0966c56e-6661-4c75-b083-d0feda8c99a6', 'name': 'load_artifacts', 'response': {'artifact_names': ['artifact_e-b3ca3385-c4b7-46d6-a22d-12105fa5351d_1'], 'status': 'artifact contents temporarily inserted and removed. to access these artifacts, call load_artifacts tool again.'}}}], 'role': 'user'}, 'invocation_id': 'e-b3ca3385-c4b7-46d6-a22d-12105fa5351d', 'author': 'image_analyst_agent', 'actions': {'state_delta': {}, 'artifact_delta': {}, 'requested_auth_configs': {}, 'requested_tool_confirmations': {}}, 'id': '3f09671e-6fec-47ae-b1b9-5d37165f3dc3', 'timestamp': 1767211212.024847}
====
====
{'model_version': 'gemini-2.5-flash', 'content': {'parts': [{'text': 'There are 3 people in the image.'}], 'role': 'model'}, 'finish_reason': 'STOP', 'usage_metadata': {'candidates_token_count': 9, 'candidates_tokens_details': [{'modality': 'TEXT', 'token_count': 9}], 'prompt_token_count': 933, 'prompt_tokens_details': [{'modality': 'TEXT', 'token_count': 417}, {'modality': 'IMAGE', 'token_count': 516}], 'total_token_count': 942, 'traffic_type': 'ON_DEMAND'}, 'avg_logprobs': -0.05960859855016073, 'invocation_id': 'e-b3ca3385-c4b7-46d6-a22d-12105fa5351d', 'author': 'image_analyst_agent', 'actions': {'state_delta': {}, 'artifact_delta': {}, 'requested_auth_configs': {}, 'requested_tool_confirmations': {}}, 'id': '30b39391-50e9-483e-aa8f-2bb4178ed631', 'timestamp': 1767211212.16356}
====
There are 3 people in the image.
同じセッションで追加の質問をします。
_ = await client.async_stream_query('Count the number of dogs in the previous image.')
出力結果は次のようになります。
====
{'model_version': 'gemini-2.5-flash', 'content': {'parts': [{'function_call': {'id': 'adk-9e4ae8b9-2f63-49aa-a6c3-fefd80c5f5ba', 'args': {'artifact_names': ['artifact_e-b3ca3385-c4b7-46d6-a22d-12105fa5351d_1']}, 'name': 'load_artifacts'}}], 'role': 'model'}, 'finish_reason': 'STOP', 'usage_metadata': {'candidates_token_count': 46, 'candidates_tokens_details': [{'modality': 'TEXT', 'token_count': 46}], 'prompt_token_count': 1683, 'prompt_tokens_details': [{'modality': 'TEXT', 'token_count': 393}, {'modality': 'IMAGE', 'token_count': 1290}], 'total_token_count': 1729, 'traffic_type': 'ON_DEMAND'}, 'avg_logprobs': -0.016883881195731785, 'invocation_id': 'e-e450f8eb-13f9-4d27-b372-1057feef5ee2', 'author': 'image_analyst_agent', 'actions': {'state_delta': {}, 'artifact_delta': {}, 'requested_auth_configs': {}, 'requested_tool_confirmations': {}}, 'long_running_tool_ids': [], 'id': 'f15e0a66-692c-4c4b-9a46-014af54a6a20', 'timestamp': 1767211214.18387}
====
====
{'content': {'parts': [{'function_response': {'id': 'adk-9e4ae8b9-2f63-49aa-a6c3-fefd80c5f5ba', 'name': 'load_artifacts', 'response': {'artifact_names': ['artifact_e-b3ca3385-c4b7-46d6-a22d-12105fa5351d_1'], 'status': 'artifact contents temporarily inserted and removed. to access these artifacts, call load_artifacts tool again.'}}}], 'role': 'user'}, 'invocation_id': 'e-e450f8eb-13f9-4d27-b372-1057feef5ee2', 'author': 'image_analyst_agent', 'actions': {'state_delta': {}, 'artifact_delta': {}, 'requested_auth_configs': {}, 'requested_tool_confirmations': {}}, 'id': '32e8d6da-443a-4a4e-8807-368969670cfb', 'timestamp': 1767211216.511607}
====
====
{'model_version': 'gemini-2.5-flash', 'content': {'parts': [{'text': 'There are no dogs in the image.'}], 'role': 'model'}, 'finish_reason': 'STOP', 'usage_metadata': {'cache_tokens_details': [{'modality': 'IMAGE', 'token_count': 284}, {'modality': 'TEXT', 'token_count': 302}], 'cached_content_token_count': 586, 'candidates_token_count': 8, 'candidates_tokens_details': [{'modality': 'TEXT', 'token_count': 8}], 'prompt_token_count': 1064, 'prompt_tokens_details': [{'modality': 'IMAGE', 'token_count': 516}, {'modality': 'TEXT', 'token_count': 548}], 'total_token_count': 1072, 'traffic_type': 'ON_DEMAND'}, 'avg_logprobs': -0.031230559572577477, 'invocation_id': 'e-e450f8eb-13f9-4d27-b372-1057feef5ee2', 'author': 'image_analyst_agent', 'actions': {'state_delta': {}, 'artifact_delta': {}, 'requested_auth_configs': {}, 'requested_tool_confirmations': {}}, 'id': '80d028e2-e90c-49b1-a8b9-8b03b69d103d', 'timestamp': 1767211216.651785}
====
There are no dogs in the image.
なお、2 回目の質問に対しては、Gemini API が持っている implicit context caching(暗黙のコンテクストキャッシング)の機能が利用されて、Artifact の再ロードが回避される場合があります。
Agent Engine にデプロイする例
AdkApp クラスのオブジェクトは、次のコードで Agent Engine にデプロイできます。
agent_engines = vertexai.Client().agent_engines display_name = 'Image Analyzer App' remote_app = None for item in agent_engines.list(): if item.api_resource.display_name == display_name: remote_app = agent_engines.get(name=item.api_resource.name) break if not remote_app: remote_app = agent_engines.create( agent=app, config={ 'agent_framework': 'google-adk', 'requirements': ['google-adk==1.21.0'], 'staging_bucket': f'gs://{PROJECT_ID}', 'display_name': display_name, } )
デプロイしたインスタンスのクライアントオブジェクトが変数 remote_app に格納されますが、これは、ローカルのオブジェクト(先ほどの例では、変数 app に格納された AdkApp クラスのオブジェクト)と同様に利用できます。次のように、先ほど用意した ChatClient クラスがそのまま再利用できます。
client = ChatClient(remote_app) image_base64 = get_image_data('testimage.png') message_input = { 'role': 'user', 'parts': [ {'text': 'describe the image'}, { 'inline_data': { 'mime_type': 'image/png', 'data': image_base64 } } ] } _ = await client.async_stream_query(message_input)
まとめ
ここまでの例からわかるように、App クラスと AdkApp クラスは、いずれも、root agent と plugins をまとめるコンテナクラスになりますが、AdkApp クラスは、エージェントの実行に必要な Runner の機能を含んでいる点が大きな違いになります。
さらに、AdkApp クラスには、ローカルに用意したオブジェクトと Agent Engine にデプロイしたリモートオブジェクトを区別なく利用できるという利点があります。Agent Engine を利用する際は、AdkApp クラスを利用することで、ローカルでのテストとデプロイ後のテストが同一のコードで実行できるので特に便利です。